This article, the fourth in the object-oriented design series, looks at seven of the more common behavioral patterns in design patterns (in this order) :

  • Template method pattern
  • The strategy pattern
  • Chain of Responsibility model
  • The state pattern
  • Command mode
  • Observer model
  • The mediator pattern

I. Template method pattern

define

In Template Method Pattern, the framework of an algorithm in an operation is defined, but the execution of some steps is deferred to a subclass so that the subclass can redefine certain steps of the algorithm without changing the structure of the algorithm.

Applicable scenario

Usually an algorithm requires several execution steps to implement, and sometimes we need to define several algorithms that perform the same steps, but may implement slightly different steps. That is to say, we need to implement the same step repeatedly, and we can flexibly extend more different algorithms through different implementations of a step.

In this scenario, we can use the template method pattern: define the framework of an algorithm, implement reusable algorithm steps in the parent class, and defer the task of extending and modifying other steps to the child class.

Now that we know where the template method pattern applies, let’s take a look at the members and class diagram of this pattern.

Members and class diagrams

Members of the

The members of the template method pattern have only two members besides the client:

  • Algorithm class: The Algorithm class is responsible for declaring Algorithm interface, Algorithm step interface. And implements a reusable algorithm step interface, and exposes interfaces that require subclass implementation.
  • Concrete Algorithm class: The Concrete Algorithm class is responsible for implementing the Algorithm step interface of the Algorithm class declaration.

Some references define these two members as Abstract Class and Concrete Class.

Let’s look at the relationship between the members of the command pattern using the class diagram:

Model class diagram

As you can see from the figure above, the Algorithm’s excute method is the Algorithm interface that internally invokes three step methods: step1,step2,step3. Step2 is not exposed externally because this step needs to be reused by subclasses. Therefore, the Algorithm only exposes step1 and step3 to be called by subclasses.

Code sample

Scenario overview

Simulate a scenario where you make three hot drinks: hot Americano, hot latte, and hot tea.

Scenario analysis

The three hot drinks are made in the same way, in three steps:

  • Step 1: Prepare hot water
  • Step 2: Add the main ingredients
  • Step 3: Add ingredients (or not, depending on the type of hot drink)

Although the steps are the same, different types of hot drinks may be different at each step: coffee and tea are made from ground coffee and tea leaves; Ingredients: Americano and tea can be dispensed with, while lattes need milk.

The first step is the same: prepare hot water.

According to the template method pattern described above, we can use the template method pattern in scenarios like this where the algorithm steps are the same and the implementation of the algorithm steps may be the same or different. Let’s take a look at simulating this scenario in code.

Code implementation

First we create the algorithm class HotDrink:

//================== HotDrink.h ==================

@interface HotDrink : NSObject

- (void)makingProcess;

- (void)addMainMaterial;

- (void)addIngredients;

@end



//================== HotDrink.m ==================

@implementation HotDrink

- (void)makingProcess{
    
    NSLog(@" ===== Begin to making %@ ===== ".NSStringFromClass([self class]));
    
    [self boilWater];
    [self addMainMaterial];
    [self addIngredients];
}


- (void)prepareHotWater{
    
    NSLog(@"prepare hot water");
}


- (void)addMainMaterial{
    
    NSLog(@"implemetation by subClasses");
}


- (void)addIngredients{
    
    NSLog(@"implemetation by subClasses");
}


@end
Copy the code

HotDrink exposes a makingProcess interface that internally calls all of the HotDrink making steps:

- (void)makingProcess{
         
     // Prepare hot water
    [self prepareHotWater];
    
    // Add the main component
    [self addMainMaterial];
    
    // Add auxiliary ingredients
    [self addIngredients];
}
Copy the code

HotDrink exposes only two interfaces of the three steps that subclasses need to implement in their own way:

// Add the main component
- (void)addMainMaterial;

// Add auxiliary ingredients
- (void)addIngredients;
Copy the code

Because the first step of the hot drink is the same (prepare the hot water), the interface of the first step is not exposed to the subclass, but is implemented directly in the current class, which is a reusable code advantage of the template method.

OK, now that we have created the algorithm class, we can then create three concrete algorithm classes according to the requirements above:

  • HotDrinkTeaHot tea:
  • HotDrinkLatte: hot latte
  • HotDrinkAmericano: hot American
//================== HotDrinkTea.h ==================

@interface HotDrinkTea : HotDrink

@end



//================== HotDrinkTea.m ==================

@implementation HotDrinkTea


- (void)addMainMaterial{
    
    NSLog(@"add tea leaf");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}


@end
Copy the code

Hot tea adds tea in the addMainMaterial step, and nothing is done in the addIngredients step (assuming pure tea).

Similarly, let’s look at two implementations of hot coffee. First up, the HotDrinkLatte:

//================== HotDrinkLatte.h ==================

@interface HotDrinkLatte : HotDrink

@end



//================== HotDrinkLatte.m ==================

@implementation HotDrinkLatte

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add milk");
}


@end
Copy the code

The hot latte is made with ground coffee in the addMainMaterial step and milk in the addIngredients step.

Here’s a look at the HotDrinkAmericano:

//================== HotDrinkAmericano.h ==================

@interface HotDrinkAmericano : HotDrink

@end



//================== HotDrinkAmericano.m ==================

@implementation HotDrinkAmericano

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}

@end
Copy the code

In the addMainMaterial step, hot Americano adds coffee powder. In the addIngredients step, nothing is done because Americano is pure coffee. In theory, you don’t need to add anything except water and coffee.

Now that three hot drinks have been created, let’s make them separately and take a look at the daily output:

===== Begin to making HotDrinkTea =====
prepare hot water
add tea leaf
add nothing
===== Begin to making HotDrinkLatte =====
prepare hot water
add ground coffee
add milk
===== Begin to making HotDrinkAmericano =====
prepare hot water
add ground coffee
add nothing
Copy the code

The day to day output above accurately reflects the process we defined for making these three hot drinks:

  • Hot tea: Prepare hot water + tea leaves
  • Hot latte: Prepare hot water + coffee + milk
  • Hot American: Prepare hot water and coffee

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  • High reusability: Put the same code in the parent class, while different parts are implemented by subclasses
  • High scalability: Different algorithms can be extended by creating different subclasses
  • Conforming to the open-close principle: mutable and immutable parts are separated, and different mutable parts (subclasses) are separated from each other, so conforming to the open-close principle

disadvantages

  • Increase the number of classes: Each algorithm implementation requires a subclass, and too many implementations will increase the number of classes
  • Disadvantages caused by inheritance: If the parent class needs to increase or decrease its behavior, all subclasses need to change it simultaneously

Apps in iOS SDK and JDK

  • In the iOS SDK, we can rewrite itUIViewthedrawRect:Method can be customized drawing, is a practice of the template method pattern.
  • In JDK,java.lang.RunnableThis is a classic scenario using the JDK:RunnableInterfaces can serve as abstract commands, while threads implementing Runnable are concrete commands.

