Home Simplify Decorator Pattern (Structure Pattern)
Post
Cancel

Simplify Decorator Pattern (Structure Pattern)

🌑 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.

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

🌥️ Why Decorator

One day our customer sent an email that he required one more analytics tool to be integrated into the application. We already have the Firebase analytic tool in the application. The reason mentioned by the customer to add a new analytic tool is that it provides good support with their BI Tool.

The team checked the current implementation. The current implementation has an analytics event controller class which accepts an event object and sends the same to the Google Analytics framework.

One quick common approach all the team members have is to add support for Adobe Analytics in the Analytic controller, so when the developer sends analytics from any screen it will by default capture both frameworks (Firebase, Adobe Analytics).

With this approach, the Analytic Controller code will become bloated. If there is a new requirement to add a new type of analytics, then it will not be easy to accommodate in the controller.

We brainstormed more on this so that the Analytic Controller would not be overburdened in future and then the decorator came into the picture.

🌛 = 🌜 Metaphor

Adding topping to the Pizza base can related to the decorator. If you are preparing Tomato Pizza then you will put Tomato on the base. If you add the vegetable on the base then it is a vegetable Pizza.

So based on topping on the Pizza base behaviour is changed and taste also :).

🌓 Talk is cheap. Show me the code

⚡ Create an interface with the required methods.
⚡ Create a concrete class implementing the basic behaviour in it.
⚡ Create a decorator class implementing the same interface and also implement the add-on behaviour in it.
⚡ The decorator class should also hold a strong reference to the base class and call the base class method first.

Code Snippet

An analytic event is a struct which will hold an analytic event and an Analytic Delegate is protocol.

1
2
3
4
5
6
7
8
9
10
struct AnalyticEvent {
    let eventName: String
    var eventDetail: [String: String]
}


protocol AnalyticDelegate {
    func sendEvent(_ event: AnalyticEvent)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Firebase Analytics

import Firebase

class AnalyticController: AnalyticDelegate {
    init() {
        FirebaseApp.configure()
    }
    
    func sendEvent(_ event: AnalyticEvent) {
        Analytics.logEvent(event.eventName, parameters:event.eventDetail)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Class Implementation before Decorator

class AddToCartController {
    let analyticDelegate: AnalyticDelegate
    
    init(analyticDelegate: AnalyticDelegate = AnalyticController()) {
        self.analyticDelegate = analyticDelegate
    }
    
    func startPayment(for product: Product, paymentMethod: SupportedPaymentFacade) {
        let paymentInfo = PaymentInfo(skuID: product.skuID, amount: product.amount, method: paymentMethod)
        facade.doPayment(paymentInfo)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Decorator class implements the Adobe Analytics and delegates firebase event to the existing controller.

class AnalyticControllerDecorator: AnalyticDelegate {
    let analyticDelegate: AnalyticDelegate
    init(analyticDelegate: AnalyticDelegate = AnalyticController()) {
        self.analyticDelegate = analyticDelegate
    }
    
    func sendEvent(_ event: AnalyticEvent) {
        MobileCore.track(action: event.eventName, data: event.eventDetail)
        self.analyticDelegate.logEvent(event.eventName, parameters:event.eventDetail)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Class Implementation before Decorator

class AddToCartController {
    let analyticDelegate: AnalyticDelegate
    
    init(analyticDelegate: AnalyticDelegate = AnalyticController()) {
        self.analyticDelegate = analyticDelegate
    }
    
    func startPayment(for product: Product, paymentMethod: SupportedPaymentFacade) {
        let paymentInfo = PaymentInfo(skuID: product.skuID, amount: product.amount, method: paymentMethod)
        facade.doPayment(paymentInfo)
    }
}

1
2
3
//Uses with or without Decorator
var controllerWithFirebaseAnalytic =  AddToCartController(AnalyticController())
var controllerWithFirebaseAnalytic =  AddToCartController(AnalyticControllerDecorator())

✨ Trade-offs

Flexibility vs Usability

It provides the flexibility of adding new functionality at run time. Also, you can mix & match the new functionalities by creating n number of classes. However, you cannot remove any decorator from the sequence of execution without writing any custom logic in the decorator.

Flexibility vs Readability

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.

For example: if you need to support 3 different analytics tools based on the configuration then you might end up minimum of 4 small classes.

🌕 Applicability

⚡ Use the decorator when you need to add additional behaviour to an existing object without impacting the current uses of the object.
⚡ 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.
⚡ Additional behaviour is required on the final class without changing anything on an existing object.
⚡ 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.

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

🌚 Facts

Inheritance & Decorator

⚡ Where inheritance only works for similar kinds of objects but decorator can be used on objects with a common interface.
⚡ Classes with final behaviour. In those cases, inheritance is not possible. so we can use a decorator pattern to add additional behaviour.
⚡ Decorator applies to only a single object of a class. However, inheritance applies to the entire class.
⚡ Use inheritance when you need additional behaviour on non-public members of the class.

Adapter vs Decorator ⚡ An adapter pattern also holds an object and provides a simplified interface (familiar Interface) to the outer world for that object.
⚡ Adapter Object does not provide an additional behaviour on the object.
⚡ In the adapter case, the client (who calls the method) is unaware of the object interface. It only knows the Adapter interface.

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

Simplified Facade Pattern (Structure Pattern)

Distribute iOS Beta Build (Scheme & Configurations)

Comments powered by Disqus.