Archive for the ‘wzorce projektowe w akcji’ Category

Observer patterns is one of the core design patterns that every developer should know and use. It let’s you create components with low coupling and adhere to Hollywood principle.

Some pseudo-code for adding new observer might look like this:

void addObserver(String ev, Observer ob);
producer.addObserver("myEventName", new Observer(){public void observe(Producer producer){}});

But there are at least 2 issues with this design:

  • You have to explicitly define event name
  • You can only pass reference to a producer object.
You could of course decide that a producer can only dispatch one type of events and we don’t need to explicitly name them, but usually that’s not the case.

So what if you wanted to define your events using some kind of interface, eg:


public interface MyEvent{}
producer.addListener("myEventName", new Observer<MyEvent>(){public void observe(MyEvent me){}});

So this is a little bit better but you still need to pass name of event as String.

So maybe we could use java generics to use power of type-safety at compile time and still have all the benefits of low-coupling. Something that could look like this:


public <T> void addListener(Class<T> e, Observer<T> o);
producer.addListener(MyEvent.class, new Observer<MyEvent>(){public void observe(MyEvent me){}});

Where first parameter could be used as EventType declaration.

This is very close but has still one flaw – it forces redundant code:

  • once event type is passed explicitly as event definition
  • second during observer creation – template parameter

So maybe it could be somehow simplified into something like this:

producer.addListener(new Observer<MyEvent>(){public void observe(MyEvent me){}});

Some of you might say that this is impossible in Java due to type-erasure. This is all true – but there’s second part of it – generic types are available at runtime through getGenericSuperClass/getGenericInterfaces methods.

You can see source code for my type-safe event system on github but I think it needs just some clarification – why do you need that ugly MethodProxy class.
So after writing DefaultImplementation of event dispatcher interface I found out that compiler would not allow call to Listener’s on method with passed event instance. So I decided to find proper method using reflection and only internally resign of type-safety.
MethodProxy class creates proxy upon instantiation so it will report any problems very close to their cause.

So here’s what you can find already in the repo:

  1. Dispatcher interface with default implementation
  2. Simple event interface used as entry point for all events
  3. Simple listener interface used as entry point for all listeners
I guess there might be sample usage scenario:
Declare your event:
</pre>
public class MyEvent implements ProcessorLifecycleEvent{}
Create event dispatcher and register your listener:
ProcessorLifecycleEventDispatcher eventDispatcher = new DefaultProcessorLifecycleEventDispatcher();
eventDispatcher.registerListener(new ProcessorLifecycleEventListener<MyEvent>(){
public void on(MyEvent e) {
// some logic here
}
});

Publish new event:

eventDispatcher.dispatch(new MyEvent(){});
In the sample above MyEvent is very simple but it could take some data through constructor and act as full-blown DTO which simplifies greatly interactions beacause listener code doesn’t have to do any runtime casts – see example.
The whole project is part of another thing – Liferay service without service builder, which I’m going to describe soon 🙂

So enjoy.

Introduction (OOP principles)

Today I’d like to present you a problem that most especially rookie developers find compelling. This is strongly connected with OOP basics, specifically with 2 fundamental truths:

  1. Use inheritance whenever possible.
  2. Prefer composition over inheritance.
So at first I had problems understanding how these 2 rules are related but as the time passed I learned to distinguish the two and recently in one of my projects I came across piece of code that is perfect example on why object composision/aggregation might be a better solution over inheritance.

Old way – using inheritance

Background – code usage

To start with some background – the code referred to is part of application that displays daily trend graph of some piece of data. It’s responsible for drawing coordinate system, x-axis, y-axis, some labels, legend lines, text-labels and of course data-curves. The code is executed on client-side and one of requirements is to support ie8 – which is why there was a need to support both SVG and VML drawing formats.

Classes responsibilites

The old apporach used in inheritance to support both formats and below you can find class diagram for the described classes. To start from the top:

  • GrapDrawer is a drawing interface – it’s main goal is to provide high-level methods for drawing different parts of graph used by othere pieces of application
  • AbstractGraphDrawer which is an interesting beast – because it provides a bridge between GraphDrawer interface and client-specific implementations – that is VML/SVG achieved set of abstract protected methods that encapsulate client-specific drawing API
  • SVGDrawer/VMLDrawer – these are both client-specific drawing apis.