Second, strategy mode

define

Strategy Pattern: Define a series of algorithms, encapsulate each algorithm, and make them interchangeable.

Applicable scenario

Sometimes there may be multiple schemes to achieve a certain function: we need to make the system dynamic and flexible replacement schemes; It also makes it easy for developers to add new solutions or remove old ones.

If we hardcode all solutions in the same class, we will change the original class when we modify, add, or delete a solution in the future, which is against the open closed principle.

In fact, we can define separate classes to encapsulate different solutions, each class encapsulates a specific solution, and these different solutions are what we call policies. And we can use an abstract policy class to ensure the consistency of these policies, which is the design scheme of the policy pattern.

Now that we know where the policy pattern applies, let’s look at the members and class diagram of the policy pattern.

Members and class diagrams

Members of the

The policy pattern has three members besides the client:

  • Context: An environment class holds an instance of a specific policy class. This instance is the current policy and can be used by clients
  • Abstract Policy Class (Strategy) : The abstract policy class declares the interface that the concrete policy class needs to implement, and this interface is also provided to the client to call
  • Concrete Strategy class: Concrete policy class implements the interface declared by abstract policy class. Each Concrete policy class has its own unique implementation mode, that is, represents different policies

Let’s look at the relationship between the members through the class diagram.

Model class diagram

Code sample

Scenario overview

Simulate a scenario where two integers can be arbitrarily substituted for the addition, subtraction, multiplication and division algorithm.

Scenario analysis

In this scenario, the two integer parameters passed in are unchanged, but the specific operation of the two integers can be switched flexibly. Then we can use the policy pattern: wrap each operation (algorithm), and update the specific policy instance held by the Context class when replacement is needed.

Code implementation

First, we define the abstract policy class and the concrete policy class:

Because we operate on two integers, in the abstract policy class we only need to define an interface that passes in two integers.

Abstract policy class TwoIntOperation:

//================== TwoIntOperation.h ==================

@interface TwoIntOperation : NSObject

- (int)operationOfInt1:(int)int1 int2:(int)int2;

@end



//================== TwoIntOperation.m ==================

@implementation  TwoIntOperation

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    //implenting by sub classes;
    return 0;
}

@end
Copy the code

Then we define four specific strategy classes according to the four operations of addition, subtraction, multiplication and division:

Addition TwoIntOperationAdd:

//================== TwoIntOperationAdd.h ==================

@interface TwoIntOperationAdd : TwoIntOperation

@end



//================== TwoIntOperationAdd.m ==================

@implementation TwoIntOperationAdd

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== adding ====");
    
    return int1 + int2;
}

@end
Copy the code

Subtraction TwoIntOperationSubstract:

//================== TwoIntOperationSubstract.h ==================

@interface TwoIntOperationSubstract : TwoIntOperation

@end



//================== TwoIntOperationSubstract.m ==================

@implementation TwoIntOperationSubstract

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== Substract ====");
    return int1 - int2;
}
@end
Copy the code

Multiplication TwoIntOperationMultiply:

//================== TwoIntOperationMultiply.h ==================

@interface TwoIntOperationMultiply : TwoIntOperation

@end



//================== TwoIntOperationMultiply.m ==================

@implementation TwoIntOperationMultiply

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== multiply ====");
    
    return int1 * int2;
}

@end
Copy the code

Division TwoIntOperationDivision:

//================== TwoIntOperationDivision.h ==================

@interface TwoIntOperationDivision : TwoIntOperation

@end



//================== TwoIntOperationDivision.m ==================

@implementation TwoIntOperationDivision

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== division ====");
    return int1/int2;
}

@end
Copy the code

Now that the algorithm classes are declared, let’s finally declare the Context class:

//================== Context.h ==================

@interface Context : NSObject

- (instancetype)initWithOperation: (TwoIntOperation *)operation;

- (void)setOperation:(TwoIntOperation *)operation;

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2;

@end



//================== Context.m ==================

@implementation Context
{
    TwoIntOperation *_operation;
}

- (instancetype)initWithOperation: (TwoIntOperation *)operation{

    self = [super init];
    if (self) {
        //injection from instane initialization
        _operation = operation;
    }
    return self;
}

- (void)setOperation:(TwoIntOperation *)operation{
    
    //injection from setting method
    _operation = operation;
}

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2{
    
    //return value by constract strategy instane
    return [_operation operationOfInt1:int1 int2:int2];
}

@end
Copy the code

The Context class injects a concrete policy instance into the constructor (init method) and holds it, and the Context also provides a set method for external injection of an instance of the concrete policy class.

Strategy implementation is through the Context interface excuteOperationOfInt1: int2. This interface is provided for client calls; And inside it it actually calls the policy execution method of the currently held policy instance.

So if you want to use either policy, you just pass an instance of the specific policy into the Context instance.

Now that all classes are defined, let’s see how to use them:

int int1 = 6;
int int2 = 3;
    
NSLog(@"int1: %d int2: %d",int1,int2);
    
//Firstly, using add operation
TwoIntOperationAdd *addOperation = [[TwoIntOperationAdd alloc] init];
Context *ct = [[Context alloc] initWithOperation:addOperation];
int res1 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of adding : %d",res1);
    
//Changing to multiple operation
TwoIntOperationMultiply *multiplyOperation = [[TwoIntOperationMultiply alloc] init];
[ct setOperation:multiplyOperation];
int res2 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of multiplying : %d",res2);
    
    
//Changing to substraction operation
TwoIntOperationSubstract *subOperation = [[TwoIntOperationSubstract alloc] init];
[ct setOperation:subOperation];
int res3 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of substracting : %d",res3);
    
    
//Changing to division operation
TwoIntOperationDivision *divisionOperation = [[TwoIntOperationDivision alloc] init];
[ct setOperation:divisionOperation];
int res4 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of dividing : %d",res4);
Copy the code

Take a look at the daily output:

[13431:1238320] int1: 6    int2: 3
[13431:1238320] ==== adding ====
[13431:1238320] result of adding : 9
[13431:1238320] ==== multiply ====
[13431:1238320] result of multiplying : 18
[13431:1238320] ==== Substract ====
[13431:1238320] result of substracting : 3
[13431:1238320] ==== division ====
[13431:1238320] result dividing : 2
Copy the code

In the above example, we want to use addition first, so we instantiate the addition policy class and pass it into the constructor of the Context class.

The subsequent multiplication, subtraction, and division replacements pass their policy instances into the Context’s set method and execute them.

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  • In policy mode, users can select and change algorithms without modifying the original system
  • Avoid using multiple conditional judgments
  • Flexibility to add new algorithms or behaviors
  • Improved algorithm and policy security: The concrete implementation of the policy can be encapsulated so that the caller only needs to know the difference between different policies

disadvantages

  • The client must be aware of all the specific policy classes currently available and needs to decide for itself which policy class to use
  • If there are too many options, the number of policy classes will increase.

Apps in iOS SDK and JDK

  • In the JDKComparatorIs an implementation of the policy pattern, which can use different subclasses, i.e. concrete policies, to address different requirements.

3. Responsibility chain model

define

Chain of Responsibility Pattern: A Chain of recipient objects is created for the request, with each recipient containing a reference to another recipient. If an object cannot handle the request, it passes the same request to the next recipient, and so on.

Applicable scenario

When processing a request, the resolution strategy varies depending on the condition. In this case, instead of using if-else to distinguish different conditions and corresponding resolution strategies, we can use the chain of responsibility pattern to encapsulate different conditions and corresponding resolution strategies into a class, namely different handlers. These handlers are then formed into a chain of responsibilities, passing the request to the next handler if the current handler is unable to process or does not meet the current criteria.

Now that we know where the chain of responsibility pattern applies, let’s look at the members and class diagram of the chain of responsibility pattern.

Members and class diagrams

Members of the

The structure of responsibility chain mode is relatively simple, with only two members excluding the client:

  • Handler: The Handler defines the interface to process the request
  • Concrete Handler: The Concrete Handler implements the interface declared by the Handler and is responsible for processing the request

Model class diagram

Code sample

Scenario overview

Simulate an ATM cash withdrawal scene: ATM machine has 50,20,10 denominations of paper money, according to the cash amount that the user needs to withdraw to output the least amount of paper money equivalent to the amount of paper money.

For example, the user needs to withdraw 130 yuan, then the ATM needs to output two 50 notes, one 20 notes, one 10 notes; Instead of six 20 bills and one 10.

Scenario analysis

Apparently, ATM starts with the largest denomination in order to produce the minimum number of bills.

If we don’t use the chain of responsibility model, we might write a do-while loop in which we do if-else checks based on the denomination of the bill, and keep trying until the denomination is all divided. However, if the value of the denomination changes in the future, or if a new denomination is added, we also need to change the judgment criteria or add an if-else statement, which clearly violates the open and close principle.

However, if we use the chain of responsibility mode, we treat each denomination of paper money as a handler (node, node) in the chain of responsibility, which is a class of its own and processed separately. Then connect these processors in order (50,20,10) and process the values entered by the user in order.

The advantage of this is that if we later change the value or add a new value, we simply need to modify one of the handlers or create a new handler class and reinsert it in the appropriate place in the responsibility chain.

Let’s take a look at simulating this scenario in code.

Code implementation

First create the abstract handler DispenseChainNode:

//================== DispenseChainNode.h ==================

@interface DispenseChainNode : NSObject <DispenseProtocol>
{
    @protected DispenseChainNode *_nextChainUnit;
}

- (void)setNextChainUnit:(DispenseChainNode *)chainUnit;

@end



//================== DispenseChainNode.m ==================

@implementation DispenseChainNode

- (void)setNextChainNode:(DispenseChainNode *)chainNode{
    
    _nextChainNode = chainNode;
}

- (void)dispense:(int)amount{
    
    return;
}

@end
Copy the code
  • DispenseChainNodeIs the responsibility chain node, the parent of the concrete handler, which holdsDispenseChainNodeTo hold the next node of the current node. This instance of the next node is passedsetNextChainNode:Method injected in and,DispenseChainNodefollow<DispenseProtocol>Protocol. There’s only one way to do this protocol, which isdispense:Method, which each node implements to process the amount entered. (Dispense means to distribute, distribute)

Now we create a concrete handler for denominations 50, 20, and 10:

For 50 denominations:

//================== DispenseChainNodeFor50Yuan.h ==================

@interface DispenseChainNodeFor50Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor50Yuan.m ==================

@implementation DispenseChainNodeFor50Yuan

- (void)dispense:(int)amount{
    
    int unit = 50;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if(remainder ! =0) { [_nextChainNode dispense:remainder]; }}else{ [_nextChainNode dispense:amount]; }}@end
Copy the code

For 20 denominations:

//================== DispenseChainNodeFor20Yuan.h ==================

@interface DispenseChainNodeFor20Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor20Yuan.m ==================

@implementation DispenseChainNodeFor20Yuan

- (void)dispense:(int)amount{
    
    int unit = 20;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if(remainder ! =0) { [_nextChainNode dispense:remainder]; }}else{ [_nextChainNode dispense:amount]; }}@end
Copy the code

10 denomination specific handler:

//================== DispenseChainNodeFor10Yuan.h ==================

@interface DispenseChainNodeFor10Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor10Yuan.m ==================

@implementation DispenseChainNodeFor10Yuan

- (void)dispense:(int)amount{
    
    int unit = 10;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if(remainder ! =0) { [_nextChainNode dispense:remainder]; }}else{ [_nextChainNode dispense:amount]; }}@end
Copy the code

The handling of the three dispense: methods is similar:

First check to see if the current value is greater than the face value

  • If it is larger than face value
    • Divide the current value by the current denomination
      • If there is no remainder, it stops without processing
      • If there is a remainder, the current value is passed on to the next concrete handler (the next node in the chain of responsibility)
  • If less than the face value: Pass the current value to the next concrete handler (the next node in the chain of responsibility)

Now that we have created three concrete handlers, let’s create an ATM class to string these nodes together:

//================== ATMDispenseChain.h ==================

@interface ATMDispenseChain : NSObject<DispenseProtocol>

@end



//================== ATMDispenseChain.m ==================

@implementation ATMDispenseChain
{
    DispenseChainNode *_chainNode;
}

- (instancetype)init{
    
    self = [super init];
    if(self){
        
        DispenseChainNodeFor50Yuan *chainNode50 = [[DispenseChainNodeFor50Yuan alloc] init];
        DispenseChainNodeFor20Yuan *chainNode20 = [[DispenseChainNodeFor20Yuan alloc] init]; 
        DispenseChainNodeFor10Yuan *chainNode10 = [[DispenseChainNodeFor10Yuan alloc] init];
        
         _chainNode = chainNode50;
        [_chainNode setNextChainNode:chainNode20];
        [chainNode20 setNextChainNode:chainNode10];
        
    }
    
    return self;
    
}



- (void)dispense:(int)amount{
    
    NSLog(@ "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
    NSLog(@"ATM start dispensing of amount:%d",amount);
    
    if (amount %10! =0) {
        NSLog(@"Amount should be in multiple of 10");
        return;
    }

    [_chainNode dispense:amount];
    
}

@end
Copy the code

The ATMDispenseChain class initializers connect three dispensechains in order of 50, 20, and 10 and hold a pointer to the current DispenseChainNode (the first node in the responsibility chain, the DispenseChainNode of value 50). Because denominations start at 50).

OK, now that we have all three concrete handlers wrapped, we can see how to use them:

