The decorator pattern

The first structural pattern that we will discuss is the decorator pattern. It introduces you to the object substitution by adding new functionalities or behaviors.

Roles

The main objective of this pattern is to dynamically add new functionalities to an object. The interface of the object will not be modified, so from the client's perspective, this is fully transparent.

This pattern is an alternative to the addition of a subclass that adds functionalities to its parent class. A key implementation point in the decorator pattern is that decorators both inherit the original class and contain an instantiation of it.

This pattern can be used when:

  • A system adds dynamically new functionalities to an object, without having to modify its interface, which means without having to modify the client of this object
  • A system manages the behavior that can be dynamically removed
  • The use of inheritance is not a good option because of an already complex class hierarchy

Design

The generic UML class diagram of the decorator pattern is quiet simple: The ConcreteComponent and AbstractDecorator classes share the same interface that have the same method name. Our AbstractDecorator class defines a constructor where we pass our Abstractcomponent class as an argument. Then, in our ConcreteDecorator class, we reroute the operation call to the additionalOperation methods to add new functionalities or behaviors to the original component, as shown in the following diagram:

Design

Participants

In the preceding diagram, there are four participants in this pattern:

  • AbstractComponent: This is the common interface to components and decorators.
  • ConcreteComponent: This is the main object to which we want to add behaviors and/or functionalities.
  • AbstractDecorator: This abstract class contains a reference to a component.
  • ConcreteDecoratorA and ConcreteDecoratorB: These are the concrete subclasses of AbstractDecorator. These classes implement the functionalities added to the component.

Collaboration

When a decorator receives a message that must reach the component, it redirects the message by making a prior or posterior operation to that redirection.

Sample

To illustrate this pattern, we will take a simple example. Let's suppose that you have a drawing software that enables you to draw some shapes on the screen: a rectangle and square.

You already know how to draw these shapes. Now, you need to add a new functionality that will add a rounded angle to your shapes. To do this, you need to decide which decorator pattern you need to use that will allow you to not to interfere with the existing class method signature.

Implementation

First, we will create our interface that defines the shape. We will simulate a Draw() operation. In fact, the method will return a string that tells us what is drawn:

protocol IShape {
  func draw() -> String
}

Now, we will create our two concrete classes that implement the IShape interface. We will have the Square and Rectangle classes. They both implement the draw function. This function returns the shape that is currently drawn:

class Square: IShape {
  func draw() -> String{
    return "drawing Shape: Square"
  }
}

class Rectangle: IShape {
  func draw() -> String {
    return "drawing Shape: Rectangle"
  }
}

Our classes are ready; now, we prepare our abstract ShapeDecorator class that defines the structure of our future concrete decorators. This class implements the IShape interface too, so the Draw() function must be present. Nevertheless, Swift doesn't have an abstract class, so we implement the draw method, but we force an exception to tell us that this method must be implemented. The ShapeDecorator class will not be used by the client itself. The client will call the ConcreteDecorator object to add a new functionality to its shape:

class ShapeDecorator: IShape {
  private let decoratedShape: IShape
  
  required init(decoratedShape: IShape){
    self.decoratedShape = decoratedShape
  }
  
   func draw() -> String {
    fatalError("Not Implemented")
  }
}

Now, we add our concrete decorator class that inherits from the ShapeDecorator abstract class. We add our new setRoundedCornerShape functionality to this class and override the draw function to return the shape that is drawn, but with rounded corners:

class RoundedCornerShapeDecorator: ShapeDecorator{
  required init(decoratedShape: IShape) {
       super.init(decoratedShape: decoratedShape)
  }
  
  override func draw() ->String{
    //we concatenate our shape properties
     return  decoratedShape.draw() + "," + setRoundedCornerShape(decoratedShape)
  }
  
  func setRoundedCornerShape(decoratedShape: IShape) -> String{
    return "Corners are rounded"
  }
}

Usage

Now, here is the easy part that shows us how to use all of the code, we already have written, from the client perspective.

We first create our two concrete shapes:

let rectangle = Rectangle()
let square = Square()

Now, we want to have some shapes with rounded corners. To do this, we simply call the ConcreteDecorator class that interests us, the RoundedCornerShapeDecorator class, and pass a new shape (Rectangle or Square) as an argument of the constructor:

let roundedRectangle = RoundedCornerShapeDecorator(decoratedShape: Rectangle())

let roundedSquare = RoundedCornerShapeDecorator(decoratedShape: Square())

Now, we simulate the Draw() method on the screen of our shapes by calling the draw operation:

print("rectangle with Normal Angles")
rectangle.draw()

print("square with Normal Angles")
square.draw()

//rounded corners shapes
roundedRectangle.draw()
roundedSquare.draw()

The Playground will return the following result:

Usage

Note

Swift allows you to implement the decorator pattern using the concept of extensions. This allows you to add additional methods to concrete classes or constructs, without having to subclass or alter the original one. With extensions, you can add new methods but no new properties, as opposed to a subclass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset