mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-13 08:15:38 +00:00
docs: improve decorator
This commit is contained in:
1 parent
99f77f8119
commit
e09cada960
3 files changed
+82
-68
No files matched your search
+81
-66
@@ -3,100 +3,96 @@ title: Decorator
|
|||||||
category: Structural
|
category: Structural
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Gang of Four
|
- Gang of Four
|
||||||
- Extensibility
|
- Enhancement
|
||||||
|
- Extensibility
|
||||||
|
- Wrapping
|
||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
|
|
||||||
Wrapper
|
* Smart Proxy
|
||||||
|
* Wrapper
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Attach additional responsibilities to an object dynamically. Decorators provide a flexible
|
The Decorator pattern allows for the dynamic addition of responsibilities to objects without modifying their existing code. It achieves this by providing a way to "wrap" objects within objects of similar interface.
|
||||||
alternative to subclassing for extending functionality.
|
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real-world example
|
||||||
|
|
||||||
> There is an angry troll living in the nearby hills. Usually, it goes bare-handed but sometimes it
|
> There is an angry troll living in the nearby hills. Usually, it goes bare-handed, but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon.
|
||||||
> has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it
|
|
||||||
> dynamically with a suitable weapon.
|
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping
|
> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.
|
||||||
> them in an object of a decorator class.
|
|
||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to
|
> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified.
|
||||||
> be added to an individual object, either statically or dynamically, without affecting the behavior
|
|
||||||
> of other objects from the same class. The decorator pattern is often useful for adhering to the
|
|
||||||
> Single Responsibility Principle, as it allows functionality to be divided between classes with
|
|
||||||
> unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality
|
|
||||||
> of a class to be extended without being modified.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
Let's take the troll example. First of all we have a `SimpleTroll` implementing the `Troll`
|
Let's take the troll example. First of all we have a `SimpleTroll` implementing the `Troll` interface:
|
||||||
interface:
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface Troll {
|
public interface Troll {
|
||||||
void attack();
|
void attack();
|
||||||
int getAttackPower();
|
|
||||||
void fleeBattle();
|
int getAttackPower();
|
||||||
|
|
||||||
|
void fleeBattle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SimpleTroll implements Troll {
|
public class SimpleTroll implements Troll {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attack() {
|
public void attack() {
|
||||||
LOGGER.info("The troll tries to grab you!");
|
LOGGER.info("The troll tries to grab you!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttackPower() {
|
public int getAttackPower() {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fleeBattle() {
|
public void fleeBattle() {
|
||||||
LOGGER.info("The troll shrieks in horror and runs away!");
|
LOGGER.info("The troll shrieks in horror and runs away!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, we want to add a club for the troll. We can do it dynamically by using a decorator:
|
Next, we want to add a club for the troll. We can do it dynamically by using a decorator:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ClubbedTroll implements Troll {
|
public class ClubbedTroll implements Troll {
|
||||||
|
|
||||||
private final Troll decorated;
|
private final Troll decorated;
|
||||||
|
|
||||||
public ClubbedTroll(Troll decorated) {
|
public ClubbedTroll(Troll decorated) {
|
||||||
this.decorated = decorated;
|
this.decorated = decorated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attack() {
|
public void attack() {
|
||||||
decorated.attack();
|
decorated.attack();
|
||||||
LOGGER.info("The troll swings at you with a club!");
|
LOGGER.info("The troll swings at you with a club!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttackPower() {
|
public int getAttackPower() {
|
||||||
return decorated.getAttackPower() + 10;
|
return decorated.getAttackPower() + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fleeBattle() {
|
public void fleeBattle() {
|
||||||
decorated.fleeBattle();
|
decorated.fleeBattle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -105,17 +101,17 @@ Here's the troll in action:
|
|||||||
```java
|
```java
|
||||||
// simple troll
|
// simple troll
|
||||||
LOGGER.info("A simple looking troll approaches.");
|
LOGGER.info("A simple looking troll approaches.");
|
||||||
var troll = new SimpleTroll();
|
var troll=new SimpleTroll();
|
||||||
troll.attack();
|
troll.attack();
|
||||||
troll.fleeBattle();
|
troll.fleeBattle();
|
||||||
LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower());
|
LOGGER.info("Simple troll power: {}.\n",troll.getAttackPower());
|
||||||
|
|
||||||
// change the behavior of the simple troll by adding a decorator
|
// change the behavior of the simple troll by adding a decorator
|
||||||
LOGGER.info("A troll with huge club surprises you.");
|
LOGGER.info("A troll with huge club surprises you.");
|
||||||
var clubbedTroll = new ClubbedTroll(troll);
|
var clubbedTroll=new ClubbedTroll(troll);
|
||||||
clubbedTroll.attack();
|
clubbedTroll.attack();
|
||||||
clubbedTroll.fleeBattle();
|
clubbedTroll.fleeBattle();
|
||||||
LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower());
|
LOGGER.info("Clubbed troll power: {}.\n",clubbedTroll.getAttackPower());
|
||||||
```
|
```
|
||||||
|
|
||||||
Program output:
|
Program output:
|
||||||
@@ -124,13 +120,13 @@ Program output:
|
|||||||
A simple looking troll approaches.
|
A simple looking troll approaches.
|
||||||
The troll tries to grab you!
|
The troll tries to grab you!
|
||||||
The troll shrieks in horror and runs away!
|
The troll shrieks in horror and runs away!
|
||||||
Simple troll power: 10.
|
Simple troll power:10.
|
||||||
|
|
||||||
A troll with huge club surprises you.
|
A troll with huge club surprises you.
|
||||||
The troll tries to grab you!
|
The troll tries to grab you!
|
||||||
The troll swings at you with a club!
|
The troll swings at you with a club!
|
||||||
The troll shrieks in horror and runs away!
|
The troll shrieks in horror and runs away!
|
||||||
Clubbed troll power: 20.
|
Clubbed troll power:20.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@@ -141,12 +137,10 @@ Clubbed troll power: 20.
|
|||||||
|
|
||||||
Decorator is used to:
|
Decorator is used to:
|
||||||
|
|
||||||
* Add responsibilities to individual objects dynamically and transparently, that is, without
|
* Add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.
|
||||||
affecting other objects.
|
|
||||||
* For responsibilities that can be withdrawn.
|
* For responsibilities that can be withdrawn.
|
||||||
* When extension by subclassing is impractical. Sometimes a large number of independent extensions
|
* When extending a class is impractical due to the proliferation of subclasses that could result.
|
||||||
are possible and would produce an explosion of subclasses to support every combination. Or a class
|
* For when a class definition might be hidden or otherwise unavailable for subclassing.
|
||||||
definition may be hidden or otherwise unavailable for subclassing.
|
|
||||||
|
|
||||||
## Tutorials
|
## Tutorials
|
||||||
|
|
||||||
@@ -154,12 +148,32 @@ definition may be hidden or otherwise unavailable for subclassing.
|
|||||||
|
|
||||||
## Known uses
|
## Known uses
|
||||||
|
|
||||||
* [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),
|
* GUI toolkits often use decorators to dynamically add behaviors like scrolling, borders, or layout management to components.
|
||||||
[java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html)
|
* [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), [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html)
|
||||||
* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-)
|
* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-)
|
||||||
* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-)
|
* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-)
|
||||||
* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-)
|
* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-)
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Greater flexibility than static inheritance.
|
||||||
|
* Avoids feature-laden classes high up in the hierarchy.
|
||||||
|
* A decorator and its component aren't identical.
|
||||||
|
* Responsibilities can be added or removed at runtime.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* A decorator and its component aren't identical, so tests for object type will fail.
|
||||||
|
* Decorators can lead to a system with lots of small objects that look alike to the programmer, making the desired configuration hard to achieve.
|
||||||
|
* Overuse can complicate the code structure due to the introduction of numerous small classes.
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
|
||||||
|
* [Adapter](https://java-design-patterns.com/patterns/adapter/): A decorator changes an object's responsibilities, while an adapter changes an object's interface.
|
||||||
|
* [Composite](https://java-design-patterns.com/patterns/composite/): Decorators can be viewed as a degenerate composite with only one component. However, a decorator adds additional responsibilities—it isn't intended for object aggregation.
|
||||||
|
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Decorator lets you change the skin of an object, while Strategy lets you change the guts.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
@@ -169,3 +183,4 @@ definition may be hidden or otherwise unavailable for subclassing.
|
|||||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
||||||
|
* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/4aKFTgS)
|
||||||
@@ -35,7 +35,6 @@ class AppTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue: Add at least one assertion to this test case.
|
* Issue: Add at least one assertion to this test case.
|
||||||
*
|
|
||||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||||
* throws an exception.
|
* throws an exception.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class SimpleTrollTest {
|
|||||||
assertEquals(2, appender.getLogSize());
|
assertEquals(2, appender.getLogSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InMemoryAppender extends AppenderBase<ILoggingEvent> {
|
private static class InMemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
private final List<ILoggingEvent> log = new LinkedList<>();
|
private final List<ILoggingEvent> log = new LinkedList<>();
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user