ATMDispenseChain *atm = [[ATMDispenseChain alloc] init];
    
[atm dispense:230];
    
[atm dispense:70];
    
[atm dispense:40];
    
[atm dispense:10];

[atm dispense:8];
Copy the code

After creating an instance of ATMDispenseChain, pass in some values to see the result:

================================== ATM start dispensing of amount:230 Dispensing 4 of 50 Dispensing 1 of 20 Dispensing 1  of 10 ================================== ATM start dispensing of amount:70 Dispensing 1 of 50 Dispensing 1 of 20 ================================== ATM start dispensing of amount:40 Dispensing 2 of 20 ================================== ATM start dispensing of amount:10 Dispensing 1 of 10 ================================== ATM start dispensing of amount:8 Amount should bein multiple of 10
Copy the code

As you can see from the log output, our chain of responsibility handling was fine, and the ATMDispenseChain instance produced the most correct result for each different value.

It is important to note that the Responsibility chain class (ATMDispenseChain) in this code example is not a member of the responsibility chain pattern described above. But there’s no need to get too tangled up here, we’re just doing a little more business here. Instead of encapsulating these nodes, you could simply call setNextChainNode one at a time: the setNextChainNode method to assemble the chain of responsibility and hand the task off to the first handler.

Now that the requirements are complete, can we do a refactoring?

We go back and look at the three specific handlers in dispense: the handling of the method is very similar, the only difference being the denomination values handled: we actually created classes for these three values and hard-coded them (50,20,10). This is a disadvantage because if the denomination size changes, or if the denomination is increased or decreased, we modify the classes or add or remove the classes (even though this is better than if-else without the chain of responsibility pattern).

So instead of creating concrete classes that are hardcoded with values, we can inject values directly into the constructor at initialization! In this way, we can adjust and change denominations at will. Let’s do this refactoring:

First delete the three specific handlers DispenseChainNodeFor50Yuan, DispenseChainNodeFor20Yuan, DispenseChainNodeFor10Yuan.

DispenseChainNode then adds the initialization method passed in the denomination values and the member variables for the values:

//================== ADispenseChainNode.h ==================

@interface DispenseChainNode : NSObject <DispenseProtocol>
{
    @protected DispenseChainNode *_nextChainNode;
    @protected int _dispenseValue;
}

- (instancetype)initWithDispenseValue:(int)dispenseValue;

- (void)setNextChainNode:(DispenseChainNode *)chainNode;


@end



//================== ADispenseChainNode.m ==================

@implementation DispenseChainNode

- (instancetype)initWithDispenseValue:(int)dispenseValue
{
    self = [super init];
    if (self) {
        _dispenseValue = dispenseValue;
    }
    return self;
}

- (void)setNextChainNode:(DispenseChainNode *)chainNode{
    
    _nextChainNode = chainNode;
}

- (void)dispense:(int)amount{
    
    if (amount >= _dispenseValue) {
        
        int count = amount/_dispenseValue;
        int remainder = amount % _dispenseValue;
        
        NSLog(@"Dispensing %d of %d",count,_dispenseValue);
        
        if(remainder ! =0) { [_nextChainNode dispense:remainder]; }}else{ [_nextChainNode dispense:amount]; }}@end
Copy the code

After adding the initWithDispenseValue: method to DispenseChainNode, we can generate specific handlers of different denominations as needed.

And then let’s think about what changes we can make to the previous ATMDispenseChain?

Since DispenseChainNode can generate instances of specific handlers that handle different denominations based on different values, wouldn’t it be possible to add an initialization method that injects an array of denominations into the ATMDispenseChain class that concatenates multiple specific handlers? For example, input [50,20,10] array can generate 50,20,10 denomination of the specific handler; And arrays are ordered, so the order of the elements passed into the array can be the order of the nodes in the chain of responsibility.

With that in mind, let’s look at the implementation:

//================== ATMDispenseChain.m ==================

@implementation ATMDispenseChain
{
    DispenseChainNode *_firstChainNode;
    DispenseChainNode *_finalChainNode;
    int _minimumValue;
}


- (instancetype)initWithDispenseNodeValues:(NSArray *)nodeValues{
    
    self = [super init];
    
    if(self) {NSUInteger length = [nodeValues count];
        
        [nodeValues enumerateObjectsUsingBlock:^(NSNumber * nodeValue, NSUInteger idx, BOOL * _Nonnull stop) {
            
            DispenseChainNode *iterNode = [[DispenseChainNode alloc] initWithDispenseValue:[nodeValue intValue]];
            
            if (idx == length - 1 ) {
                _minimumValue = [nodeValue intValue];
            }
            
            if (!self->_firstChainNode) {
                
                 //because this chain is empty, so the first node and the final node will refer the same node instance
                 self->_firstChainNode =  iterNode;
                 self->_finalChainNode =  self->_firstChainNode;
                
            }else{
                
                //appending the next node, and setting the new final node
                [self->_finalChainNode setNextChainNode:iterNode];
                 self->_finalChainNode = iterNode; }}]; }return self;
}


- (void)dispense:(int)amount{
    
    NSLog(@ "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
    NSLog(@"ATM start dispensing of amount:%d",amount);
    
    if(amount % _minimumValue ! =0) {
        NSLog(@"Amount should be in multiple of %d",_minimumValue);
        return;
    }

    [ _firstChainNode dispense:amount];
    
}

@end
Copy the code

The refactored ATMDispenseChain class added initWithDispenseNodeValues: method, value would need to be introduced from outside the array. In this method the entire chain of responsibilities is constructed from the array passed in.

In the dispense: method, denominations are processed from the first node in the chain of responsibility and the smallest denomination value is taken at the front of the method to do the boundary processing.

OK, now that the handler and responsibility chain classes are created, let’s look at how to use them:

NSArray *dispenseNodeValues = @[@(100), @ (50), @ (20), @ (10)];

ATMDispenseChain *atm = [[ATMDispenseChain alloc] initWithDispenseNodeValues:dispenseNodeValues];
    
[atm dispense:230];
    
[atm dispense:70];
    
[atm dispense:40];
    
[atm dispense:10];
    
[atm dispense:8];
Copy the code

Doesn’t it feel cleaner? We simply pass in an array of denomination values to construct the entire responsibility chain and use it directly. Take a look at the daily output:

==================================
ATM start dispensing of amount:230
Dispensing 2 of 100
Dispensing 1 of 20
Dispensing 1 of 10
==================================
ATM start dispensing of amount:70
Dispensing 1 of 50
Dispensing 1 of 20
==================================
ATM start dispensing of amount:40
Dispensing 2 of 20
==================================
ATM start dispensing of amount:10
Dispensing 1 of 10
==================================
ATM start dispensing of amount:8
Amount should be in multiple of 10
Copy the code

From the output results of the log, there is no problem with our reconstituted responsibility chain scheme.

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

Refactoring:

After the refactoring:

advantages

  • Separation of responsibilities between processors, processors just need to deal with their own logic
  • It is easy to modify the processing logic of each handler, to delete or add handlers, or to change the order of handlers in the chain of responsibilities.

disadvantages

  • The need to pass responsibility up the responsibility chain until a suitable object is found to handle it can cause delays in processing. Therefore, it is not suitable to use the responsibility chain mode when the delay is not allowed to be too high.

Apps in iOS SDK and JDK

  • The responder chain in the iOS SDK is an exercise in the chain of responsibility pattern: if the current view cannot respond, it is passed on to the next level view.
  • servletIn theFilterCan formFilterChainIs a practice of responsibility chain mode.

4. State mode

define

State Pattern: Allows an object to change its behavior when its internal State changes.

Applicable scenario

An object has multiple states, which behave differently and can be converted from one state to another.

If we use if else to determine the state of an object, the code will contain a large number of conditional statements related to the state of the object, and it will be difficult to add, delete, and change these states. If you use state mode. By splitting the state objects into different classes, you can eliminate if… Else conditional selection statements.

Now that we know where the state pattern applies, let’s look at the members and class diagram of the state pattern.

Members and class diagrams

Members of the

The state mode has only four members:

  • Context: Context classes refer to instances of specific states. The concrete state held by the environment class is the current state, and you can inject a state instance into the environment class using the set method.
  • Abstract State Classes: Abstract State classes declare the interfaces that concrete State classes need to implement.
  • Concrete State: The Concrete State class implements the interface to the abstract State class declaration.

The following is a look at the relationship between members through the class diagram:

Model class diagram

Code sample

Scenario overview

Simulate a day in the life of a programmer, who has four states:

  1. awake
  2. In the bed
  3. Write the code
  4. Have a meal,

Look at these states should be a very love to write code programmer ^ ^

Scenario analysis

The programmer has four states, but some states cannot be switched: for example, he cannot switch from sleeping to writing code (because he needs to switch to being awake before he can write code); You can’t switch from eating to being awake, because you’re already awake.

If we don’t use state mode, we might write a lot of if-else judgments when switching states, and as states increase, these branches become more difficult to maintain.

If we use the state pattern, each state can be encapsulated in a class for easy management. It’s also handy to add or subtract states.

Let’s take a look at simulating this scenario in code.

Code implementation

First we define the status class:

//================== State.h ==================

@interface State : NSObject<ActionProtocol>
{
    @protected Coder *_coder;
}

- (instancetype)initWithCoder:(Coder *)coder;

@end



//================== State.m ==================

@implementation State

- (instancetype)initWithCoder:(Coder *)coder{
    
    self = [super init];
    if (self) {
        _coder = coder;
    }
    return self;
}

@end
Copy the code

The state class holds an instance of the coder, the programmer, and follows the ActionProtocol:

//================== ActionProtocol.h ==================

@protocol ActionProtocol <NSObject>

@optional;

- (void)wakeUp;

- (void)fallAsleep;

- (void)startCoding;

- (void)startEating;

@end
Copy the code

ActionProtocol defines the actions of the programmer that are the daily activities of the programmer and trigger the State switch, so State also needs to follow this protocol because its subclasses need to implement these actions.

Let’s look at the subclasses of State. According to the four states mentioned above, we define the following four subclasses of State:

StateAwake:

//================== StateAwake.h ==================

@interface StateAwake : State

@end

@implementation StateAwake

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}

- (void)startCoding{
    
    NSLog(@"Change state from awake to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Change state from awake to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Change state from awake to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
Copy the code

StateSleeping:

//================== StateSleeping.h ==================

@interface StateSleeping : State

@end



//================== StateSleeping.m ==================

@implementation StateSleeping

- (void)wakeUp{
    
    NSLog(@"Change state from sleeping to awake");
    [_coder setState:(State *)[_coder stateAwake]];
}


- (void)startCoding{
    
    NSLog(@"Already sleeping, can not change state to coding");
}

- (void)startEating{
    
    NSLog(@"Already sleeping, can change state to eating");
}


- (void)fallAsleep{
    
    NSLog(@"Already sleeping, can not change state to sleeping again");
}

@end
Copy the code

StateEating:

//================== StateEating.h ==================

@interface StateEating : State

@end



//================== StateEating.m ==================

@implementation StateEating

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"New idea came out! change state from eating to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Already eating, can not change state to eating again");
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from eating to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}



@end
Copy the code

“StateCoding”:

//================== StateCoding.h ==================

@interface StateCoding : State

@end



//================== StateCoding.m ==================

@implementation StateCoding

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"Already coding, can not change state to coding again");
}

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from coding to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
Copy the code

As you can see from the above class, some transitions between states are invalid and some are possible. For example, switching between the same state is invalid; Sleeping does not switch to coding, but the other way around, because you might fall asleep when you get tired of writing code.

Let’s look at the implementation of the programmer class:

//================== Coder.h ==================

@interface Coder : NSObject<ActionProtocol>

@property (nonatomic.strong) StateAwake *stateAwake;
@property (nonatomic.strong) StateCoding *stateCoding;
@property (nonatomic.strong) StateEating *stateEating;
@property (nonatomic.strong) StateSleeping *stateSleeping;

- (void)setState:(State *)state;

@end



//================== Coder.m ==================

@implementation Coder
{
    State *_currentState;
}

- (instancetype)init{
    
    self = [super init];
    if (self) {
        
        _stateAwake = [[StateAwake alloc] initWithCoder:self];
        _stateCoding = [[StateCoding alloc] initWithCoder:self];
        _stateEating = [[StateEating alloc] initWithCoder:self];
        _stateSleeping = [[StateSleeping alloc] initWithCoder:self];
        
        _currentState = _stateAwake;
    }
    return self;
}

- (void)setState:(State *)state{
    
    _currentState = state;
}

- (void)wakeUp{
    
    [_currentState wakeUp];
}

- (void)startCoding{
    
    [_currentState startCoding];
}

- (void)startEating{
    
    [_currentState startEating];
}


- (void)fallAsleep{
    
    [_currentState fallAsleep];
}

@end
Copy the code

As we can see from the code above, the programmer class holds an instance of the current state, which defaults to awake after initialization, and provides a setState: method to switch the state. And in the initialization method, we instantiate all the states, which are intended to be used when switching states. See the specific state class method:

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}
Copy the code

[_coder stateEating] [_coder stateEating] [_coder stateEating] [_coder stateEating] [_coder stateEating] [_coder stateEating]

Finally, in the programmer’s action method, it actually calls the method corresponding to the current state (which is why both the programmer class and the state class follow the ActionProtocol).

So, our state classes, state subclasses, programmer classes are all declared. Let’s see how it works:

Coder *coder = [[Coder alloc] init];
    
//change to awake.. failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to coding
[coder startCoding];//Change state from awake to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
    
