Posts tagged ‘faktoria’

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ć!