Merge pull request #573 from SrdjanPaunovic/extension-objects

#541 Extension objects pattern
This commit is contained in:
Ilkka Seppälä 2017-06-11 19:26:47 +03:00 committed by GitHub
commit 5b47d48fc2
25 changed files with 676 additions and 0 deletions

View File

@ -0,0 +1,28 @@
---
layout: pattern
title: Extension objects
folder: extension-objects
permalink: /patterns/extension-objects/
categories: Behavioral
tags:
- Java
- Difficulty-Intermediate
---
## Intent
Anticipate that an objects interface needs to be extended in the future. Additional
interfaces are defined by extension objects.
![Extension_objects](./etc/extension_obj.png "Extension objects")
## Applicability
Use the Extension Objects pattern when:
* you need to support the addition of new or unforeseen interfaces to existing classes and you don't want to impact clients that don't need this new interface. Extension Objects lets you keep related operations together by defining them in a separate class
* a class representing a key abstraction plays different roles for different clients. The number of roles the class can play should be open-ended. There is a need to preserve the key abstraction itself. For example, a customer object is still a customer object even if different subsystems view it differently.
* a class should be extensible with new behavior without subclassing from it.
## Real world examples
* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc)
* [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding)

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.0" 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="concreteextensions.Soldier" project="extension-objects"
file="/extension-objects/src/main/java/concreteextensions/Soldier.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="483" y="339"/>
<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="units.Unit" project="extension-objects"
file="/extension-objects/src/main/java/units/Unit.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="192" y="115"/>
<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="3" language="java" name="abstractextensions.SoldierExtension" project="extension-objects"
file="/extension-objects/src/main/java/abstractextensions/SoldierExtension.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="510" y="229"/>
<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>
<interface id="4" language="java" name="abstractextensions.UnitExtension" project="extension-objects"
file="/extension-objects/src/main/java/abstractextensions/UnitExtension.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="510" y="116"/>
<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="5" language="java" name="units.SoldierUnit" project="extension-objects"
file="/extension-objects/src/main/java/units/SoldierUnit.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="157" y="355"/>
<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="6" language="java" name="concreteextensions.Sergeant" project="extension-objects"
file="/extension-objects/src/main/java/concreteextensions/Sergeant.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="650" y="375"/>
<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="7" language="java" name="abstractextensions.SergeantExtension" project="extension-objects"
file="/extension-objects/src/main/java/abstractextensions/SergeantExtension.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="672" y="230"/>
<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="8" language="java" name="units.SergeantUnit" project="extension-objects"
file="/extension-objects/src/main/java/units/SergeantUnit.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="315" y="460"/>
<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="9" language="java" name="units.CommanderUnit" project="extension-objects"
file="/extension-objects/src/main/java/units/CommanderUnit.java" binary="false" corner="BOTTOM_RIGHT">
<position height="99" width="210" x="429" y="476"/>
<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="10" language="java" name="concreteextensions.Commander" project="extension-objects"
file="/extension-objects/src/main/java/concreteextensions/Commander.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="823" y="477"/>
<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="11" language="java" name="abstractextensions.CommanderExtension" project="extension-objects"
file="/extension-objects/src/main/java/abstractextensions/CommanderExtension.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="827" y="217"/>
<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>
<realization id="12">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="3"/>
</realization>
<realization id="13">
<end type="SOURCE" refId="10"/>
<end type="TARGET" refId="11"/>
</realization>
<generalization id="14">
<end type="SOURCE" refId="9"/>
<end type="TARGET" refId="2"/>
</generalization>
<association id="15">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="16" name="unit"/>
<multiplicity id="17" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="5" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="18">
<end type="SOURCE" refId="10" navigable="false">
<attribute id="19" name="unit"/>
<multiplicity id="20" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="9" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="21">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="22" name="unitExtension"/>
<multiplicity id="23" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="24">
<end type="SOURCE" refId="6"/>
<end type="TARGET" refId="7"/>
</realization>
<generalization id="25">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="4"/>
</generalization>
<generalization id="26">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="2"/>
</generalization>
<generalization id="27">
<end type="SOURCE" refId="7"/>
<end type="TARGET" refId="4"/>
</generalization>
<generalization id="28">
<end type="SOURCE" refId="8"/>
<end type="TARGET" refId="2"/>
</generalization>
<association id="29">
<end type="SOURCE" refId="6" navigable="false">
<attribute id="30" name="unit"/>
<multiplicity id="31" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="8" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="32">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="4"/>
</generalization>
<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"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