//change to eat... failed
[coder startEating];//Already sleeping, can change state to eating
    
//change to wake up
[coder wakeUp];//Change state from sleeping to awake

//change wake up... failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to eating
[coder startEating];//Change state from awake to eating
    
//change to coding
[coder startCoding];//New idea came out! change state from eating to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
Copy the code

In the above code, we instantiate a programmer class and then repeatedly call methods that trigger state changes. We have commented out the day to day output of each state switch to the right side of the code. We can see that some state switches are not allowed:

  • Like the first one from the top to the bottom[coder wakeUp]Because the programmer object is initialized by defaultawakeState, so you cannot switch to the same state
  • Like the first one from the top to the bottom[coder startEating]: There is no way to switch directly while sleepingeatingState; After wake, execute[coder startEating]It worked.

As you can see from the above example, there is no need to write if-else to use state mode, and if you want to add a state in the future, you just need to create another state subclass, add processing for all states to the new state subclass, and add processing for the new state to the previous state subclass. Even if we modify the state subclass we defined earlier, it’s still a lot more convenient than using a huge if-else.

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  1. The conversion logic of various states is distributed to different classes to reduce mutual dependence.

disadvantages

  1. Adding a new state class requires modifying the source code for the state transition, and adding a new behavior also requires modifying the original state class (if the new behavior is related to the original state).
  2. Too many states can increase the number of classes in the system and increase the complexity of the system.

Apps in iOS SDK and JDK

  • Javax.mail packageLifyCycleIs an implementation of the state pattern

5. Command mode

define

Command Pattern: Commands (or requests) are encapsulated as objects. The client passes the command (or request) object to the calling object first. The call object passes the command (or request) object to an appropriate object that can handle the command (or request) for processing.

By definition, in command mode, commands are encapsulated as objects, and the calling object is separated between the client that sends the command and the receiver that processes the command. What are the reasons for this design or the applicable scenarios?

Applicable scenario

In some scenarios, the processing of a task may not need to be performed immediately: it may need to be logged (day arrival), revoked, or retried (network request). In these scenarios, if the requester and the performer of the task are tightly coupled, it is possible to mix a lot of other code that executes the policy with code that executes immediately.

These other execution policies, we temporarily call control and management policies, and if we want to control and manage requests, we need to:

  1. Abstract out the request
  2. Let another role be responsible for controlling and managing the requested tasks

So the command mode is tailored for this scenario, and it works by:

  1. Encapsulate the request as an object
  2. Use the caller to “intercept” between the client and the request handler to facilitate control and management of the request object.

Now that we know where the command pattern applies, let’s look at the members and class diagram of the command pattern.

Members and class diagrams

Members of the

Not including the initiator of the request (client), the command mode has four members:

  • Abstract Command: The Command class is responsible for declaring the interface of the Command.
  • Concrete Command: The Concrete Command class is responsible for implementing the interface of the abstract Command class declaration
  • Invoker: The caller is responsible for passing an instance of a specific command class to the recipient
  • Receiver: The Receiver is responsible for handling commands

Let’s look at the relationship between the members of the command pattern using the class diagram:

Model class diagram

Code sample

Scenario overview

Simulate an example of turning lights on and off using a remote control.

Scenario analysis

In this example, the person using the remote control is the client who initiates the command to turn the lights on or off to the remote control (caller). The caller then passes the command to the receiver (lamp).

Here, people are not in direct contact with the lamp, and the command to turn on and off is forwarded through the remote control, and finally transmitted to the lamp for execution.

Let’s take a look at simulating this scenario in code.

Code implementation

First we create the receiver, the lamp class:

//================== Light.h ==================

@interface Light : NSObject

- (void)lightOn;

- (void)lightOff;

@end



//================== Light.m ==================

@implementation Light


- (void)lightOn{
    
    NSLog(@"Light on");
}


- (void)lightOff{
    
    NSLog(@"Light off");
}

@end
Copy the code

The lamp class declares and implements two interfaces, the on and off interfaces, to allow the external to perform on and off operations.

Next we create the abstract command class and the concrete command class:

Abstract command class:

//================== Command.h ==================

@interface Command : NSObject

- (void)excute;

@end



//================== Command.m ==================

@implementation Command

@end
Copy the code

The abstract command class declares an interface for executing commands, excute, which is implemented by its subclass, the concrete command class.

Since there are only two commands for turning lights on and off, we create two concrete command classes to inherit the above abstract command class:

Command CommandLightOn:

//================== CommandLightOn.h ==================

@interface CommandLightOn : Command

- (instancetype)initWithLight:(Light *)light;

@end


//================== CommandLightOn.m ==================

@implementation CommandLightOn
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOn];
}
Copy the code

CommandLightOff:

//================== CommandLightOff.h ==================

@interface CommandLightOff : Command

- (instancetype)initWithLight:(Light *)light;

@end



//================== CommandLightOff.m ==================
@implementation CommandLightOff
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOff];
}
Copy the code

We can see that these two concrete command classes each implement the Excute interface declared by their parent class in their own way.

RemoteControl = RemoteControl; RemoteControl = RemoteControl;

//================== RemoteControl.h ==================

@interface RemoteControl : NSObject

- (void)setCommand:(Command *)command;

- (void)pressButton;

@end



//================== RemoteControl.m ==================

@implementation RemoteControl
{
    Command *_command;
}


- (void)setCommand:(Command *)command{
    
    _command = command;
}

- (void)pressButton{
    
    [_command excute];
}

@end
Copy the code

The remote control class uses the set method to inject the concrete command class and provides the pressButton method externally to internally call the excute method of the passed concrete command class.

Finally, let’s look at how the client works with these classes:

//================== client ==================

//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
    
//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];
    
//excute command (light on command)
[rm pressButton];
    

//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];

//change to off command
[rm setCommand:cf];

//excute Command (light close command)
[rm pressButton];
Copy the code

Take a look at the daily output:

[11851:1190777] Light on
[11851:1190777] Light off
Copy the code

As you can see from the code above, we first prepare the instance of the specific command class, then pass it to the remote control class, and finally trigger the pressButton method of the remote control to indirectly trigger the corresponding operation on the Light object.

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  • The initiator of a command and the executor of a command are separated to reduce the coupling degree of the system
  • Facilitate batch processing of commands, such as the implementation of day to day queues; Easy to undo or retry commands, such as network requests

disadvantages

  • You need to create a command object for each command. If there are too many commands in the system, a large number of command classes exist in the system, which increases the system complexity.

Apps in iOS SDK and JDK

  • In JDK,java.lang.RunnableThis is the classic scenario using the command pattern, where the Runnable interface serves as an abstract command, and the thread implementing the Runnable is the concrete command.

The observer model

define

Observer Pattern: Defines a one-to-many dependency relationship between objects, so that whenever the state of an object changes, its dependent objects can be notified and processed accordingly.

Applicable scenario

