🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.objects
Article #17250

Re: Composition Filters [uwaga dlugie]

#17250
From: Adam Przybylek
Date: Thu, 16 Aug 2007 16:35
417 lines
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-mail
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>

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