mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-13 18:15:41 +00:00
docs: extension objects
This commit is contained in:
1 parent
59d3e52434
commit
b35494af9a
8 files changed
+65
-38
No files matched your search
+56
-14
@@ -1,18 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Extension objects
|
title: Extension Objects
|
||||||
category: Behavioral
|
category: Structural
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Extensibility
|
- Encapsulation
|
||||||
|
- Extensibility
|
||||||
|
- Object composition
|
||||||
|
- Polymorphism
|
||||||
---
|
---
|
||||||
|
|
||||||
# Extention Objects Pattern
|
## Also known as
|
||||||
|
|
||||||
|
* Interface Extensions
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Anticipate that an object’s interface needs to be extended in the future. Additional
|
|
||||||
interfaces are defined by extension objects.
|
The Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure, by attaching additional objects that can dynamically add new functionality.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real-world example
|
||||||
|
|
||||||
> Suppose you are developing a Java-based game for a client, and in the middle of the development process, new features are suggested. The Extension Objects pattern empowers your program to adapt to unforeseen changes with minimal refactoring, especially when integrating additional functionalities into your project.
|
> Suppose you are developing a Java-based game for a client, and in the middle of the development process, new features are suggested. The Extension Objects pattern empowers your program to adapt to unforeseen changes with minimal refactoring, especially when integrating additional functionalities into your project.
|
||||||
@@ -27,10 +33,10 @@ Wikipedia says
|
|||||||
|
|
||||||
**Programmatic example**
|
**Programmatic example**
|
||||||
|
|
||||||
The aim of utilising the Extension Objects pattern is to implement new features/functionality without having to refactor every class.
|
The aim of utilising the Extension Objects pattern is to implement new features/functionality without having to refactor every class. The following examples shows utilising this pattern for an Enemy class extending Entity within a game:
|
||||||
The following examples shows utilising this pattern for an Enemy class extending Entity within a game:
|
|
||||||
|
|
||||||
Primary App class to execute our program from.
|
Primary App class to execute our program from.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class App {
|
public class App {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -50,7 +56,9 @@ public class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Enemy class with initial actions and extensions.
|
Enemy class with initial actions and extensions.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Enemy extends Entity {
|
class Enemy extends Entity {
|
||||||
public Enemy(String name) {
|
public Enemy(String name) {
|
||||||
@@ -72,7 +80,9 @@ class Enemy extends Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
EnemyExtension class with overriding extendAction() method.
|
EnemyExtension class with overriding extendAction() method.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class EnemyExtension implements EntityExtension {
|
class EnemyExtension implements EntityExtension {
|
||||||
@Override
|
@Override
|
||||||
@@ -81,7 +91,9 @@ class EnemyExtension implements EntityExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Entity class which will be extended by Enemy.
|
Entity class which will be extended by Enemy.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Entity {
|
class Entity {
|
||||||
private String name;
|
private String name;
|
||||||
@@ -105,32 +117,62 @@ class Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
EntityExtension interface to be used by EnemyExtension.
|
EntityExtension interface to be used by EnemyExtension.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
interface EntityExtension {
|
interface EntityExtension {
|
||||||
void extendedAction();
|
void extendedAction();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Program output:
|
Program output:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Enemy performs the initial action.
|
Enemy performs the initial action.
|
||||||
Enemy wants to attack you.
|
Enemy wants to attack you.
|
||||||
Enemy has advanced towards you!
|
Enemy has advanced towards you!
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the Extension Objects pattern allows the enemy entity to perform unique initial actions and advanced actions when specific extensions are applied. This pattern provides flexibility and extensibility to the codebase while minimizing the need for major code changes.
|
In this example, the Extension Objects pattern allows the enemy entity to perform unique initial actions and advanced actions when specific extensions are applied. This pattern provides flexibility and extensibility to the codebase while minimizing the need for major code changes.
|
||||||
|
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## 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
|
This pattern is applicable in scenarios where an object's functionality needs to be extended at runtime, avoiding the complications of subclassing. It's particularly useful in systems where object capabilities need to be augmented post-deployment, or where the capabilities might vary significantly across instances.
|
||||||
* 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
|
## Known Uses
|
||||||
|
|
||||||
|
* Extending services in an application server without altering existing code.
|
||||||
|
* Plugins in IDEs like IntelliJ IDEA or Eclipse to add features to the base application.
|
||||||
|
* Enabling additional features in enterprise software based on license levels.
|
||||||
* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc)
|
* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc)
|
||||||
* [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding)
|
* [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding)
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Enhances flexibility by allowing dynamic extension of an object's capabilities.
|
||||||
|
* Promotes loose coupling between the base object and its extensions.
|
||||||
|
* Supports the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle) by keeping the object open for extension but closed for modification.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* Can increase complexity due to the management of extension objects.
|
||||||
|
* May introduce performance overhead if the interaction between objects and extensions is not efficiently designed.
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
|
||||||
|
* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar in intent to add responsibilities dynamically, but uses a different structure.
|
||||||
|
* [Composite](https://java-design-patterns.com/patterns/composite/): Also manages a group of objects, which can be seen as a form of extension.
|
||||||
|
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Offers an alternative way to change the behavior of an object dynamically.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/4aBMuuL)
|
||||||
|
* [Pattern-Oriented Software Architecture: A System of Patterns](https://amzn.to/3Q9YOtX)
|
||||||
|
* [Patterns of Enterprise Application Architecture](https://amzn.to/3W6IZYQ)
|
||||||
@@ -33,12 +33,8 @@ import units.CommanderUnit;
|
|||||||
/**
|
/**
|
||||||
* Class defining Commander.
|
* Class defining Commander.
|
||||||
*/
|
*/
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Commander implements CommanderExtension {
|
public record Commander(CommanderUnit unit) implements CommanderExtension {
|
||||||
|
|
||||||
private final CommanderUnit unit;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commanderReady() {
|
public void commanderReady() {
|
||||||
|
|||||||
@@ -33,12 +33,8 @@ import units.SergeantUnit;
|
|||||||
/**
|
/**
|
||||||
* Class defining Sergeant.
|
* Class defining Sergeant.
|
||||||
*/
|
*/
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Sergeant implements SergeantExtension {
|
public record Sergeant(SergeantUnit unit) implements SergeantExtension {
|
||||||
|
|
||||||
private final SergeantUnit unit;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sergeantReady() {
|
public void sergeantReady() {
|
||||||
|
|||||||
@@ -33,12 +33,8 @@ import units.SoldierUnit;
|
|||||||
/**
|
/**
|
||||||
* Class defining Soldier.
|
* Class defining Soldier.
|
||||||
*/
|
*/
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Soldier implements SoldierExtension {
|
public record Soldier(SoldierUnit unit) implements SoldierExtension {
|
||||||
|
|
||||||
private final SoldierUnit unit;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void soldierReady() {
|
public void soldierReady() {
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ import java.util.List;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Srdjan on 03-May-17.
|
* CommanderTest
|
||||||
*
|
|
||||||
* Modified by ToxicDreamz on 15-Aug-20
|
|
||||||
*/
|
*/
|
||||||
class CommanderTest {
|
class CommanderTest {
|
||||||
|
|
||||||
@@ -56,10 +54,9 @@ class CommanderTest {
|
|||||||
commander.commanderReady();
|
commander.commanderReady();
|
||||||
|
|
||||||
List<ILoggingEvent> logsList = listAppender.list;
|
List<ILoggingEvent> logsList = listAppender.list;
|
||||||
assertEquals("[Commander] " + commander.getUnit().getName() + " is ready!", logsList.get(0)
|
assertEquals("[Commander] " + commander.unit().getName() + " is ready!", logsList.get(0)
|
||||||
.getMessage());
|
.getMessage());
|
||||||
assertEquals(Level.INFO, logsList.get(0)
|
assertEquals(Level.INFO, logsList.get(0)
|
||||||
.getLevel());
|
.getLevel());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -54,7 +54,7 @@ class SergeantTest {
|
|||||||
sergeant.sergeantReady();
|
sergeant.sergeantReady();
|
||||||
|
|
||||||
List<ILoggingEvent> logsList = listAppender.list;
|
List<ILoggingEvent> logsList = listAppender.list;
|
||||||
assertEquals("[Sergeant] " + sergeant.getUnit().getName() + " is ready!", logsList.get(0)
|
assertEquals("[Sergeant] " + sergeant.unit().getName() + " is ready!", logsList.get(0)
|
||||||
.getMessage());
|
.getMessage());
|
||||||
assertEquals(Level.INFO, logsList.get(0)
|
assertEquals(Level.INFO, logsList.get(0)
|
||||||
.getLevel());
|
.getLevel());
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class SoldierTest {
|
|||||||
soldier.soldierReady();
|
soldier.soldierReady();
|
||||||
|
|
||||||
List<ILoggingEvent> logsList = listAppender.list;
|
List<ILoggingEvent> logsList = listAppender.list;
|
||||||
assertEquals("[Soldier] " + soldier.getUnit().getName() + " is ready!", logsList.get(0)
|
assertEquals("[Soldier] " + soldier.unit().getName() + " is ready!", logsList.get(0)
|
||||||
.getMessage());
|
.getMessage());
|
||||||
assertEquals(Level.INFO, logsList.get(0)
|
assertEquals(Level.INFO, logsList.get(0)
|
||||||
.getLevel());
|
.getLevel());
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
class UnitTest {
|
class UnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConstGetSet() throws Exception {
|
void testConstGetSet() {
|
||||||
final var name = "testName";
|
final var name = "testName";
|
||||||
final var unit = new Unit(name);
|
final var unit = new Unit(name);
|
||||||
assertEquals(name, unit.getName());
|
assertEquals(name, unit.getName());
|
||||||
|
|||||||
Reference in new issue
Block a user