Article View: pl.comp.objects
Article #17250Re: Composition Filters [uwaga dlugie]
From: Adam Przybylek
Date: Thu, 16 Aug 2007 16:35
Date: Thu, 16 Aug 2007 16:35
417 lines
12506 bytes
12506 bytes
Jeszcze raz to samo tylko poprawiłem kodowanie na polskie.
W poprzednim poście napisałem, że znalazłem częściowo odpowiedź na
pytanie dlaczego CFs się nie upowszechniły. Otóż celem CFs było
dostarczenie mechanizmu umożliwiającego lepszą separację zagadnień (SoC
- Separation of Concerns) niż w paradygmacie obiektowym. Moje zdanie
jest takie, że wykorzystując CFs dla niektórych przypadków można
osiągnąć lepszą SoC, ale nie ma ona większego wpływu na poprawę
evolvability i reusability.
Poniżej przykład na problemy jakie powstają przy implementacji w Javie
oraz próba (bezowocna) rozwiązania ich przy pomocy dodania do Javy
filtrów input (hipotetyczna składnia).
Załóżmy, że tworząc nowy moduł chcemy w jak największym stopniu
wykorzystać istniejące moduły. Ponadto na każdym etapie chcemy zrobić
dobrą implementację pod aktualne wymagania, a nie pod ewentualne
przyszłe, o których nic nie wiemy. Zapominamy o istnieniu gotowych
pakietów Javy. Załóżmy także, że posiadamy w repozytorium następującą
implementację kolejki:
public class Queue {
protected Object[] buf;
protected int numItems;
protected int nextToRemove;
public Queue(int capacity) {
buf = new Object[capacity];
}
public boolean isFull() {
return (numItems==buf.length);
}
public boolean isEmpty() {
return (numItems==0);
}
public int size() {
return numItems;
}
public boolean put(Object x) {
if ( isFull() ) return false;
buf[(nextToRemove+numItems) % buf.length] = x;
numItems++;
return true;
}
public Object get() {
Object tmp=null;
if ( !isEmpty() ) {
tmp = buf[nextToRemove];
numItems--;
nextToRemove = (nextToRemove+1) % buf.length;
}
return tmp;
}
}
Etap 1. Należy zaimplementować klasę Buffer pod klasyczny problem
producenta-konsumenta.
a) rozwiązanie Java
public class Buffer extends Queue {
public Buffer(int capacity) {
super(capacity);
}
public synchronized boolean put(Object x) {
while ( isFull() ) try {
wait();
} catch (InterruptedException e) {}
super.put(x);
notifyAll();
return true;
}
public synchronized Object get() {
while ( isEmpty() ) try {
wait();
} catch (InterruptedException e) {}
Object tmp = super.get();
notifyAll();
return tmp;
}
}
Należy zauważyć, że zagadnienie synchronizacji (zagadnienie
przecinające) jest splecione z zagadnieniem odkładania i pobierania
danych (zagadnienie podstawowe).
b) rozwiązanie CFs
public class BufferCF {
private Queue delegate;
filter Wait {
delegate.isFull() => put(Object),
delegate.isEmpty() => get()
};
filter Dispatch { true => delegate.* };
}
Działa to w ten sposób, że każdy komunikat przychodzący z zewnątrz do
obiektu przechodzi najpierw przez filtr Wait, a następnie przez filtr
Dispatch. Pierwszy wpis w filtrze Wait mówi, aby uśpić wątek wykonujący
put(Object) na pełnym buforze, a drugi, aby uśpić wątek wykonujący get()
na pustym buforze.
Nałożenie na metodę filtru Wait sprawia, że staje się ona synchroniczna.
Ponadto sprawdzenie warunku (również synchroniczne) i ewentualne
rozpoczęcie metody jest atomowe.
Z kolei zastosowany filtr Dispatch mówi, aby każdy komunikat (*)
bezwarunkowo (true) przekazać do obsłużenia obiektowi delegate. Gdyby
filtr Dispatch zawierał więcej wpisów rozpatrywane by były od lewej do
prawej tak długo aż skojarzony z którymś warunek będzie spełniony. Jeśli
żaden warunek nie zostanie spełniony to rzucany jest run-time error.
Etap 2. Przychodzi nowe wymaganie - dla każdego elementu wkładanego do
bufora należy pamiętać datę kiedy został on włożony. Ponadto umożliwić
pobranie elementu razem z tą datą.
a) rozwiązanie Java
public class HistoryBuffer extends Buffer {
protected Queue delegateDates;
public HistoryBuffer(int capacity) {
super(capacity);
delegateDates = new Queue(capacity);
}
public synchronized boolean put(Object x) {
while ( isFull() ) try {
wait();
} catch (InterruptedException e) {}
super.put(x);
delegateDates.put( new Date() );
notifyAll();
return true;
}
public synchronized Object get() {
while ( isEmpty() ) try {
wait();
} catch (InterruptedException e) {}
Object tmp = super.get();
delegateDates.get();
notifyAll();
return tmp;
}
public synchronized Pair getWithDate() {
while ( isEmpty() ) try {
wait();
} catch (InterruptedException e) {}
Pair tmp = new Pair( super.get(), delegateDates.get() );
notifyAll();
return tmp;
}
}
b) rozwiazanie CFs
public class HistoryBufferCF {
private Queue delegateObjects;
private Queue delegateDates;
public HistoryBufferCF(int capacity) {
delegateObjects = new Queue(capacity);
delegateDates = new Queue(capacity);
}
public void put(Object x) {
delegateObjects.put(x);
delegateDates.put(new Date());
}
public Object get() {
delegateDates.get();
return delegateObjects.get();
}
public Pair getWithDate() {
return new Pair(delegateObjects.get(),delegateDates.get());
}
filter Wait {
delegateObjects.isFull() => put(Object),
delegateObjects.isEmpty() => { get(), getWithDate() }
};
filter Dispatch { true => this.*, true => delegateObjects.* };
}
Etap 3. Na potrzeby monitoringu należy logować informacje o stanie
bufora po każdej transakcji.
a) rozwiązanie Java
public class LogHistoryBuffer extends HistoryBuffer {
public LogHistoryBuffer(int capacity) {
super(capacity);
}
protected void log() {
System.out.println("stan bufora: "+size());
}
public synchronized boolean put(Object x) {
super.put(x);
log();
return true;
}
public synchronized Object get() {
Object tmp = super.get();
log();
return tmp;
}
public synchronized Pair getWithDate() {
Pair tmp = super.getWithDate();
log();
return tmp;
}
}
b) rozwiązanie CFs
public class LogHistoryBufferCF {
private HistoryBufferCF delegate;
private void log() {
System.out.println("stan bufora: "+delegate.size());
}
public void put(Object x) {
delegate.put(x);
log();
}
public Object get() {
Object tmp = delegate.get();
log();
return tmp;
}
public Pair getWithDate() {
Pair tmp = delegate.getWithDate();
log();
return tmp;
}
filter Wait {
delegate.isFull() => put(Object),
delegate.isEmpty() => { get(), getWithDate() }
};
filter Dispatch { true => this.*, true => delegate.* };
}
W obu implementacjach zagadnienie logowania (zagadnienie przecinające)
przeplatane jest z zagadnieniem podstawowym.
Etap 4. Stworzyć możliwość pobrania n kolejnych elementów w postaci
tablicy. Pobieranie rozpocząć jeśli istnieje w buforze przynajmniej
jeden element. Logowanie ma być pojedyncze dla każdej transakcji
(pobranie tablicy n elementów będzie logowane tylko raz).
a) rozwiązanie Java
public class EnhancedLogHistoryBuffer extends HistoryBuffer {
protected boolean lock;
public EnhancedLogHistoryBuffer(int capacity) {
super(capacity);
}
protected void lock(boolean b) {
lock = b;
}
protected boolean isLock() {
return lock;
}
protected void log() {
System.out.println("stan bufora: "+size());
}
public synchronized boolean put(Object x) {
super.put(x);
log();
return true;
}
public synchronized Object get() {
while ( isEmpty()||isLock() ) try {
wait();
} catch (InterruptedException e) {}
Object tmp = super.get();
log();
return tmp;
}
public synchronized Pair getWithDate() {
while ( isEmpty()||isLock() ) try {
wait();
} catch (InterruptedException e) {}
Pair tmp = super.getWithDate();
log();
return tmp;
}
public synchronized Object[] get(int n) {
while ( isEmpty()||isLock() ) try {
wait();
} catch (InterruptedException e) {}
lock(true);
Object[] tmp = new Object[n];
for(int i=0; i<n; i++) {
tmp[i] = super.get();
dates.get();
}
lock(false);
log();
return tmp;
}
public synchronized Pair[] getWithDate(int n) {
while ( isEmpty()||isLock() ) try {
wait();
} catch (InterruptedException e) {}
lock(true);
Pair[] tmp = new Pair[n];
for(int i=0; i<n; i++) tmp[i] = super.getWithDate();
lock(false);
log();
return tmp;
}
}
Implementując operacje get(int) i getWithDate(int) nie można wykorzystać
implementacji get() i getWithDate() z klasy LogHistoryBuffer ponieważ są
one splecione z zagadnieniem logowania. Ponieważ w Javie nie ma
możliwości, aby niepolimorficznie wywołać metodę zdefiniowaną w
określonej nadklasie, należy ponownie za punkt wyjścia przyjąć klasę
HistoryBuffer. Takie rozwiązanie wymaga ponownego zaimplementowania
metody log.
Alternatywne rozwiązanie polega na rozszerzeniu klasy LogHistoryBuffer i
"od zera" napisanie implementacji get(int) i getWithDate(int).
b) rozwiązanie CFs
public class EnhancedLogHistoryBufferCF {
private HistoryBufferCF delegate;
private boolean lock;
public EnhancedLogHistoryBufferCF(int capacity) {
delegate = new HistoryBufferCF(capacity);
}
public void lock(boolean b) {
lock = b;
}
public boolean isLock() {
return lock;
}
private void log() {
System.out.println("stan bufora: "+delegate.size());
}
public void put(Object x) {
delegate.put(x);
log();
}
public Object get() {
Object tmp = delegate.get();
log();
return tmp;
}
public Pair getWithDate() {
Pair tmp = delegate.getWithDate();
log();
return tmp;
}
public Object[] get(int n) {
lock(true);
Object[] tmp = new Object[n];
for(int i=0; i<n; i++) tmp[i] = delegate.get();
lock(false);
log();
return tmp;
}
public Pair[] getWithDate(int n) {
lock(true);
Pair[] tmp = new Pair[n];
for(int i=0; i<n; i++) tmp[i] = delegate.getWithDate();
lock(false);
log();
return tmp;
}
filter Wait {
delegate.isFull() => put(Object),
delegate.isEmpty() || isLock() => {
get(), getWithDate(), get(int), getWithDate(int)
}
};
filter Dispatch { true => this.*, true => delegate.* };
}
Podobnie jak w przypadku rozwiązania w Javie, trzeba ponownie
zaimplementować zagadnienie logowania. Ponadto zagadnienie
synchronizacji poprzez metodę lock zostało splecione z zagadnieniem
podstawowym.
***
Wszystkie przedstawione problemy związane z przeplataniem zagadnień
można rozwiązać za pomocą AspectJ, a część z nich w klasycznym języku OO
umożliwiającym niepolimorficzne wołanie metody.
Technical Details
Message-ID:
<fa1u6f$1j4$1@news.onet.pl>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!newsfeed.gazeta.pl!news.onet.pl!not-for-mailReferences:
<f738su$kn$1@news.onet.pl> <c68j93p3t4k797gp8cimgdifl25q1on43j@4ax.com> <1186094704.244462.260290@e9g2000prf.googlegroups.com> <c8q4b3d5kr5245mvpe7frtu5siik750r0n@4ax.com> <1186147569.379046.72670@r34g2000hsd.googlegroups.com> <fa1tpl$vru$2@news.onet.pl>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!newsfeed.gazeta.pl!news.onet.pl!not-for-mail From: Adam Przybylek <adampap1@vp.pl> Newsgroups: pl.comp.objects Subject: Re: Composition Filters [uwaga dlugie] Date: Thu, 16 Aug 2007 18:35:32 +0200 Organization: Onet.pl Lines: 417 Message-ID: <fa1u6f$1j4$1@news.onet.pl> References: <f738su$kn$1@news.onet.pl> <c68j93p3t4k797gp8cimgdifl25q1on43j@4ax.com> <1186094704.244462.260290@e9g2000prf.googlegroups.com> <c8q4b3d5kr5245mvpe7frtu5siik750r0n@4ax.com> <1186147569.379046.72670@r34g2000hsd.googlegroups.com> <fa1tpl$vru$2@news.onet.pl> NNTP-Posting-Host: 81.210.34.66 Mime-Version: 1.0 X-Trace: news.onet.pl 1187281936 1636 81.210.34.66 (16 Aug 2007 16:32:16 GMT) X-Complaints-To: usenet@news.onet.pl NNTP-Posting-Date: Thu, 16 Aug 2007 16:32:16 +0000 (UTC) X-Sender: 7oaVUrxaL5umdVGseRDCgA== In-Reply-To: <fa1tpl$vru$2@news.onet.pl> User-Agent: Thunderbird 1.5.0.8 (Windows/20061025) Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit