6 grudnia 2009

Implementacja wzorca Dekorator w Javie

Ostatnimi czasy wzorzec Dekorator przydał nam się w sytuacji, w której nie chcieliśmy modyfikować widoku jednak chcieliśmy aby dane w pewnych przypadkach były inaczej prezentowane. Zaimplementowaliśmy ten wzorzec w najprostszy możliwy sposób:
public class Coffee {
  private final String name;

  public Coffee(String name) {
    this.name = name;
  }

  public int price() {
    return 4;
  }

  public String name() {
    return name;
  }
}

public class Milk extends Coffee {
  private final Coffee coffee;

  public Milk(Coffee coffee) {
    super("");
    this.coffee = coffee;
  }

  @Override
  public int price() {
    return coffee.price() + 2;
  }

  @Override
  public String name() {
    return coffee.name();
  }
}
public class BasicTest {
  private Coffee decorated;

  @Test
  public void testPriceDecoration() {
    assertEquals(6, decorated.price());
  }

  @Test
  public void testNameDecoration() {
    assertEquals("coffee", decorated.name());
  }

  @Test
  public void testMultiLevelDecoration() {
    assertEquals(8, new Milk(decorated).price());
  }

  @Before
  public void setUp() throws Exception {
    decorated = new Milk(new Coffee("coffee"));
  }
}
Powyższy test potwierdza, że implementacja jest prosta i skuteczna jednak posiada kilka wad:
  • klasa dekorowana musi nadpisywać wszystkie metody klasy dekorowanej,
  • dodanie metody do klasy dekorowanej nie wymusza na nas implementacji tej metody w klasie dekorującej.
Chcąc znaleźć rozwiązanie tych problemów postanowiłem użyć klasy Proxy.
public interface Coffee {

  int price();

  String name();
}

public class CoffeeImpl implements Coffee {
  private final String name;

  public CoffeeImpl(String name) {
    this.name = name;
  }

  public int price() {
    return 4;
  }

  public String name() {
    return name;
  }
}

public class Milk extends CoffeeImpl {

  public static Coffee add(final Coffee coffee) {
    return Decorators.decorate(coffee, new Milk(coffee));
  }

  private final Coffee coffee;

  private Milk(Coffee coffee) {
    super("");
    this.coffee = coffee;
  }

  public int price() {
    return coffee.price() + 2;
  }
}

public class Decorators {

  public static Coffee decorate(final Coffee coffee, final Coffee decorator) {
    InvocationHandler handler = new InvocationHandler() {
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class decoratorClass = decorator.getClass();
        Method m = decoratorClass.getMethod(method.getName(), method.getParameterTypes());
        Coffee target = m.getDeclaringClass() == decoratorClass ? decorator : coffee;
        return method.invoke(target, args);
      }
    };
    return (Coffee) Proxy.newProxyInstance(Coffee.class.getClassLoader(), new Class[] { Coffee.class }, handler);
  }
}
public class ProxyTest {
  private Coffee decorated;

  @Test
  public void testPriceDecoration() {
    assertEquals(6, decorated.price());
  }

  @Test
  public void testNameDecoration() {
    assertEquals("coffee", decorated.name());
  }

  @Test
  public void testMultiLevelDecoration() {
    assertEquals(8, Milk.add(decorated).price());
  }

  @Before
  public void setUp() throws Exception {
    decorated = Milk.add(new CoffeeImpl("coffee"));
  }
}
Implementacja z użyciem klasy Proxy także działa bez zarzutu oraz rozwiązuje problemy wersji podstawowej jednak nie podoba mi się konieczność zastosowania interfejsu wymuszana przez klasę Proxy. Postanowiłem bardziej zagłębić się w ten temat i natrafiłem na bibliotekę cglib, która pomogła w rozwiązaniu ostatniego problemu.
public class Coffee {
  private final String name;

  public Coffee(String name) {
    this.name = name;
  }

  public int price() {
    return 4;
  }

  public String name() {
    return name;
  }
}

public class Milk extends Coffee {

  public static Coffee add(final Coffee coffee) {
    return Enhancers.enhance(coffee, new Milk(coffee));
  }

  private final Coffee coffee;

  public Milk(Coffee coffee) {
    super("");
    this.coffee = coffee;
  }

  @Override
  public int price() {
    return coffee.price() + 2;
  }
}

public class Enhancers {

  public static Coffee enhance(final Coffee coffee, final Coffee enhancer) {
    MethodInterceptor interceptor = new MethodInterceptor() {
      public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Class enhancerClass = enhancer.getClass();
        Method m = enhancerClass.getMethod(method.getName(), method.getParameterTypes());
        Object target = m.getDeclaringClass() == enhancerClass ? enhancer : coffee;
        return method.invoke(target, objects);
      }
    };

    Enhancer e = new Enhancer();
    e.setSuperclass(Coffee.class);
    e.setCallback(interceptor);

    return (Coffee) e.create(new Class[] { String.class }, new Object[] { "" });
  }
}
Na koniec test, który potwierdza poprawność implementacji.
public class CglibTest {
  private Coffee decorated;

  @Test
  public void testPriceDecoration() {
    assertEquals(6, decorated.price());
  }

  @Test
  public void testNameDecoration() {
    assertEquals("coffee", decorated.name());
  }

  @Test
  public void testMultiLevelDecoration() {
    assertEquals(8, Milk.add(decorated).price());
  }

  @Before
  public void setUp() throws Exception {
    decorated = Milk.add(new Coffee("coffee"));
  }
}
Projekt znajduje się na stronie github.com.

Maven: Ustawienie wersji JDK dla kompilatora

13 października 2009

Jak zabić proces po opisie w systemie *nix

Często mam problem z zatrzymaniem serwera Tomcat po uruchomieniu go z poziomu IDE. Poniższe polecenie ułatwia znacznie to zadanie.
kill `ps aux|grep tomcat|grep -v grep|awk '{ print $2 }'`

11 maja 2009

Tworzenie obiektów klas, które zależą od siebie nawzajem

W systemie istnieją dwie klasy (FooImpl i BarImpl), które zależą od siebie poprzez pole konstruktora.
interface Foo {
  void execute(boolean forward);
}

class FooImpl implements Foo {
  private final Bar bar;

  public FooImpl(Bar bar) {
    this.bar = bar;
  }

  public void execute(boolean forward) {
    System.out.print("Foo");
    if (forward) {
      System.out.print(" -> ");
      bar.execute(!forward);
    }
  }
}

interface Bar {
  void execute(boolean forward);
}

class BarImpl implements Bar {
  private final Foo foo;

  public BarImpl(Foo foo) {
    this.foo = foo;
  }

  public void execute(boolean forward) {
    System.out.print("Bar");
    if (forward) {
      System.out.print(" -> ");
      foo.execute(!forward);
    }
  }
}
Taki stan nie pozwala stworzyć obiektów tychże klas bez konieczności wprowadzenia dodatkowego obiektu pośredniczącego wywołania do poprawnie stworzonego obiektu. W javie funkcjonalność tą realizuje klasa java.lang.reflect.Proxy. Aby stworzyć takie proxy potrzebna będzie jeszcze implementacja interfejsu java.lang.reflect.InvocationHandler, do której przekazane będą wszystkie wywołania metod obiektu proxy.

Poniższa implementacja przekazuje wywołania metod obiektu proxy bezpośrednio do obiektu delegata.

class DelegateInvocationHandler implements InvocationHandler {
  private Object delegate;

  public void setDelegate(Object delegate) {
    this.delegate = delegate;
  }

  public Object invoke(Object target, Method method, Object[] args)
      throws Throwable {
    return method.invoke(delegate, args);
  }
}
Aby zbudować obiektu obu klas najpierw przygotujemy proxy FooImpl.
DelegateInvocationHandler handler = new DelegateInvocationHandler();

Foo fooProxy = (Foo) Proxy.newProxyInstance(
  Foo.class.getClassLoader(),
  new Class[]{Foo.class},
  handler);
Korzystając z obiektu fooProxy możemy stworzyć obiekty klas FooImpl i BarImpl za pomocą operatora new.
Bar bar = new BarImpl(fooProxy);
Foo foo = new FooImpl(bar);
Ostatnim etapem wymaganym do poprawnego działania proxy potrzebne jest ustawienia obiektu delegata.
handler.setDelegate(foo);
Po wykonaniu tych wszystkich operacji wywołania metod execute zostaną przekazane do odpowiednich obiektów.
foo.execute(true); // na wyjsciu Foo -> Bar
bar.execute(true); // na wyjsciu Bar -> Foo
Pełny kod źródłowy: UsingProxy.java.

7 maja 2009

Implementacja mechanizmu wtyczek za pomocą ServiceLoader

Java w wersji 6 dostarcza klasę java.util.ServiceLoader, z użyciem której można zaimplementować mechanizm wtyczek. Za przykład posłuży nam aplikacja generująca raporty wspierająca różne formaty danych wyjściowych.

Każdy generator raportów musi implementować odpowiedni interfejs:

package com.mbialon.rg;

public interface ReportGenerator {
  String format();
  void generate();
}
W podstawowej wersji aplikacji możemy dostarczyć tylko raport generujący wyjście w formacie HTML.
package com.mbialon.rg;

public class HtmlReportGenerator implements ReportGenerator {
  public String format() {
    return "html";
  }

  public void generate() {
    // Html report generation
  }
}
Aby skorzystać z możliwości dostarczanych przez java.util.ServiceLoader musimy w katalogu META-INF/services archiwum jar umieścić plik o nazwie odpowiadającej ładowanej usłudze (w moim przypadku to pełna ścieżka do interfejsu generatora raportów), który zawierał będzie ścieżki do klas implementujących ładowane usługi (w moim przypadku będzie to ścieżka do generatora HTML). Treść pliku META-INF/services/com.mbialon.rg.ReportGenerator to tylko jedna linia:
com.mbialon.rg.HtmlReportGenerator
Z przygotowanej zgodnie z powyższym opisem implementacji możemy skorzystać w aplikacji klienckiej.
package com.mbialon.rg;

import java.util.*;

public class Client {
  public static void main(String[] args) {
    ServiceLoader<ReportGenerator> providers = ServiceLoader.load(ReportGenerator.class);

    List<String> availableFormats = new ArrayList<String>();
    for (ReportGenerator generator : providers) {
      availableFormats.add(generator.format());
    }

    // user can select format from available options
    String selectedFormat;

    ReportGenerator selectedGenerator;
    for (ReportGenerator generator : providers) {
      if (selectedFormat.equals(generator.format()))
        selectedGenerator = generator;
    }
  }
}
Po załadowaniu dostępnych generatorów raportów w aplikacji klienckiej możemy odczytać listę dostępnych formatów oraz możemy wybrać odpowiedni generator zgodnie z wybranym przez użytkownika formatem raportu. Stworzoną w ten sposób aplikację kliencką możemy rozszerzać o nowe formaty raportów dostarczając kolejne biblioteki bez konieczności rekompilacji aplikacji klienckiej. Każda z tych bibliotek musi zawierać implementacje generatorów oraz plik META-INF/services/com.mbialon.rg.ReportGenerator zawierający ścieżki do tych klas.

29 marca 2009

Kontekst w tagach JSP 2.0

W weekend trafiłem na całkiem dobry opis użycia tagów z JSP 2.0. Tagi w tej wersji pozwalają w łatwy i przyjemny sposób upraszczać kod źródłowy widoku.

Próbowałem uruchomić jeden z pierwszych przykładów, operujący na danych zawartych wewnątrz taga: Niestety próba kompilacji strony używającej taga się nie powiodła. Szukałem pomocy w internecie jednak nie znalazlem żadnej interesującej wskazówki. Zajrzałem więc do implementacji taga wygenerowanej przez serwer aplikacji i coś przykuło moją uwagę.