The observer pattern can be used in any scenario involving one-to-one or one-to-many object interactions. In general, we use the observer pattern to implement requirements that changes to one object cause changes to one or more other objects, such as peels, listening to the offset of list scrolling, and so on.

Now that we know where the Observer pattern applies, let’s look at the observer pattern’s members and class diagram.

Members and class diagrams

Members of the

The Observer mode has four members:

  • Target (Subject) : The target is the role being observed, declares the interface for adding and removing observers and notifying observers.
  • Concrete Subject: An interface that implements a target class declaration and holds instances of all observers (in the form of collections). Notifications are sent to all registered observers when the observed state changes.
  • Observer: An Observer defines an update interface for a specific Observer to perform some action when notified.
  • Concrete Observer: Concrete Observer implements an update interface to the abstract Observer definition.

The following is a look at the relationship between members through the class diagram:

Model class diagram

Code sample

Scenario overview

Simulate a scenario where a client (investor) subscribes to a financial adviser’s advice to buy stocks at different prices. When the price information changes, all customers will be notified (SMS, email, etc.), and the customer will then view the latest data and take action.

Scenario analysis

A financial adviser may serve multiple clients, and information needs to be communicated to each client in a timely manner. After receiving these messages, customers need to take corresponding measures. In this one-to-many notification scenario, we can use the Observer pattern: the financial adviser is the Subject being observed, and the TA’s client is the Observer.

Let’s take a look at simulating this scenario in code.

Code implementation

First we define an Observer Observer:

//================== Observer.h ==================

@interface Observer : NSObject
{
    @protected Subject *_subject;
}

- (instancetype)initWithSubject:(Subject *)subject;

- (void)update;

@end



//================== Observer.m ==================

@implementation Observer

- (instancetype)initWithSubject:(Subject *)subject{
    
    self = [super init];
    if (self) {
        _subject = subject;
       [_subject addObserver:self];
    }
    return self;
}

- (void)update{
    
    NSLog(@"implementation by subclasses");
}
Copy the code

The Observer class is the parent of the specific Observer, declaring a constructor passed in to the Subject class and holding an instance of it in the constructor. In addition, the constructor calls Subject’s ‘addObserver’ method, addObserver:, to place the current observer instance into Subject’s collection of observer instances. (see this in the Subject class section below.)

It also defines the update method for subclasses to use.

Let’s take a look at the specific observer class Investor:

//================== Investor.h ==================

@interface Investor : Observer

@end



//================== Investor.m ==================

@implementation Investor

- (void)update{

    float buyingPrice = [_subject getBuyingPrice];
    NSLog(@"investor %p buy stock of price:%.2lf".self,buyingPrice);    
}

@end
Copy the code

The specific observer implements the update method defined in the protocol, where it first gets the latest buyingPrice that is being listened on through the getBuyingPrice method, and then does anything else. Here, for demonstration purposes, the memory address of the current specific observer instance and the latest value of the current listener are printed directly using the date.

Let’s declare target classes and concrete target classes:

The target class Subject

//================== Subject.h ==================

@interface Subject : NSObject
{
    @protected float _buyingPrice;
    @protected NSMutableArray <Observer *>*_observers;
}

- (void)addObserver:(Observer *) observer;


- (void)removeObserver:(Observer *) observer;


- (void)notifyObservers;


- (void)setBuyingPrice:(float)price;


- (double)getBuyingPrice;


@end




//================== Subject.m ==================

@implementation Subject

- (instancetype)init{
    
    self = [super init];
    if (self) {
        _observers = [NSMutableArray array];
    }
    return self;
}


- (void)addObserver:( Observer * ) observer{
    
    [_observers addObject:observer];
}


- (void)removeObserver:( Observer *) observer{
    
    [_observers removeObject:observer];
}


- (void)notifyObservers{
    
    [_observers enumerateObjectsUsingBlock:^(Observer *  _Nonnull observer, NSUInteger idx, BOOL * _Nonnull stop) {
        
        [observer update];
    }];
}


- (void)setBuyingPrice:(float)price{
    
    _buyingPrice = price;
    
    [self notifyObservers];
}


- (double)getBuyingPrice{
    
    return _buyingPrice;
}


@end
Copy the code

The target class holds a mutable array to hold observers of its own; It also provides interfaces for adding and deleting observers, as well as notifying all observers.

And it holds a buyingPrice, which is the data for external observers to observe. Note in particular the setBuyingPrice: method it provides to outsiders: When outsiders call this method, updating the buyingPrice data, the target class calls notifyObservers that they are updated.

The getBuyingPrice method is used to return the current buyingPrice value, usually when observers are notified of updates (see the implementation of the Investor class above).

The FinancialAdviser class is the same as the FinancialAdviser class.

//================== FinancialAdviser.h ==================

@interface FinancialAdviser : Subject

@end



//================== FinancialAdviser.m ==================

@implementation FinancialAdviser

@end
Copy the code

Since all interfaces are already defined in the Subject class, we can simply create a new subclass that we want (we can define it in our own way if we have operations that are different from the parent class).

Let’s take a look at how the observer mechanism works:

FinancialAdviser *fa = [[FinancialAdviser alloc] init];
    
Investor *iv1 = [[Investor alloc] initWithSubject:fa];
    
NSLog(@"====== first advice ========");
[fa setBuyingPrice:1.3];
    
    
Investor *iv2 = [[Investor alloc] initWithSubject:fa];
Investor *iv3 = [[Investor alloc] initWithSubject:fa];

NSLog(@"====== second advice ========");
[fa setBuyingPrice:2.6];
Copy the code

As can be seen from the code, at the beginning, we added an instance iv1 of the specific observer class to the FinancialAdviser class, and then the instance FA of the FinancialAdviser class notified all the observers (only IV1 was available at this time).

We continue to send notifications after adding IV2 and IV3 to fa. At this point all three observers receive the message.

As you can also see in the day to day output below, the memory addresses 0x600003094C00 are IV1, 0x600003083680 and 0x600003083690 are IV2 and IV3.

====== First Advice ======== Investor 0x600003094C00 Buy stock of price:1.30 ====== Second advice ======== investor 0x600003094C00 Buy stock of price:2.60 Investor 0x600003083680 Buy stock of price:2.60 investor 0x600003083690 Buy stock Of price: 2.60Copy the code

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  • The observer pattern establishes an abstract coupling between the observed object and the observer.
  • Can realize broadcast, one-to-many communication

disadvantages

  • If an observation object has many direct and indirect observers, more communication time is required.
  • Note whether there are circular references between the observer and the observed target.

Apps in iOS SDK and JDK

  • KVO and NSNotification in iOS SDK are observer mode applications.
  • In the JDKjava.utilPackage, providedObservableClass, andObserverInterface, which constitute the Java language’s support for the observer pattern.

7. Intermediary model

define

Mediator Pattern: Encapsulates a set of object interactions with a Mediator object. The Mediator makes objects loosely coupled without explicitly referring to each other and can change their interactions independently.

