Decorator Design Pattern: The Adornments of Software
[tr] Türkçe Oku 2023-06-15
Once upon a time, there was an object named “Component”. This object was used to perform a specific function. However, sometimes it was necessary to extend or modify this function. That’s where “Decorators” came into play.
Decorators are objects that “decorate” or extend the Component. A Decorator comes on top of a Component and extends or modifies its function. This is used to extend the functionality of the Component without changing it itself.
For example, consider a “TextComponent”. This component prints a text to the screen. However, sometimes we may want to make the text bold or italic. That’s where “BoldDecorator” or “ItalicDecorator” come into play. These decorators come on top of the TextComponent and make the text bold or italic.
The decorator pattern works in this way. With a component and a series of decorators that extend it, we can perform a wide variety of functions.
Here is a class diagram of the decorator pattern:
In this diagram, you can see a “Component” and a series of “Decorators” that decorate or extend this component. The “ConcreteComponent” class extends the “Component” class and performs a specific function. The “Decorator” class also extends the “Component” class and contains a “Component” object. The “ConcreteDecoratorA” and “ConcreteDecoratorB” classes extend the “Decorator” class and perform a specific function.
First, I will give an example in C#:
// Component
public abstract class Component
{
public abstract void Operation();
}
// ConcreteComponent
public class ConcreteComponent : Component
{
public override void Operation()
{
// ...
}
}
// Decorator
public abstract class Decorator : Component
{
protected Component component;
public Decorator(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
// ConcreteDecoratorA
public class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(Component component) : base(component) { }
public override void Operation()
{
base.Operation();
// Additional behavior
}
}
// ConcreteDecoratorB
public class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(Component component) : base(component) { }
public override void Operation()
{
base.Operation();
// Additional behavior
}
}
In this code, we created a “Component” and a series of “Decorators” that decorate or extend this component. The “ConcreteComponent” class extends the “Component” class and performs a specific function. The “Decorator” class also extends the “Component” class and contains a “Component” object. The “ConcreteDecoratorA” and “ConcreteDecoratorB” classes extend the “Decorator” class and perform a specific function.
Java:
// Component
public interface Component {
void operation();
}
// ConcreteComponent
public class ConcreteComponent implements Component {
public void operation() {
// ...
}
}
// Decorator
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
if (component != null) {
component.operation();
}
}
}
// ConcreteDecoratorA
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
public void operation() {
super.operation();
// Additional behavior
}
}
// ConcreteDecoratorB
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
public void operation() {
super.operation();
// Additional behavior
}
}
Go:
// Component
type Component interface {
Operation() string
}
// ConcreteComponent
type ConcreteComponent struct{}
func (c *ConcreteComponent) Operation() string {
return "ConcreteComponent"
}
// Decorator
type Decorator struct {
component Component
}
func (d *Decorator) Operation() string {
if d.component != null {
return d.component.Operation()
}
return ""
}
// ConcreteDecoratorA
type ConcreteDecoratorA struct {
Decorator
}
func (c *ConcreteDecoratorA) Operation() string {
return "ConcreteDecoratorA(" + c.Decorator.Operation() + ")"
}
// ConcreteDecoratorB
type ConcreteDecoratorB struct {
Decorator
}
func (c *ConcreteDecoratorB) Operation() string {
return "ConcreteDecoratorB(" + c.Decorator.Operation() + ")"
}
Rust:
// Component
trait Component {
fn operation(&self) -> String;
}
// ConcreteComponent
struct ConcreteComponent;
impl Component for ConcreteComponent {
fn operation(&self) -> String {
String::from("ConcreteComponent")
}
}
// Decorator
struct Decorator<T: Component> {
component: T,
}
impl<T: Component> Component for Decorator<T> {
fn operation(&self) -> String {
self.component.operation()
}
}
// ConcreteDecoratorA
struct ConcreteDecoratorA<T: Component> {
decorator: Decorator<T>,
}
impl<T: Component> Component for ConcreteDecoratorA<T> {
fn operation(&self) -> String {
let base_operation = self.decorator.operation();
format!("ConcreteDecoratorA({})", base_operation)
}
}
// ConcreteDecoratorB
struct ConcreteDecoratorB<T: Component> {
decorator: Decorator<T>,
}
impl<T: Component> Component for ConcreteDecoratorB<T> {
fn operation(&self) -> String {
let base_operation = self.decorator.operation();
format!("ConcreteDecoratorB({})", base_operation)
}
}
These examples show how the decorator pattern can be implemented in different languages. Remember that each language has its own features and syntax, so examples for each language may be slightly different.