Design Patterns in Ruby – Structural Patterns – Decorator Pattern

Tram Ho

Design Patterns in Ruby – Structural Patterns – Decorator Pattern

Intent

Decorator is a structural design pattern that allows you to attach new behaviors to objects by placing them inside special enclosing objects that contain those behaviors.

Problem

Imagine that you work on a notification library that allows other programs to notify their users about important events.

The original version of the Notifier class library was based on only a few fields, constructors, and a single send method. This method can accept a message argument from a client and send the message to a list of emails. The client is supposed to create and configure the notifier object once, then use it whenever there’s a problem .

At some point, you realize that users of the library expect more than just email notifications. Many of them would like to receive an SMS about critical issues. Others would like to be notified on Facebook and, of course, the corporate users would love to get Slack notifications.

However you realize that library users expect more than just email notifications. Many of them want to receive an SMS about important issues. Others want to be notified on Facebook and of course, business users would love to receive notifications via Slack.

You extended the Notifier class and placed additional notification methods on new subclasses. The customer is now obliged to initialize the desired message class and use it for all subsequent messages.

But then someone asks you, Why can you use several types of notifications at the same time? If your house is on fire, you may want to be notified via any channel.

You try to solve that problem by creating special subclasses that combine several notification methods in a class. However, it is clear that this approach will greatly expand the code, not only library code but also client code.

You have to find some other way to structure the message classes to avoid the number of subclasses accidentally breaking Guinness record. ? .

Solution

Extending a class is the first thing you think of when you need to change an object behavior. However, inheritance has some important things that you need to know.

  • You cannot change the behavior of an existing object at run time. The object must be replaced with a new object created from the subclass.
  • Child classes may have only one parent class. Most languages ​​do not allow a class to inherit the behavior of multiple classes at the same time.

One of the ways to overcome these is to use Aggregation or Composition instead of Inheritance. Both schemes work almost the same way: one object has a reference to another object and delegates some work to it, while with inheritance, the object itself must do the job.

Wrapper is an alternative nickname for Decorator model expressing the main idea of ​​the model. A “wrapper” is an object that can be associated with some “target” object. The wrapper contains the same set of methods as target and delegates the target to all requests it receives. However, the wrapper can change the result by doing something before or after it passes the request to the target.

When does a simple wrapper become a real decorator? As mentioned, the wrapper has the same interface as the wrapped object. That is why from the customer’s point of view, these objects are identical. Make the Wrapper accept any reference object with that interface. This will allow you to cover an object in multiple wrappers, adding the combined behavior of all wrappers to it.

In the example of the message, do the following:

The client code will need to wrap a basic wrapper object into a set of decorators that match the client’s options. The resulting object will be structured as a stack.

The last decorator in the stack will be the object the client actually works with. Because all decorators have the same interface as the base notifier, the rest of the client code doesn’t care whether it works with the notifier object or a decorated notifier object.

We can apply the same approach to other behaviors such as message formatting or compiling lists of recipients. Customers can decorate the object with any custom decorator, as long as they follow the same interface as the others.

Real-World Analogy

Dressing is an example of using decorators. When you are cold, you wrap yourself in a sweater. If you’re still cold with a sweater, you can wear a top coat. If it rains, you can wear a raincoat. All of these garments expand your basic behavior but are part of you, and you can easily remove any outfit whenever you don’t need it.

Structure

  1. The component declares a common interface for both wrappers and wrapped objects.
  2. Concrete Component is a class of wrapped objects. It defines basic behavior, which can be changed by decorators .
  3. Base Decorator has a field to reference a wrapped object. Must be declared with component interface so that it can contain both concrete components and decorators . base decorator delegates all operations to the wrapped object.
  4. Concrete Decorators define additional behaviors that can be flexibly added to components . Concrete decorators override base decorator ‘s methods and execute their behavior before or after calling the parent method.
  5. The client can wrap components in multiple decorators classes, as long as it works with all objects through the component interface .

Applicability

  • Use the Decorator pattern when you need to be able to assign additional behaviors to objects at run time without changing the code using these objects.
  • Use the model when it is difficult or impossible to extend an object behavior by inheritance.

How to Implement

  1. Make sure your business domain name can be represented as a key component with many optional classes on it.
  2. Indicates which methods are common to both the main component and optional classes. Create a component interface and declare those methods.
  3. Create a concrete component class and define the basic behavior in it.
  4. Create a base decorator class. It should have a field to store a reference to a wrapped object. The field must be declared as a component interface to allow binding to concrete components as well as decorators . base decorator must delegate all the work for the wrapped object.
  5. Make sure all classes implement component interface .
  6. Create concrete decorators by extending base decorator . A concrete decorator must perform its behavior before or after calling the parent method (always delegating to the wrapped object).
  7. The client code is responsible for creating decorators and composing them the way the client needs.

Decorator in Ruby

main.rb:

output.txt:

Refer

refactoring.guru

Share the news now

Source : Viblo