Maven: Ustawienie wersji JDK dla kompilatora
<project> <build> <plugins> <plugin> …
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:
class Coffee {
private final String name;
public Coffee(String name) {
this.name = name;
}
public int price() {
return 4;
}
public String name() {
return name;
}
}
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();
}
}
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:
Chcąc znaleźć rozwiązanie tych problemów postanowiłem użyć klasy Proxy.
interface Coffee {
int price();
String name();
}
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;
}
}
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;
}
}
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);
}
}
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.
class Coffee {
private final String name;
public Coffee(String name) {
this.name = name;
}
public int price() {
return 4;
}
public String name() {
return name;
}
}
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[] { "" });
}
}
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"));
}
}