Problem description

You might be asking yourselves why is there any problem with this approach. It has all the good parts:

  • there’s an interface for accessing public methods,
  • there’s an abstract method that acts as a bridge between drawer and
  • client-specific implementation.

Everything is encapsulated so user has no knowledge of what’s going on behing the secenes – so what’s the big deal.

This is all true but due to AbstractGraphDrawer dual nature it’s code is really hard to maintain. Both high and low-level API collide with each other – the former changes frequently where the latter very rarely. If you look closer you can see that all protected methods are there only to be overridden by implementation and they’re completely hidden from user. While all parts of public interface are not used in any way by the implementation.

New way – use object aggregation

Solution description

These two worlds interact with each other only through their interfaces. So it’s a perfect candidate for split – low-level methods should be hidden behind an interface that must be implemented by client-specific code, whereas high-level methods, used by some other code go to a public class which has reference to client-specific low-level drawing api.

This is all presented in the diagram below, where you can find:

  • GrapDrawer class which is used for high-level operations. Low level operations are executed by graphDrawerWorker
  • GraphDrawerWorker interface which defines set of required low-level operations
  • SVGDrawer/VMLDrawer classes which only implement GraphDrawerWorker and thus loose all coupling with high-level code

Summary

There’s still at least one question that needs some attention: how did this code creep into the application? The answer is fairly simple – it was built incrementally as business needs rose and suddenly AbstractGraphDrawer became a beast with two heads. Unfortunately for everyone who’s been working with it for longer time it was hidden. Only when someone with fresh look apporached a project, not only could this be spotted but also addressed.

All of you that can spot more basic OOP principles broken in original code (or a new one :P) please leave them in comments 🙂

Dzisiaj czas na przykład dość popularnego wzorca projektowego, czyli Obserwatora.

Problem, który będę tutaj opisywał dotyczy przechowywania referencji do obiektu w HashMapie. Obiekt jest dość prosty i zawiera tylko 2 własności, numer i nazwę, oto przykład:


class MyObject {

private String name;

private int number;

}

Żeby nie gmwatwać kodu pominę gettery i settery.

Do przechowywania obiektów wykorzystam równie prosty pojemnik, umożliwiający dodawanie gotowych obiektów i pobieranie ich wg nazwy, klasa nazywa się MyObjectContainer:


class MyObjectContainer {

private Map&lt;String,MyObject&gt;  myObjectsMap = new HashMap&lt;String,MyObject&gt;();

// 2 proste metody do wstawiania i pobierania obiektów

public MyObject get(String key){ // specjalnie nie implementuje interfejsu java.util.Map, bo tam by to nie przeszło

return myObjectsMap.get(key);

}

public MyObject putSimple(MyObject myObject){

return myObjectsMap.put(myObject.getName(), myObject);

}

}

I w ten prosty sposób doszliśmy do problemu, który chciałem opisać, a zdarza się, gdy pobranemu na zewnątrz obiektowi MyObject zostanie zmieniona nazwa. W mapie pozostanie stary wpis, natomiast referencja będzie miała nowa nazwę. Próba pobrania elementu wg. nowej nazwy skończy się albo niesławnym NPE, albo (co gorsza) pobraniem innego elementu, który przypadkiem ma taką sama nazwę. W obydwu przypadkach nie jest za dobrze. Kod ilustrujący znajduje się ponizej:


MyObject myObject = new MyObject("name", 1);

myObjectContainer.putSimple(myObject);

MyObject myObject = myObjectContainer.get( "name" );

myObject.setName( "newName" );

myObjectContainer.get( "newName" );  // i tu jest problem

Jak widać ostatnia linijka oznaczona „tu jest problem”, spowoduje błąd, gdyż obiekt o nazwie „name” zmienił nazwę na „newName” natomiast w mapie pozostanie dostępny jedynie po kluczu „name”.

Żeby temu zapobiec skorzystamy z wzorca obserwator właśnie i w momencie zmiany nazwy obiektu, nasz MyObjectContainer zostanie powiadomiony przez samego zainteresowanego, co da mu możliwość zaktualizownia własnego stanu. Do tego jednak potrzebujemy lekko zmodyfikować nasz przykład. Po pierwsze obiekt MyObject musi mieć możliwość notyfikowania o zmianie swojego stanu, skorzystam tutaj z implementacji Obserwatora /Obserwowanego dostępnej w pakiecie java.util :


class MyObject extends Observable {

public void setName( String newName ) {

String oldName =name;

name = newName;

if( !newName.equals( oldName ) ) {

setChanged();

notifyObservers(oldName);  // przekazemy stara nazwe, zeby dalo sie mape zaktualizowac

}

}

}

Teraz wystarczy tylko dodać możliwości „nasluchiwania” do MyObjectContainer:


class MyObjectContainer implements Observer {

public MyObject putSimple(MyObject myObject){

myObject.addObserver( this );

return myObjectsMap.put(myObject.getName(), myObject);

}

// no i jeszcze aktualizacja stanu

public void update(Observable o, Object arg)  {

String oldName = (String) arg;

MyObject myObject = (MyObject) o;

if( !oldName.equals(myObject.getName())){

MyObject oldObject = myObjectsMap.put(myObject.getName(), myObject);

myObjectsMap.remove(oldName);

if( oldObject != null ) { // to znaczy, ze cos pod tą nazwą już było

}

}

}

Jak widać kontener dostaje starą nazwę dzięki której może usunąć odpowiedni wpis, a także zmodyfikowany obiekt MyObject, co pozwala mu dodać referencje pod nową nazwą. Teraz już można zmieniać nazwy w te i wewtę, coprawda pozostała jeszcze implementacja zachowanai w przypadku znalezienia takiego samego obiektu w mapie, no ale to zostawię już jako zadanie domowe dla czytelnika.

Ostatnio prace nad pewną aplikacją w końcu wyszły na prostą i można w miarę normalny sposób odnaleźć się w kodzie, ale żeby nie było zbyt prosto uciążliwe do znalezienia błedy jeszcze sie zdarzają. Ostatnio zdarzyło mi się poświęcić 3 godziny na znalezienie jednego – niby wszystko działa cacy a program nic nie wypluwa 🙁 Zaczęło się żmudne przeszukiwanie, porównywanie wersji i ostatnich zmian, aż nagle okazało się, że w pewnej strukturze 3-poziomowej na najniższym poziomie znajdują się elementy zawierające referencję do kolekcji, której zadaniem jest przechowywanie pewnych id’ków i udostępniać je dla poziomu 1-go. Wszystko ładnie i pięknie, ale podczas prac, ktoś wpadł na pomysł, że podczas czyszczenia elementu, korzystając z metody clean metody clear, wyczyści także i tę kolekcję, co niestety powodowało brak danych 2 pooziomy wyżej, a to już niestety skutki miało opłakane.

Zacząłem się jednak zastanawiać co zrobić, żeby uniknąć tego typu „niespodzianki” w przyszłośći, gdyż jestem pewien, że za miesiąc, no góra 2, nikt z zespołu włącznie ze mną nie będzie pamiętał dlaczego akurat ta „wyjątkowa” kolekcja nie jest czyszczona i znowu doda wywołanie „clean”‚a „clear”‚a, bo przecież wszystko trzeba wyczyścić.

Pierwszy pomysł jaki przyszedł mi do głowy to udostępnić każdemu elementowi na najniższym poziomie swoją kopię tejże kolekcji. Jednak szybko go odrzuciłem, bo jak sobie pomyślałem o synchronizacji wszystkich tych małych kolekcji z główną, to aż mnie głowa rozbolała.

No dobrze, a co gdyby tak zablokować dostęp do metody clean w kolekcji na najniższym poziomie, ew jakoś tak ją opakować, żeby „clean” „clear” był niedostępny – łatwo powiedzieć ale to oznaczało, ze trzeba zaimplementować cały interfejs, jedynie po to, żeby z metody clean clear wyrzucić UnsupportedOperationException a resztę wywołań przekazać do obiektu zawiniętego, ot takie sobie proxy…

Jednak tutaj Java przychodzi z pomocą, bo oddaje do dyspozycji programiście dynamiczne proxy (jeżeli ktoś się w tym momencie zastanawia nad wyjdajnościa, to przejdę do tego za chwilę). Sprawa jest banalnie prosta:

