Decorator pattern: Improve the example

This commit is contained in:
Ilkka Seppälä
2016-11-22 23:08:39 +02:00
parent 68ec24c62e
commit 92f8501f7d
10 changed files with 225 additions and 184 deletions

View File

@ -24,9 +24,9 @@ functionality.
## Applicability
Use Decorator
* to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects
* for responsibilities that can be withdrawn
* when extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing
* To add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects
* For responsibilities that can be withdrawn
* When extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing
## Real world examples
* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,66 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.9" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<interface id="1" language="java" name="com.iluwatar.decorator.Hostile" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/Hostile.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="554" y="188"/>
<class-diagram version="1.1.11" icons="true" automaticImage="PNG" always-add-relationships="false"
generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true"
router="FAN">
<class id="1" language="java" name="com.iluwatar.decorator.App" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="113" width="114" x="177" y="237"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.decorator.SimpleTroll" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java" binary="false" corner="BOTTOM_RIGHT">
<position height="149" width="125" x="496" y="237"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.decorator.ClubbedTroll" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java" binary="false" corner="BOTTOM_RIGHT">
<position height="149" width="125" x="249" y="426"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.decorator.TrollDecorator" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/TrollDecorator.java" binary="false" corner="BOTTOM_RIGHT">
<position height="149" width="125" x="331" y="237"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="5" language="java" name="com.iluwatar.decorator.Troll" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/Troll.java" binary="false" corner="BOTTOM_RIGHT">
<position height="113" width="125" x="414" y="426"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="2" language="java" name="com.iluwatar.decorator.App" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="117" y="202"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.decorator.Troll" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/Troll.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="315" y="100"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.decorator.SmartHostile" project="decorator"
file="/decorator/src/main/java/com/iluwatar/decorator/SmartHostile.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="318" y="315"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<dependency id="5">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="4"/>
</dependency>
<realization id="6">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="1"/>
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="5"/>
</realization>
<association id="7">
<generalization id="7">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="4"/>
</generalization>
<realization id="8">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="5"/>
</realization>
<association id="9">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="8" name="decorated"/>
<multiplicity id="9" minimum="0" maximum="1"/>
<attribute id="10" name="decorated"/>
<multiplicity id="11" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<end type="TARGET" refId="5" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="10">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="1"/>
</realization>
<dependency id="11">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="3"/>
</dependency>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>

View File

@ -32,8 +32,8 @@ import org.slf4j.LoggerFactory;
* target. Using the Decorator pattern it is possible to change the behavior of the class during
* runtime.
* <p>
* In this example we show how the simple {@link Troll} first attacks and then flees the battle.
* Then we decorate the {@link Troll} with a {@link SmartHostile} and perform the attack again. You
* In this example we show how the simple {@link SimpleTroll} first attacks and then flees the battle.
* Then we decorate the {@link SimpleTroll} with a {@link ClubbedTroll} and perform the attack again. You
* can see how the behavior changes after the decoration.
*
*/
@ -50,16 +50,16 @@ public class App {
// simple troll
LOGGER.info("A simple looking troll approaches.");
Hostile troll = new Troll();
Troll troll = new SimpleTroll();
troll.attack();
troll.fleeBattle();
LOGGER.info("Simple troll power {}.\n", troll.getAttackPower());
// change the behavior of the simple troll by adding a decorator
LOGGER.info("A smart looking troll surprises you.");
Hostile smart = new SmartHostile(troll);
smart.attack();
smart.fleeBattle();
LOGGER.info("Smart troll power {}.\n", smart.getAttackPower());
LOGGER.info("A troll with huge club surprises you.");
Troll clubbed = new ClubbedTroll(troll);
clubbed.attack();
clubbed.fleeBattle();
LOGGER.info("Clubbed troll power {}.\n", clubbed.getAttackPower());
}
}

View File

@ -0,0 +1,49 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.decorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Decorator that adds a club for the troll
*/
public class ClubbedTroll extends TrollDecorator {
private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class);
public ClubbedTroll(Troll decorated) {
super(decorated);
}
@Override
public void attack() {
super.attack();
LOGGER.info("The troll swings at you with a club!");
}
@Override
public int getAttackPower() {
return super.getAttackPower() + 10;
}
}

View File

@ -1,38 +1,51 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.decorator;
/**
*
* Interface for the hostile enemies.
*
*/
public interface Hostile {
void attack();
int getAttackPower();
void fleeBattle();
}
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.decorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* SimpleTroll implements {@link Troll} interface directly.
*
*/
public class SimpleTroll implements Troll {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTroll.class);
@Override
public void attack() {
LOGGER.info("The troll tries to grab you!");
}
@Override
public int getAttackPower() {
return 10;
}
@Override
public void fleeBattle() {
LOGGER.info("The troll shrieks in horror and runs away!");
}
}

View File

@ -1,51 +1,38 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.decorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Troll implements {@link Hostile} interface directly.
*
*/
public class Troll implements Hostile {
private static final Logger LOGGER = LoggerFactory.getLogger(Troll.class);
@Override
public void attack() {
LOGGER.info("The troll swings at you with a club!");
}
@Override
public int getAttackPower() {
return 10;
}
@Override
public void fleeBattle() {
LOGGER.info("The troll shrieks in horror and runs away!");
}
}
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.decorator;
/**
*
* Interface for trolls
*
*/
public interface Troll {
void attack();
int getAttackPower();
void fleeBattle();
}

View File

@ -22,40 +22,32 @@
*/
package com.iluwatar.decorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SmartHostile is a decorator for {@link Hostile} objects. The calls to the {@link Hostile} interface
* are intercepted and decorated. Finally the calls are delegated to the decorated {@link Hostile}
* TrollDecorator is a decorator for {@link Troll} objects. The calls to the {@link Troll} interface
* are intercepted and decorated. Finally the calls are delegated to the decorated {@link Troll}
* object.
*
*/
public class SmartHostile implements Hostile {
public class TrollDecorator implements Troll {
private static final Logger LOGGER = LoggerFactory.getLogger(SmartHostile.class);
private Troll decorated;
private Hostile decorated;
public SmartHostile(Hostile decorated) {
public TrollDecorator(Troll decorated) {
this.decorated = decorated;
}
@Override
public void attack() {
LOGGER.info("It throws a rock at you!");
decorated.attack();
}
@Override
public int getAttackPower() {
// decorated hostile's power + 20 because it is smart
return decorated.getAttackPower() + 20;
return decorated.getAttackPower();
}
@Override
public void fleeBattle() {
LOGGER.info("It calls for help!");
decorated.fleeBattle();
}
}

View File

@ -29,30 +29,26 @@ import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;
/**
* Date: 12/7/15 - 7:47 PM
*
* @author Jeroen Meulemeester
* Tests for {@link ClubbedTroll}
*/
public class SmartHostileTest {
public class ClubbedTrollTest {
@Test
public void testSmartHostile() throws Exception {
// Create a normal troll first, but make sure we can spy on it later on.
final Hostile simpleTroll = spy(new Troll());
final Troll simpleTroll = spy(new SimpleTroll());
// Now we want to decorate the troll to make it smarter ...
final Hostile smartTroll = new SmartHostile(simpleTroll);
assertEquals(30, smartTroll.getAttackPower());
// Now we want to decorate the troll to make it stronger ...
final Troll clubbed = new ClubbedTroll(simpleTroll);
assertEquals(20, clubbed.getAttackPower());
verify(simpleTroll, times(1)).getAttackPower();
// Check if the smart troll actions are delegated to the decorated troll
smartTroll.attack();
// Check if the clubbed troll actions are delegated to the decorated troll
clubbed.attack();
verify(simpleTroll, times(1)).attack();
smartTroll.fleeBattle();
clubbed.fleeBattle();
verify(simpleTroll, times(1)).fleeBattle();
verifyNoMoreInteractions(simpleTroll);
}
}

View File

@ -36,17 +36,15 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* Date: 12/7/15 - 7:26 PM
*
* @author Jeroen Meulemeester
* Tests for {@link SimpleTroll}
*/
public class TrollTest {
public class SimpleTrollTest {
private InMemoryAppender appender;
@Before
public void setUp() {
appender = new InMemoryAppender(Troll.class);
appender = new InMemoryAppender(SimpleTroll.class);
}
@After
@ -56,11 +54,11 @@ public class TrollTest {
@Test
public void testTrollActions() throws Exception {
final Troll troll = new Troll();
final SimpleTroll troll = new SimpleTroll();
assertEquals(10, troll.getAttackPower());
troll.attack();
assertEquals("The troll swings at you with a club!", appender.getLastMessage());
assertEquals("The troll tries to grab you!", appender.getLastMessage());
troll.fleeBattle();
assertEquals("The troll shrieks in horror and runs away!", appender.getLastMessage());