Applicable scenario

The system structure may become increasingly complex, with a large number of interrelationships and calls between objects, and the overall structure of the system may easily become a network structure. In this case, if you need to modify an object, you might want to keep track of all the other objects associated with that object and process them. The more coupling you have, the more changes you can make.

If we use a mediator object, we can change the network structure of the system into a star structure with the mediator at the center. Mediators act as intermediaries and coordinators, simplifying the interaction between objects and giving them further control.

Now that we know where the mediator pattern applies, let’s look at the members and class diagram of the mediator pattern.

Members and class diagrams

Members of the

The Mediator model has four members:

  1. Mediator: An abstract Mediator defines the interface that a concrete Mediator needs to implement.
  2. Concrete Mediators: Concrete mediators implement interfaces defined by abstract mediators and assume the role of mediators between multiple Concrete colleague classes.
  3. Abstract Colleague classes: Abstract Colleague classes define interfaces that concrete Colleague classes need to implement.
  4. Concrete Colleague classes: Concrete Colleague classes implement interfaces defined by abstract Colleague classes.

Model class diagram

Code sample

Scenario overview

Simulate a multi-person conversation scenario: when one person sends a message, the other people can receive it.

Scenario analysis

Suppose there are three people A, B, and C. After A sends A message, the message needs to be sent to B and C respectively. If three people communicate directly with each other, the pseudocode might look like this:

A sent message to B
A sent message to C
Copy the code

And as the number of people increases, so does the number of lines of code, which is obviously unreasonable.

Therefore, in this scenario, we need to use the intermediary pattern to multi-forward A message among all people: when A sends A message, the intermediary sends it to B and C:

A sent message to Mediator ;
Mediator sent message to B & C
Copy the code

Let’s take a look at simulating this scenario in code.

Code implementation

First we create the User class User for the call:

//================== User.h ==================

@interface User : NSObject

- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator;

- (void)sendMessage:(NSString *)message;

- (void)receivedMessage:(NSString *)message;

@end



//================== User.m ==================

@implementation User
{
    NSString *_name;
    ChatMediator *_chatMediator;
}

- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator{
    
    self = [super init];
    if (self) {
        _name = name;
        _chatMediator = mediator;
    }
    return self;
}

- (void)sendMessage:(NSString *)message{
    
    NSLog(@ "= = = = = = = = = = = = = = = =");
    NSLog(@"%@ sent message:%@",_name,message);
    [_chatMediator sendMessage:message fromUser:self];
    
}

- (void)receivedMessage:(NSString *)message{
    
    NSLog(@"%@ has received message:%@",_name,message);
}

@end
Copy the code

The user class needs to pass in an instance of the mediator and hold it when initialized. The purpose is to forward the message to the mediator when it is later sent.

In addition, the user class provides an interface for sending and receiving messages. In fact, inside the method that sends the message, the method that sends the message is called by the mediator (because the mediator has all the user instances, so it can do multi-route forwarding). In detail, we can see the implementation of the ChatMediator class:

//================== ChatMediator.h ==================

@interface ChatMediator : NSObject

- (void)addUser:(User *)user;

- (void)sendMessage:(NSString *)message fromUser:(User *)user;

@end



//================== ChatMediator.m ==================

@implementation ChatMediator
{
    NSMutableArray <User *>*_userList;
}

- (instancetype)init{
    
    self = [super init];
    
    if (self) {
        _userList = [NSMutableArray array];
    }
    return self;
}

- (void)addUser:(User *)user{

    [_userList addObject:user];
}

- (void)sendMessage:(NSString *)message fromUser:(User *)user{
    
    [_userList enumerateObjectsUsingBlock:^(User * _Nonnull iterUser, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if (iterUser != user) {
            [iterUser receivedMessage:message];
        }
    }];
}

@end
Copy the code

The mediator class provides the addUser: method, so you can constantly add users to the mediator (think of it as an act of registration or “joining a group chat”). After each addition of a User instance, that instance is added to the mutable array held by the mediator. In the future, intermediaries can multiroute messages by iterating through groups of numbers, as shown in sendMessage:fromUser:.

Now that the user and mediator classes are created, let’s look at how messages are forwarded:

ChatMediator *cm = [[ChatMediator alloc] init];
    
User *user1 = [[User alloc] initWithName:@"Jack" mediator:cm];
User *user2 = [[User alloc] initWithName:@"Bruce" mediator:cm];
User *user3 = [[User alloc] initWithName:@"Lucy" mediator:cm];
    
[cm addUser:user1];
[cm addUser:user2];
[cm addUser:user3];
    
[user1 sendMessage:@"happy"];
[user2 sendMessage:@"new"];
[user3 sendMessage:@"year"];
Copy the code

As you can see from the code, we have created three users and added them to the chat broker object. Then we asked each user to send a message. Let’s take a look at each user’s message reception by day to day output:

[13806:1284059] ================
[13806:1284059] Jack sent message:happy
[13806:1284059] Bruce has received message:happy
[13806:1284059] Lucy has received message:happy
[13806:1284059] ================
[13806:1284059] Bruce sent message:new
[13806:1284059] Jack has received message:new
[13806:1284059] Lucy has received message:new
[13806:1284059] ================
[13806:1284059] Lucy sent message:year
[13806:1284059] Jack has received message:year
[13806:1284059] Bruce has received message:year
Copy the code

Let’s take a look at the corresponding class diagram for the code above.

Class diagram corresponding to the code

advantages

  • The mediator loosens the coupling by eliminating the need for objects to explicitly refer to each other.

disadvantages

  • The inclusion of interaction details between colleague classes in the concrete broker class can make the concrete broker class very complex and difficult to maintain.

Apps in iOS SDK and JDK

  • In the JDKTimerIs the implementation of the mediator class, and used in conjunction withTimerTaskIs the implementation of the colleague class.

This is the end of the behavioral patterns in design patterns. Readers can use UML class diagrams and demo code to understand the characteristics and differences of each design pattern.

The code and class diagrams for this blog are saved in my GitHub library: Chapter 2.3 in KnightsJ: Object-oriented Design.

So far, the object-oriented design series has come to an end, and there won’t be another article in the near future. Readers feel free to give me suggestions or communication.

This post has been synced to my blog: Portal

The previous three articles in this series:

  • Six Design Principles of Object-oriented Design (with Demo and UML class Diagram)
  • Design Patterns for Object-oriented Design (I) : Creation Design Patterns (with Demo and UML class Diagrams)
  • Design Patterns for Object-oriented Design (II) : Structural Patterns (with Demo & UML Class Diagrams)

Consult books and tutorials

  • Fundamentals of Reusable Object-oriented Software for Design Patterns
  • The Objective-C Way of Programming: iOS Design Patterns
  • Head First Design Patterns
  • Big Talk Design Mode