  1. Implementujemy interfejs InvocationTarget
  2. Sprawdzamy nazwę metody.
  3. Jeżeli nazwa metody to ‚clear’ wyrzucamy wyjątek.
  4. Jeżeli to inna nazwa, to przekazujemy wywołanie dalej.

Prościzna – zamiast przepisywania 10-20 metod (zależnie od interfejsu), mamy tylko jedną, która robi co od niej chcemy. Reszta kodu jest zupełnie nieświadoma, że coś wogóle zaszło po drodze, a jak trzeba sypie wyjątkiem po oczach aż miło 🙂

Zanim przejde do przykładu, to chciałbym wrócić na chwilę do problemu z wydajnością – otóż dynamic proxy jest realizowane przez refleksję i o ile w Javie do wersji 1.3 koszt takiego wywołania był spory, bo były one 30-40 razy wolniejsze niż zwykłe wywołanie statycznie skompilowanej metody, to teraz ponoć całe rozwiązanie zostało udoskonalone i działa tylko 2 razy wolniej, co może być dalej za wolno dla niektórych, ale trzeba przyznać, ze postęp jest ogromny – jak na moje potrzeby w zupełności wystarcza.

No dobrze ale przejdzmy do przykładu:

// this code contains a but - as the method is called CLEAR
class StructElement3 {

private class SetNoClear implements java.lang.reflect.InvocationHandler{

private Set<String> proxy;

public SetNoClear(Set<String> proxy) {

this.proxy = proxy;

}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
try {

if( m.getName().equals( "clean" ) ) {

throw new UnsupportedOperationException("Cannot call clear");

}
return m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " +
e.getMessage());
}

}

}

public StructElement3(Set<String> obj){

proxy = Proxy.newInstance( obj.getClass().getClassLoader(), new Class[]{Set.class}, new SetNoClear(obj));

}

}

Jak widać większość wywołań przechodzi bez problemu do Set’a pod spodem poza clean’em, który zawiedzie, a wywołujący od razu będzie wiedział, że tak nie można.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;

class StructElement3 {

	private class SetNoClear implements InvocationHandler {

		private Set<String> obj;

		public SetNoClear(Set<String> proxy) {
			this.obj = proxy;
		}

		public Object invoke(Object proxy, Method m, Object[] args)
				throws Throwable {
			try {
				if (m.getName().equals("clear")) {
					throw new UnsupportedOperationException("Cannot call clear");
				}
				return m.invoke(obj, args);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			} catch (Exception e) {
				throw new RuntimeException("unexpected invocation exception: "
						+ e.getMessage());
			}

		}
	}

	public StructElement3(Set<String> obj) {
		Set<String> proxy = (Set<String>) Proxy.newProxyInstance(
			obj.getClass().getClassLoader(),
			new Class[] { Set.class },
			new SetNoClear(obj)
		);
	}

}

Wyobraźmy sobie taką sytuację, że mamy aplikację korzystającą z JAXB i do generowania klas wykorzystujemy task ant’owy XJC, na podstawie DTD dostarczanego z zewnątrz. Niestety jako, że nie jesteśmy autorami, ani właścicielami tego DTD modyfikacje mogą się pojawić niespodziewanie z tego powodu zamiana go na scheme nie wchodzi w grę.

Jako wprawni programiści szybko zauważamy, że pewne elementy mają powtarzające się własności, jednak z uwagi na to, że to DTD wykorzystanie polimorfizmu jest żadne i powstają takie brzydactwa:

String name = null;
if( get1 ) {
Tag1 t1 = container.getTag1();
name = tag.getName();
}
else {
Tag2 t2 = container.getTag2();
name = tag.getName();
}

Oczywiście zmienna get1 jest zdeklarowana gdzieś wcześniej. Ten kod aż krzyczy o wywołania polimorficzne, ale z uwagi na to, że obiektów poza nazwą nic nie łączy, nie można skorzystać z tego cudownego wynalazku.

Z pomocą przychodzą nam wzorce projektowe. Na początek weźmiemy się za napisanie zawijacza do naszej struktury, który to pozwoli wykorzystać poliformizm – niektórzy powiedzą, że to Proxy inni, że dekorator, ja uznałem, że opiszę go bardziej ogólnie jako „zwijacz”, gdyż na początku będzie przypominał zwykłe proxy, później już chyba nieco bardziej dekoratora, no ale do dzieła.

Zacznijmy od prostego interfejsu:

public interface MyTag {
String getName();
}

Dodajmy klasę abstrakcyjną, która tenże interfejs implementuje:

public class MyAbstractTag implements MyTag {}

Po co nam klasa abstrakcyjna, o tym już za chwile 🙂

Następnie piszemy konkretne „zawijacze” dla poszczególnych obiektów:

public class MyTag1 extends MyAbstractTag {
private Tag1 tag;
public MyTag1(Tag1 tag) {
this.tag = tag;
}
public String getName(){
return tag.getName();
}
}

I Tag2 prawie identycznie:

public class MyTag2 extends MyAbstractTag {
private Tag2 tag;
public MyTag2(Tag2 tag) {
this.tag = tag;
}
public String getName(){
return tag.getName();
}
}

W c++ byłoby jescze prościej, bo możnaby było zmusić kompilator do wygenerowania dla nas obu klas z szablonu, ale niestety w Javie, szablony nie sięgają do czasu wykonania, więc pozostaje nam czas kompilacji, ale to wcale nie znaczy, że należy z tego rezygnować:)

public class MyAbstractTag<T> implements MyTag {
protected T tag;
public MyAbstractTag(T tag, Class<T> clazz){
this.tag = tag;
}
}

I implementacja dla MyTag1

public class MyTag1 extends MyAbstractTag<Tag1> {
public MyTag1(Tag1 tag) {
super(tag, Tag1.class);
}
public String getName(){
return tag.getName();
}
}

Niby nic, bo metodę i tak musimy zadeklarować w implementacji, ale zawsze można nacieszyć oko 🙂 Dodajmy małą metodę do budowania zawijaczy:

public MyTag getTag(TagType type, Wrapper wrapper){
MyTag ret = null;
switch(type){
case Tag1: ret = new MyTag1(wrapper.getTag1()); break;
case Tag2: ret = new MyTag2(wrapper.getTag2()); break;
}
return ret;
}// wrócmy do przykładu z góry:
MyTag t = getTag(TagType.Tag1, wrapper);
// już nas zupełnie nie interesuje co jest pod spodem:
System.out.println( t.getName() );

I takim to sposobem doszliśmy do „prostej faktorii”, zwanej czasem też (ponoć mylnie) wzorcem projektowym factory. Z którym ma tyle wspólnego, że produkuje obiekty :)Jednak nic nie stoi na przeszkodzie iść dalej tym tropem, załóżmy, że nasze obiekty Tag1/Tag2 zwracają dane w dość dziwnym formacie jak na xml, ale np jeden z atrybutów ma listę oddzieloną przecinkami. Powodów może być wiele, przyjmijmy ten pozytywny – po prostu tak musiało być inaczej masa plików xml, które już mamy i działają wymagałaby modyfikacji, co do tych mniej pozytywnych to już pozostawiam wyobraźni czytelnika.

Wracając do tematu wzorców, jaki by tutaj pasował – metoda szablonowa. Dlaczego? Bo jedyne co potrzebujemy z obiektów Tag1/Tag2 to wartość atrybutu, cała reszta przetwarzania odbywa się w jednym miejscu (klasa abstrakcyjna). Dodajemy publiczną metodę do interfejsu:

// dodajemy metode do interfejsu
public interface MyTag {
// wcześniejsze deklaracje
List<String> getList();
}

Teraz klasa abstrakcyjna implementuje publiczny interfejs i dodaje swoją metodę abstrakcyjną, żeby pobrać wartość atrybutu z implementacji:

public class MyAbstractTag<T> implements MyTag {
// wszystko jak poprzednio
public List<String> getList(){
String list = doGetList();
if( list == null ) {
return new ArrayList();
}
return Arrays.asList(list.split(","));
}
protected abstract String doGetList();
}

Prawda, że pięknie 🙂 Jedyne co musi dostarczyć implementacja, to metodę do pobierania swojego atrybutu:

public class MyTag1 extends MyAbstractTag<Tag1> {
// reszta bez zmian
protected String doGetList(){
return tag.getList();
}
}

I wykorzystaliśmy złotą zasadę Hollywood – „nie dzwoń do nas to my zadzwonimy do Ciebie” implementacja dostarcza nam danych a klasa abstrakcyjna może z tego skorzystać albo i nie, do tego odwracamy zależności.Możnaby się jeszcze rozwodzić długo nad zaletami takiego rozwiązania, ale trzeba go po prostu spróbować!

Tym wpisem pragnę zapoczątkować nowy dział nazwany „wzroce projektowe a akcji„, którego celem jest ukazywanie konkretnych zastosowań/rozszerzeń wzorców projektowych opisywanych na forach w książkach. Dlaczego uważam, że to ważne? Dlatego, że wzorce mają być tylko wskazówką i opisem, który ktoś kiedyś sformalizował dla powtarzalnych zadań, jednak każde z nich miało swoją specyfike i wzorzec jako taki ma być tylko DROGOWSKAZEM, a nie „lekiem na całe zło”.

W tym poście nie będę się skupiał na żadnym konkretnym wzorcu, ale bardziej opisem przykładu zauważania podobieństw/powtórzeń w kodzie. Schematów, które mogą nam wskazać nasze małe bądź większe wzorce.

Przykład będzie bazował na moim poprzednim poście, w którym to opisywałem jak elegancko zamienić typ string na enuma i dzieki temu skorzystać z dobrodziejstw silnej typizacji podczas pracy z obiektami reprezentującymi struktury XML’owe. Aby tego dokonać należy napisać własny adapter, czyli klasę odpowiedzialną za konwersję, które dziedziczy po szablonowej klasie XmlAdapter.

Najprostsze rozwiązanie, to dodanie do naszej implementacji 2 map, jedna z mapowaniem typu na string i w drugą stronę, przykładowa klasa może wyglądać tak:


// plik MyEnum.java
public enum MyEnum{aa,bb,cc};

// plik MyAdapter.java
public class MyAdapter extends XmlAdapter<String,MyEnum>{

// anonimowa Map'a
private Map<String,MyEnum> name2Type = new HashMap<String,MyEnum>(){
// anonimowy blok inicjalizacyjny
{
put( "aa", MyEnum.aa );
// itd
}
}

// teraz w drugą stronę 
private Map<MyEnum,String> name2Type = new HashMap<MyEnum,String>(){
// anonimowy blok inicjalizacyjny
{
put( MyEnum.aa, "aa" );
// itd
}
}

// no i metody marshall/unmarshall, zapewniające dostęp

@Override
String unmarshall(MyEnum type) throws Exception{ return name2Type.get(type);}

@Override
MyEnum marshall(String typeName) throws Exception{ return type2Name.get(typeName);}

}

Wygląda nieźle, ale co jeżeli mamy więcej typów wyliczeniowych i nazw które chcemy obsługiwać. Pewnie czytelnik pomyśli, że możnaby dodać prywatną własność do enum’a, np. name typu String co definitywnie rozwiązałoby sprawę. Problem polega na tym, że takie rozwiązanie niekoniecznie musi prowadzić do faktycznej poprawy jakości kodu, ponieważ do takiej własności trzeba zadeklarować metodę dostępową, co znowu oznacza, że jak już dawno zapomnimy do czego ta własność jest, przyjdzie nam do głowy, żeby z niej skorzystać. Co gorsza, ktoś z naszych współpracowników, który nie jest świadom znaczenia atrybutu name, wykorzysta go w swoim kodzie i znowu silną typizację szlag trafi 🙂

Z tego względu zalecaną przeze mnie ścieżka jest dodanie po 2 mapy do każdego adaptera, pozniej kod drugiego adaptera:


// plik MyEnum2.java
public enum MyEnum2{dd,ee,ff};

// plik MyAdapter.java
public class MyAdapter2 extends XmlAdapter<String,MyEnum2>{

// anonimowa HashMap'a
private Map<String,MyEnum2> name2Type = new HashMap<String,MyEnum2>(){
// anonimowy blok inicjalizacyjny
{
put( "dd", MyEnum2.dd );
// itd
}
}

// mapowanie w druga strone
private Map<MyEnum2,String> name2Type = new HashMap<MyEnum2,String>(){
// anonimowy blok inicjalizacyjny
{
put( MyEnum2.dd, "dd" );
// itd
}
}

@Override
String unmarshall(MyEnum2 type) throws Exception{ return name2Type.get(type);}

@Override
MyEnum2 marshall(String typeName) throws Exception{ return type2Name.get(typeName);}

}