public void doTag() throws JspException, IOException {
  PageContext _jspx_page_context = (PageContext) jspContext;
  // ...
  _jspx_page_context.setAttribute("theBody", _jspx_sout.toString(), PageContext.PAGE_SCOPE);
  // ...
  String theBody = (String) pageContext.getAttribute("theBody");
  // ...
}
Zgodnie z komunikatem błędu nie ma tam dostępu do zmiennej pageContext jednak istnieje jspContext, która jest rzutowana do typu PageContext. Podmiana zmiennej w kodzie źródłowym taga spowodowała, że tag się skompilował i wszystko zadziałało. Ostatecznie znalazłem potwierdzenie mojego domysłu w tym artykule jednak nadal nie wiem skąd ta róźnica w nazewnictwie wynika. W związku z tym, poprawna implementacja taga powinna wyglądać następująco:

10 marca 2009

18 lutego 2009

"Craftsmanship and Ethics" by Robert C. Martin

Na stronach InfoQ pojawiła się jedna z wielu ciekawych i pouczających prezentacji prowadzonych przez Pana Roberta C. Martina. Można w nich znaleźć wiele wartościowych myśli, reguł czy koncepcji dotyczących profesji programisty.

Jednej z nich należy się specjalne wyróżnienie:

“Professionalism is what constraints us to do well, not managers.”

15 lutego 2009

Pierwsze Coding Dojo za nami

Tydzień temu w piątek poprowadziłem pierwsze w moim zespole (ale także w mojej karierze) Coding Dojo, którego tematem było Bowling Kata. Spotkaliśmy się w 4 osoby na ostatnie 2,5h piątkowego popołudnia.

Stanowisko, zorganizowane w sali konferencyjnej, składało się z:

  • laptopa,
  • projektora,
  • zewnętrznej klawiatury i myszki.

Spotkanie miało na celu zapoznanie kolegów z samą ideą Coding Dojo oraz praktyczne wykorzystanie TDD. W związku z faktem, że jesteśmy bardzo początkujący w tym temacie, nie udało nam się osiągnąć głównego zamysłu tego rodzaju spotkań - wykorzystania zasad projektowania obiektowego do rozwiązania postawionego problemu. Mam jednak nadzieję, że w miarę kolejnych spotkań uda nam się ten element układanki poprawić.

Pierwsze wrażenia były bardzo pozytywne w związku z czym spotkania zostaną także zorganizowane dla pozostałych kolegów z mojej jednostki organizacyjnej (2 grupy po 4 osoby). Przeprowadzę z nimi spotkania na ten sam temat, co powinno dać mi nowe doświadczenia na przyszłość.

Wnioski
  • musimy zwracać większą uwagę na sposób rozwiązania problemu, najważniejsza jest jakość rozwiązania a nie jego kompleksowość,
  • po pewnym czasie spotkanie stało się troszkę chaotyczne, każdy kto miał jakiś pomysł tworzył test zaś inna osoba starała się go spełnić; podczas kolejnych spotkań muszę położyć większy nacisk na kolejność oraz długość okienek czasowych,
  • piątkowe popołudnie to nie jest najlepsza pora, zmęczenie po całym tygodniu pracy negatywnie wpływa na wydajność,
  • 2,5h w zupełności nam wystarczyło, będę się jednak zastanawiał nad zmianą długości spotkania (do 2h lub 1,5h) w zależności od tematu spotkania,
  • klawiatura laptopa może być niewygodna, dlatego następnym razem muszę przygotować dwa stanowiska z normalną klawiaturą i myszką.

7 stycznia 2009

Sortowanie kluczy tekstowych z uwzględnieniem wersji językowej

“The Collator class performs locale-sensitive String comparison. You use this class to build searching and sorting routines for natural language text.”

http://java.sun.com/j2se/1.5.0/docs/api/java/text/Collator.html