Home Simplify Decorator (Structural Pattern)
Post
Cancel

Simplify Decorator (Structural Pattern)

🌥️ Why Decorator

One day has received an email from a customer to support the movie review application offline. She wants to implement caching for the movie list.

team sits together and analyses the current implementation. Code has a repository to get a movie list.

Her team designed a quick solution to add caching support to the repository. They designed and added a new cache object as a dependency in MovieListRepository.

This solution will change the existing repository code and defy the open/closed principle. wants to decouple caching from the repository like she has a constructor and decorator two different workers for her home.

Constructor Spider
Decorator Spider

🌑 Outline

The decorator pattern adds new functionality to an existing object without changing its structure. A wrapper on the existing object is created to add new functionality.

Decorator - Decorate an object (wrapper of an object).

🌛 = 🌜 Decorator In Real World

Imagine a vanilla ice cream. Now add chocolate syrup to it. It adds a new behaviour (chocolate flavour) to the ice cream. Similarly, sprinkles are another decorator for the ice cream.

So every topping on the ice cream adds new behaviour and taste :).

<img src=”decoratorMetaphor.png height=”400” width=”400”>

🌕 Solution

created a decorator class MovieListCachingRepository that wraps the repository DefaultMovieListRepository. It implements the same MovieListRepository interface. This decorator calls the actual repository while also checking and caching data.

If you observe the decorator class, it wraps the original repository object, which is why its nickname is “Wrapper”. It also implements the same interface as the wrapped object; from the client’s perspective, these objects are identical.

As in the application, based on the need, wraps the basic repository object into a MovieListCachingRepository. Similarly, you can create a stack of decorators to achieve the desired behaviour.

The final object in the decorator chain is the one that the application interacts with directly. Since all decorators implement the same interface MovieListRepository, the application code remains unaffected by changes to the decorator chain.

Decorator Componentes

Component Interface:
This is a common interface for concrete components and decorators. Defines the basic operations that both must support. MovieListRepository created by is an example of interface.

ConcreteComponent:
These are the basic objects that provide the core functionality. DefaultMovieListRepository provides the core functionality repository functionality.

Decorator:
An interface that defines the decorator’s behaviour. It holds a reference to the component it decorates. did not create a decorator interface in the solution. However, you can create one to provide a defined standard behaviour for all decorators.

ConcreteDecorators:
It extends the decorator class and provides concrete implementations for the additional behaviour. It adds additional behaviour to the component. MovieListCachingRepository is a concrete Decorator.

🌟 Where To Apply

    Use the decorator when you need to add additional behaviour to an existing object without impacting the existing implementation.

The decorator object and main objects share a common interface so both can be used interchangeably. In this way, existing applications can use additional business requirements with minimal changes.

    Use the decorator when it is not possible to add the behaviour to an existing object using Inheritance

Sometimes, in a library, classes are declared as final when you want to add additional behaviour to the object of the classes. Then, the only rescue is the decorator.

Some Common use cases for the decorator pattern are Logging, Caching and performance evaluation of a method.

🌓 To Gain Something, You Have To Lose

Design patterns are solutions to common problems in software design. They offer a structured approach to solving problems, like a blueprint. It is not that the problem cannot be solved without a design pattern, however, a solution using a design pattern will provide better flexibility, scalability and n number of benefits. However, every benefit comes with a cost. Let’s explore what that means in the context of the Decorator pattern.

Reusability vs Complexity

Decorators promote code reuse by allowing you to add functionality to existing functions or classes without modifying their original code. It helps separate concerns by encapsulating additional behaviour in separate functions or classes.

Excessive use of decorators can make code more complex and harder to understand. Also, Chaining decorators can complicate the execution flow, making debugging more difficult.

Flexibility vs Complexity

It provides the flexibility of adding new functionality at run time. Multiple decorators can be chained together to create complex behaviours.

On the other hand, It may also lead to many tiny objects in your code, none of which do anything on their own. It will be very difficult to understand for a new developer and debug any issue.

However, you cannot remove any decorator from the sequence of execution without writing any custom logic in the decorator.

🌚 Some Interesting Facts

   Adapter & Decorator

Both Adapter and Decorator patterns involve wrapping an object to modify its behaviour. However, the purpose and operating mechanism are different.
An adapter provides a simplified interface for accessing an object. It wraps an existing object and provides a new interface that conforms to the desired standard. Conversely, a Decorator dynamically adds responsibilities to an object without altering its original class.

Adapter
Decorator

    Startegy & Decorator

Again, Both the Strategy and Decorator patterns are used to add flexibility to software design. However, they have distinct purposes and approaches.
The Strategy pattern provides flexibility to select an algorithm at runtime. Instead of hardcoding a specific algorithm, the code receives instructions on which algorithm to use. Sorting algorithms are a good example of strategy.
Decorator provides flexibility to dynamically add responsibilities to an object.

   Decorator Factories:
You can create decorator factories, which are functions that generate decorators. This adds another layer of flexibility and customization.

Code Example (Swift)

Component Interface:

1
2
3
4
protocol MovieListRepository {
    var request: NetworkRequest { get }
    func getLatestMovies()
}

ConcreteComponent:

1
2
3
4
5
6
7
8
9
10
11
class DefaultMovieListRepository: MovieListRepository {
    var request: any NetworkRequest
    
    init(request: NetworkRequest) {
        self.request = request
    }
    
    func getLatestMovies() {
        
    }
}

ConcreteDecorators:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MovieListCachingRepository: MovieListRepository {
    var repository: MovieListRepository
    var cache: Storage
    var request: any NetworkRequest {
            return repository.request
    }
    
    init(cache: Storage, repository: MovieListRepository) {
        self.cache = cache
        self.repository = repository
    }
    
    func getLatestMovies() {
        //repository.getLatestMovies()
        //Logic to store the movie
    }
}

Client Application:

1
2
3
4
5
6
7
8
9
10
11
12
//Client
class clientAPP {
    let isCachingEnable = true
    let storage = MovieStorge()
    
    func getMovies() {
        var repositery: MovieListRepository = DefaultMovieListRepository(request: MovieNetworkRequest())
        if isCachingEnable {
            repositery = MovieListCachingRepository(cache: storage, repository: repositery)
        }
    }
}

You can create decorators on the UIView/SwiftUI View to display borders, rounded corners, add background views, etc.

This post is licensed under CC BY 4.0 by the author.

Flutter on Apple Silicon Mac: Cocoa-pods Issue

Simplified Facade (Structural Pattern)

Comments powered by Disqus.