Sprawa wydaje się czysta, działać działa, ale wygląda jakby kod sie zdziebko powtarzał:

  • każda z klas zawiera 2 mapy, z typami identycznymi do tych przekazanych jako parametry szablonu
  • każda z nich nadpisuje metody marshall/unmarshall, tylko po to aby wybrać odpowiedni element z mapy

Powyższy przykład jest jeszcze dość prosty, a co jeżeli będziemy  chcieli poinformować użytkownika że dana nazwa/typ nie istnieje w tablicy mapowań? Trzebaby rzucić jakiś wyjątek, np IllegalArgumentException, ale jak narazie sprawdzanie trzeba dodać w każdej metodzie, a może istnieje inny sposób?

Tak, a oto lista zmian, które trzeba wprowadzić:

  • stworzyć klasę abstrakcyja AdapterBase, dziedziczącą po szablonie
  • klasa AdapterBase także deklaruje 2 parametry
  • AdapterBase ma 2 własności w postaci Map – name2Type i type2Name
  • name2Type i type2Name nie mają konkretnych typów, tylko parametry szablonu
  • dostęp name2Type/type2Name  jest typu protected, tzn że klasy dziedziczące mogą ustalać ich wartość
  • klasa AdapterBase zawiera implementację metoda marshall/unmarshall, które także są metodami szablonowymi (w przypadku nieznalezienia elementu w odpowiedniej tablicy, obie rzucają wyjątek)
  • konkretne adaptery dziedziczą po AdapterBase
  • deklarują typy z których konwertują
  • deklaruja mapowania typów i już

A teraz jak to może wyglądać, zaczniemy od końca czyli od konretnego adaptera:


// MyEnum zostaje bez zmian
// plik MyAdapter.java
public class MyAdapter extends AbstractAdapter<String,MyEnum>{
// konstruktor dostarcza mapowań
public MyAdapter() {

name2Type.put( "aa", MyEnum.aa );
// itd

type2Name.put( MyEnum.aa, "aa" );
}
// i to wszystko!
}

Klasa drugiego adaptera:

// MyEnum2 zostaje bez zmian
// plik MyAdapter2.java
public class MyAdapter2 extends AbstractAdapter<String,MyEnum2>{
// konstruktor dostarcza mapowań
public MyAdapter2() {
name2Type.put( "dd", MyEnum2.dd );
// itd
type2Name.put( MyEnum2.dd, "dd" );
}
// i to wszystko!
}

A teraz abstrakcyjny adapter


public class AbstractAdapter<T,E> extends XmlAdapter<T,E>{
protected Map<T,E> name2Type = new HashMap<T,E>();
protected Map<E,T> type2Name = new HashMap<E,T>();

@Override
T unmarshall(E type) throws Exception{ return name2Type.get(type);}

@Override
E marshall(T typeName) throws Exception{ return type2Name.get(typeName);}

}

Jak widać AbstractAdapter nie konkretyzuje szablonu, jedynie deklaruje, że wszystkie mapowania będą przekazywane przez Map’y o odwróconych typach.

No dobra, zobaczmy jak teraz dodać obsługę wyjątków:


public class AbstractAdapter<T,E> extends XmlAdapter<T,E>{
protected Map<T,E> name2Type;
protected Map<E,T> type2Name;

@Override
T unmarshall(E type) throws Exception{ 
T ret = name2Type.get(type);
if( ret == null ) {
throw new IllegalArgumentException("Cannot find name for type: " + type);
}
return ret;
}

@Override
E marshall(T typeName) throws Exception{ 
E ret = type2Name.get(typeName);
if( ret == null ) {
throw new IllegalArgumentException("Cannot find type for name: " + typeName);
}
return ret;
}

}

Jak widać jest to bajecznie proste, zmiana w jednym miejscu, konkretne adaptery nawet nie są świadome tego, że coś się zmieniło, a do tego wszystkie zareagują tak samo na brakujące mapowanie.