Decorator pattern using Dart

ref –

  • https://dart.academy/structural-design-patterns-for-dart-and-flutter-decorator/
  • https://medium.com/flutter-community/flutter-design-patterns-16-decorator-bf0dd711f093
  • Pizza Example

Decorator pattern provides a flexible way to add attributes or behavior to an object at runtime

…as an alternative to creating new classes to cover every combination of traits an object may need.

Here we create a square first. It has its basic methods and attributes for use.

But what if we’re not satisfied with its basic functionalities and properties? Say we don’t just want to draw it. We also want to give it a color.

So we then create something like GreenShapeDecorator(square) to let it know that we want to color our square green:

..or blue

Extends an object’s functionality by wrapping it in an object of a Decorator class, leaving the original object intact without modification.

How to do it

Let’s create an abstract class called Shape. (It cannot be instantiated because we want real shapes to implement according to its interface)
It says you must implement draw function.

Hence, when we declare a Shape, for example Square or Triangle, it implements Shape,
and we

We define the Shape interface using an abstract class, so that it can’t be directly instantiated. In this simplified example, the draw() method will return a string appropriate to the shape’s type. Because Square and Triangle implement Shape, they are required to provide an implementation of the draw() method. This means client code can create variables of type Shape that can be assigned any of the shape classes.

In order to add attributes to our Shape, we first create an parent abstract class called ShapeDecorator

1) use constructor to reference a shape, and pass it in using constructor
2) Satisfy implements by declaring it again.
3) Use ‘implements’ to say we’re simply wrapping abstract Shape.

We basically wrapped abstract Shape by passing in the Shape, and keeping the interface draw.

Now, we create concrete decorator class by extending our ShapeDecorator.
Let’s say we want to add color green. We over-ride the draw function and implement it, thus satisfying the interface ShapeDecorator. Inside its implementation, we use green to color the Shape.

1) We use an abstract class to define the interface all shape decorators should adhere to.

2) ShapeDecorator implements Shape, so all classes that extend ShapeDecorator are interface compatible with shape classes.

This is key, because it means we can instantiate a decorator, pass it a shape, and then use the decorator as though it were the shape.

That’s the essence of the Decorator pattern.

Pizza Example

We first declare an interface. It says, all pizzas that implements this must have a description property, a function to get the description, and a function to get the price.

We then have to create a concrete base where it implements our Pizza interface. In order to conform to the interface, we use declaration for properties and override for functions. Being a concrete implementation, don’t forget the constructor.

So, as long as a class implements an interface that is Pizza (or extends from Pizza), we can have a reference of type Pizza and point to the instantiation of that class. This is shown in the picture above.

Now that our base is complete, we are to do the same for an interface for decorating our Pizza base.

Because PizzaDecorator needs to be abstract and needs to extend from abstract Pizza, we simply use extends
It just means we extend the abstract properties from Pizza.

We have a property where its of type Pizza. This means any property that implements Pizza is valid here.
This is because we are going to use it in our overriden getDescription and getPrice functions.
Also, a constructor that injects an instance of pizza is required.

As you can see, we have Pizza pizzaInt, which can reference either our PizzaBase object or a Sauce object. This is because at the top of the abstract class, we have Pizza.

Also notice that in abstract PizzaDecorator, we have a Pizza pizza reference that points to PizzaBase.

On the right side, we instantiate a PizzaBase object. We have a reference pizzaInst point to it.

We then pass the reference inside of Sauce.

Sauce is depicted on the left side. Its abstract class Decorator has a property Pizza pizza, which points to abstract Pizza. This means, it can point to PizzaBase, Sauce, or any condiments (which must extend from abstract Pizza).

Notice getDescription.

To get the whole pizza’s description of the name, and toppings, it first looks at reference pizza, which is pointing to the PizzaBase. It calls its getDescription, which gets “Pizza Margherita”.

Then we return the interpolated string with our own description.

Thus

“Pizza Margherita” <-- PizzaBase's getDescription "- Sauce" <-- Sauce's getDescription

If were to keep going with toppings like Mozzarella

it would be the same concept. Mozzarella’s Pizza pizza reference would point to Sauce.
When we call getDescription, it would call

Mozzarella’s

Sauce’s

PizzaBase

Thus, in the end the output would be: