How do I add behavior to a class or object?
- Inheritance mechanisms. Using inheritance mechanisms is an effective way to add functionality to an existing class. By inheriting an existing class, a subclass can have its parent’s methods in its peers. But this approach is static, and the user has no control over how or when the behavior is added
- An association mechanism whereby an object of one class is embedded in another object, and the other object decides whether to invoke the behavior of the embedded object to extend its behavior. We call this embedded object a Decorator.
The schema definition
Decorator Pattern: Dynamically assigning additional responsibilities to an object is more flexible in terms of adding object functionality than generating subclass implementations. This type of design pattern is a structural pattern that acts as a wrapper around existing classes.
Pattern structure and specification
Component: Interface to Component objects to which responsibilities can be dynamically added
ConcreteComponent: ConcreteComponent objects that implement component object interfaces, usually primitive objects decorated by decorators, to which functions can be added
Decorator: An abstract parent class of all decorators that defines an interface consistent with the Component interface and has only one Component object that holds a Decorator object. Note that the object to be decorated is not necessarily the original object, but may be an object that has been decorated by other decorators, which implement the same interface, i.e., the same type.
ConcreteDecorator: The actual decorator object that implements the specific function to be added to the decorator object.
Sample implementation
Consider a situation where there is a Pancake stand and people go to buy pancakes, some people want Ham, some people want Egg, some people want Lettuce, and some people want some of these
Implementation without patterns
To make a pancake, the first thing is to know what the guest wants to add. Is it done according to the guest’s request
- The first thing is to get all the materials ready
- (void)addEgg {NSLog(@" addEgg "); } - (void)addHam {NSLog(@" addHam "); } - (void)addLettuce {NSLog(@" addLettuce "); } - (void)cook {NSLog(@" make a pancake "); }Copy the code
- Then according to the needs of the guests to customize
- (void)cookPancakeWithEgg:(BOOL)egg ham:(BOOL)ham lettuce:(BOOL)lettuce
{
if (egg) {
[self addEgg];
}
if (ham) {
[self addHam];
}
if (lettuce) {
[self addLettuce];
}
[self cook];
}
Copy the code
- Client testing
Pancake *pancake = [[Pancake alloc] init];
[pancake cookPancakeWithEgg:YES ham:NO lettuce:YES];
Copy the code
- The results show
There is a pancake with egg and lettuce on it, and when you look at this, you will say that there is no problem. I can do whatever the customer needs. There are two problems:
- If we add steak to our current ingredients, what about chicken fillets?
- When you’re making pancakes, it’s a bunch of ifs that say, if the ingredients change, the code has to change, which is a serious violation of the open and closed rule.
So what’s the solution? Let’s abstract the above problem, suppose I make a pancake, as for adding ingredients, just flexibly add functions to it according to needs, but also dynamic combination, then the question comes, how can transparently add functions to an object, and achieve dynamic combination of functions?
A reasonable solution to the above problem is to use decoration patterns
Decoration mode solution
Add functions to an object transparently. In other words, add functions to an object transparently without letting the object know about it. In other words, you can’t change the object
In order to transparently add functionality to an object, that is, to extend the functionality of the object, using inheritance, someone immediately proposed a solution, but was quickly rejected, how about reducing or modifying functionality? Inheritance is actually a very inflexible way to reuse. Then use “object composition”, someone came up with a new scheme, this scheme has been approved by everyone.
In the realization of the decorative pattern, in order to be able to and use the code to achieve seamless combination of object being decorated, is by defining an abstract class, make the class implements the same interface and decorative objects, and then the implementation class, transfer subject of decoration, before and after the transfer of adding new features, this is achieved by decorative objects increase function, the idea with “object composition “Very similar to this, if you feel that the function of the decorated object is no longer needed, you can simply replace it, that is, no longer transpose, but completely new implementation in the decorated object.
Decorator pattern sample code
- Pancake component interfaces and basic implementation objects
@interface AbPancake: NSObject - (void)cook; @endCopy the code
- Define a basic implementation for the pancake
@interface IPancake: AbPancake @end @implementation IPancake - (void)cook {NSLog(@" pancake "); } @endCopy the code
- Define abstract decorator classes
We define the common parent class of each decorator, where we define the methods that all decorator objects need to implement. The parent class should implement the component’s interface so that the decorated object can still be decorated
@interface Decorator: AbPancake /* Holds the decorated component object */ @property (nonatomic,strong) AbPancake *pancake; // pass in the constructor to the decorated object + (instancetype)decoratorWithComponent:(AbPancake *)pancake; @end @implementation Decorator + (instancetype)decoratorWithComponent:(AbPancake *)pancake { Decorator *decorator = [[[self class] alloc] init]; decorator.pancake = pancake; return decorator; } - (void)cook { [self.pancake cook]; } @endCopy the code
- Defines a series of decorator objects
Implement a rule with a concrete decorator object to add an ingredient
@interface EggDecorator: Decorator @end - (void)cook {NSLog(@" add an egg "); [super cook]; }Copy the code
. The rest is written like the egg decorator on top
- Client use
// create a basic pancake object, which is also decorated AbPancake *ipancake = [[ipancake alloc] init]; AbPancake *egg = [EggDecorator decoratorWithComponent:ipancake]; AbPancake *ham = [HamDecorator decoratorWithComponent:egg]; [ham cook];Copy the code
And as you can see from the results,Invoking the corresponding decorators in turn to perform the business function is a recursive invocation
Pancakes as example, the different ingredients, in different decorator object, with the method of dynamic combination, to generate the client want, obviously is more flexible than increase subclasses to this way, because the decorative patterns of the point of origin is with the method of object composition, and then combined the added some functions, by the way. To achieve the effect of assembling layer by layer, the decorator pattern also requires that the decorator implement the same business interface as the decorator object so that it can be assembled in the same way.
Flexibility also comes in the form of dynamics, where all class instances have this capability if inherited, whereas decoration allows you to dynamically add functionality to several object instances rather than the entire class.
Pattern on
Model function
The ** decoration mode allows you to dynamically add functionality to an object, adding functionality to an object from outside of it, effectively changing the appearance of the object. ** When decorated, from the point of view of the external use system, you no longer use the original object, but instead use the object decorated with a series of decorators.
This makes it possible to change the function of an object flexibly. As long as the dynamically composed decorator is changed, the function of the resulting object is also changed.
Another benefit is the reuse of decorator functions. You can add the same decorator to an object many times, or decorate different objects with the same decorator. For example, in the example above, a customer wants two eggs
Object composition
As mentioned earlier, the functionality of a class can be extended either through inheritance or through more powerful and flexible object composition.
For example, if there is an object A that implements A method a1, and C1 wants to extend A’s functionality by adding A method c11 to it, then one solution is to inherit. The example code for A object is as follows:
public class A { public void a1(){ System.out.println("now in A.a1"); }}Copy the code
Example code for the C1 object is as follows:
public class C1 extends A{ public void c11(){ System.out.println("now in C1.c11"); }}Copy the code
Another solution is to use object composition. How? C c c c c c c c C C C C C C C C C C C c c c c c
Public class C2 {private A A = new A(); private A A = new A(); Public void a1(){// Call a.a1(); } public void c11(){ System.out.println("now in C2.c11"); }}Copy the code
Think about it, is there any functional processing you can do before and after the transition? Is object A transparent? Object composition is also simpler and more flexible:
Firstly, there can be selective reuse of functions. Not all functions of A can be reused, so it is ok to call fewer functions defined by A in C2.
Secondly, before and after the transfer, some function processing can be realized, and the A object is transparent, that is, the A object does not know when the A1 method processing was added function; As an added bonus, you can combine the functions of multiple objects. If you have another object B, and C2 wants to have the functions of B, you can simply add another method and call B.
A decorator
Decorator implements some decoration functions on the decorated object. You can call the function of the decorated object in the decorator and get the corresponding value. This is actually a recursive call
In the decorator, you can not only add functions to the decorated object, but also choose whether to call the function of the decorated object according to the need. If you do not call the function of the decorated object, it becomes completely reimplemented, equivalent to dynamically modifying the function of the decorated object
In addition, it is best to have completely independent functions between each decorator, without dependence, so that when decorating the combination, there is no order limit, that is, who should be decorated first and who should be decorated after the same, otherwise it will greatly reduce the flexibility of the combination of decorators.
The relationship between decorators and component classes
Decorators are used to decorate components. Decorators must implement the same interface as component classes, ensure that they are of the same type and have the same look and feel, so that combined decorators can be recursively called. The component class is unaware of the existence of a decorator. The decorator adds functionality to the component as a transparent wrapper, and the component class is unaware of it. What needs to change is where the component classes are used externally. Now you need to use the wrapped classes. The interfaces are the same, but the implementation classes have changed.
conclusion
There are several main points to note about the use of decorator patterns:
- The abstract decorator implements the same interface as the concrete object being decorated
- The abstract decorator holds interface objects for request passing
- The concrete decorator overrides the abstract decorator method and calls it with super, passing the request
The Demo address