21
extension-objects/pom.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.16.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>extension-objects</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,62 @@
import abstractextensions.CommanderExtension;
import abstractextensions.SergeantExtension;
import abstractextensions.SoldierExtension;
import units.CommanderUnit;
import units.SergeantUnit;
import units.SoldierUnit;
import units.Unit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Anticipate that an objects interface needs to be extended in the future.
* Additional interfaces are defined by extension objects.
*/
public class App {
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {
//Create 3 different units
Unit soldierUnit = new SoldierUnit("SoldierUnit1");
Unit sergeantUnit = new SergeantUnit("SergeantUnit1");
Unit commanderUnit = new CommanderUnit("CommanderUnit1");
//check for each unit to have an extension
checkExtensionsForUnit(soldierUnit);
checkExtensionsForUnit(sergeantUnit);
checkExtensionsForUnit(commanderUnit);
}
private static void checkExtensionsForUnit(Unit unit) {
final Logger logger = LoggerFactory.getLogger(App.class);
SoldierExtension soldierExtension = (SoldierExtension) unit.getUnitExtension("SoldierExtension");
SergeantExtension sergeantExtension = (SergeantExtension) unit.getUnitExtension("SergeantExtension");
CommanderExtension commanderExtension = (CommanderExtension) unit.getUnitExtension("CommanderExtension");
//if unit have extension call the method
if (soldierExtension != null) {
soldierExtension.soldierReady();
} else {
logger.info(unit.getName() + " without SoldierExtension");
}
if (sergeantExtension != null) {
sergeantExtension.sergeantReady();
} else {
logger.info(unit.getName() + " without SergeantExtension");
}
if (commanderExtension != null) {
commanderExtension.commanderReady();
} else {
logger.info(unit.getName() + " without CommanderExtension");
}
}
}

View File

@ -0,0 +1,9 @@
package abstractextensions;
/**
* Interface with their method
*/
public interface CommanderExtension extends UnitExtension {
void commanderReady();
}

View File

@ -0,0 +1,9 @@
package abstractextensions;
/**
* Interface with their method
*/
public interface SergeantExtension extends UnitExtension {
void sergeantReady();
}

View File

@ -0,0 +1,8 @@
package abstractextensions;
/**
* Interface with their method
*/
public interface SoldierExtension extends UnitExtension {
void soldierReady();
}

View File

@ -0,0 +1,7 @@
package abstractextensions;
/**
* Other Extensions will extend this interface
*/
public interface UnitExtension {
}

View File

@ -0,0 +1,25 @@
package concreteextensions;
import abstractextensions.CommanderExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import units.CommanderUnit;
/**
* Class defining Commander
*/
public class Commander implements CommanderExtension {
private CommanderUnit unit;
public Commander(CommanderUnit commanderUnit) {
this.unit = commanderUnit;
}
final Logger logger = LoggerFactory.getLogger(Commander.class);
@Override
public void commanderReady() {
logger.info("[Commander] " + unit.getName() + " is ready!");
}
}

View File

@ -0,0 +1,25 @@
package concreteextensions;
import abstractextensions.SergeantExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import units.SergeantUnit;
/**
* Class defining Sergeant
*/
public class Sergeant implements SergeantExtension {
private SergeantUnit unit;
public Sergeant(SergeantUnit sergeantUnit) {
this.unit = sergeantUnit;
}
final Logger logger = LoggerFactory.getLogger(Sergeant.class);
@Override
public void sergeantReady() {
logger.info("[Sergeant] " + unit.getName() + " is ready! ");
}
}

View File

@ -0,0 +1,25 @@
package concreteextensions;
import abstractextensions.SoldierExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import units.SoldierUnit;
/**
* Class defining Soldier
*/
public class Soldier implements SoldierExtension {
private SoldierUnit unit;
public Soldier(SoldierUnit soldierUnit) {
this.unit = soldierUnit;
}
final Logger logger = LoggerFactory.getLogger(Soldier.class);
@Override
public void soldierReady() {
logger.info("[Solider] " + unit.getName() + " is ready!");
}
}

View File

@ -0,0 +1,27 @@
package units;
import abstractextensions.UnitExtension;
import concreteextensions.Commander;
/**
* Class defining CommanderUnit
*/
public class CommanderUnit extends Unit {
public CommanderUnit(String name) {
super(name);
}
@Override
public UnitExtension getUnitExtension(String extensionName) {
if (extensionName.equals("CommanderExtension")) {
if (unitExtension == null) {
unitExtension = new Commander(this);
}
return unitExtension;
}
return super.getUnitExtension(extensionName);
}
}

View File

@ -0,0 +1,27 @@
package units;
import abstractextensions.UnitExtension;
import concreteextensions.Sergeant;
/**
* Class defining SergeantUnit
*/
public class SergeantUnit extends Unit {
public SergeantUnit(String name) {
super(name);
}
@Override
public UnitExtension getUnitExtension(String extensionName) {
if (extensionName.equals("SergeantExtension")) {
if (unitExtension == null) {
unitExtension = new Sergeant(this);
}
return unitExtension;
}
return super.getUnitExtension(extensionName);
}
}

View File

@ -0,0 +1,27 @@
package units;
import abstractextensions.UnitExtension;
import concreteextensions.Soldier;
/**
* Class defining SoldierUnit
*/
public class SoldierUnit extends Unit {
public SoldierUnit(String name) {
super(name);
}
@Override
public UnitExtension getUnitExtension(String extensionName) {
if (extensionName.equals("SoldierExtension")) {
if (unitExtension == null) {
unitExtension = new Soldier(this);
}
return unitExtension;
}
return super.getUnitExtension(extensionName);
}
}

View File

@ -0,0 +1,28 @@
package units;
import abstractextensions.UnitExtension;
/**
* Class defining Unit, other units will extend this class
*/
public class Unit {
private String name;
protected UnitExtension unitExtension = null;
public Unit(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UnitExtension getUnitExtension(String extensionName) {
return null;
}
}

View File

@ -0,0 +1,14 @@
import org.junit.Test;
/**
* Created by Srdjan on 03-May-17.
*/
public class AppTest {
@Test
public void main() throws Exception {
String[] args = {};
App.main(args);
}
}

View File

@ -0,0 +1,17 @@
package concreteextensions;
import org.junit.Test;
import units.CommanderUnit;
/**
* Created by Srdjan on 03-May-17.
*/
public class CommanderTest {
@Test
public void commanderReady() throws Exception {
final Commander commander = new Commander(new CommanderUnit("CommanderUnitTest"));
commander.commanderReady();
}
}

View File

@ -0,0 +1,17 @@
package concreteextensions;
import org.junit.Test;
import units.SergeantUnit;
/**
* Created by Srdjan on 03-May-17.
*/
public class SergeantTest {
@Test
public void sergeantReady() throws Exception {
final Sergeant sergeant = new Sergeant(new SergeantUnit("SergeantUnitTest"));
sergeant.sergeantReady();
}
}

View File

@ -0,0 +1,17 @@
package concreteextensions;
import org.junit.Test;
import units.SoldierUnit;
/**
* Created by Srdjan on 03-May-17.
*/
public class SoldierTest {
@Test
public void soldierReady() throws Exception {
final Soldier soldier = new Soldier(new SoldierUnit("SoldierUnitTest"));
soldier.soldierReady();
}
}

View File

@ -0,0 +1,23 @@
package units;
import abstractextensions.CommanderExtension;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Created by Srdjan on 03-May-17.
*/
public class CommanderUnitTest {
@Test
public void getUnitExtension() throws Exception {
final Unit unit = new CommanderUnit("CommanderUnitName");
assertNull(unit.getUnitExtension("SoldierExtension"));
assertNull(unit.getUnitExtension("SergeantExtension"));
assertNotNull((CommanderExtension) unit.getUnitExtension("CommanderExtension"));
}
}

View File

@ -0,0 +1,23 @@
package units;
import abstractextensions.SergeantExtension;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Created by Srdjan on 03-May-17.
*/
public class SergeantUnitTest {
@Test
public void getUnitExtension() throws Exception {
final Unit unit = new SergeantUnit("SergeantUnitName");
assertNull(unit.getUnitExtension("SoldierExtension"));
assertNotNull((SergeantExtension) unit.getUnitExtension("SergeantExtension"));
assertNull(unit.getUnitExtension("CommanderExtension"));
}
}

View File

@ -0,0 +1,25 @@
package units;
import abstractextensions.SoldierExtension;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Created by Srdjan on 03-May-17.
*/
public class SoldierUnitTest {
@Test
public void getUnitExtension() throws Exception {
final Unit unit = new SoldierUnit("SoldierUnitName");
assertNotNull((SoldierExtension) unit.getUnitExtension("SoldierExtension"));
assertNull(unit.getUnitExtension("SergeantExtension"));
assertNull(unit.getUnitExtension("CommanderExtension"));
}
}

View File

@ -0,0 +1,30 @@
package units;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Created by Srdjan on 03-May-17.
*/
public class UnitTest {
@Test
public void testConstGetSet() throws Exception {
final String name = "testName";
final Unit unit = new Unit(name);
assertEquals(name, unit.getName());
final String newName = "newName";
unit.setName(newName);
assertEquals(newName, unit.getName());
assertNull(unit.getUnitExtension(""));
assertNull(unit.getUnitExtension("SoldierExtension"));
assertNull(unit.getUnitExtension("SergeantExtension"));
assertNull(unit.getUnitExtension("CommanderExtension"));
}
}

View File

@ -140,7 +140,9 @@
<module>converter</module>
<module>guarded-suspension</module>
<module>balking</module>
<module>extension-objects</module>
<module>marker</module>
</modules>
<dependencyManagement>