preface

The book Design Patterns: The Foundations of Reusable Object-oriented Software introduces 23 classic design patterns, but these are not the only ones. In this article, WE will take a look at some of the most common design patterns we use in iOS development.

  • A single mode
  • The factory pattern
  • Decorative pattern
  • The proxy pattern
  • Observer model
  • Command mode
  • The flyweight pattern

A single mode

The singleton pattern ensures that there is only one instance of a class in the system. Like global variables, singleton objects can be accessed anywhere in the system.

Usage scenarios

Consider using a single column when the entire application needs to share a single resource

  • [UIApplication sharedApplication];
  • [NSNotificationCenter defaultCenter];
  • [NSFileManager defaultManager];
  • [NSUserDefaults standardUserDefaults];
  • [NSURLCache sharedURLCache];
  • [NSHTTPCookieStorage sharedHTTPCookieStorage];

implementation

Mainly look at the common way to create a single column, less common will not say, so as not to mislead

Dispatch_once singleton

+ (instancetype)shareInstance
{
    static Singleton* single;
    static dispatch_once_t onceToken;  
    
    dispatch_once(&onceToken, ^{
        single = [[Singleton alloc] init];
    });
    return single;
}
Copy the code

@ synchronized singleton

@implementation Singleton

+ (instancetype)shareInstance
{
    static Singleton* single;
    @synchronized(self) {if (!single) {
            single = [[Singleton alloc] init];
        }
    }
    return single;
}
@end
Copy the code

At present, most of the dispatch_once method is used, which can solve the problem of simultaneous multi-threading without affecting performance

The factory pattern

Define an interface for creating objects and let subclasses decide which class to instantiate. The factory method delays instantiation of a class to subclasses.

UML diagrams

  • Abstract Factory (IFactory) : Provides an interface for creating products through which callers access the factory method newProduct() of a concrete factory to create products.
  • Concrete factory (actory) : it is mainly to realize the abstract method in the abstract factory and complete the creation of concrete products.
  • Abstract product (IProduct) : Defines the specification of the product, describing the main features and functions of the product.
  • Concrete products: Implement interfaces defined by abstract Product roles, created by concrete factories and corresponding to concrete factories.

Usage scenarios

The actual creation of the product in the factory class, the caller does not need to care about how the implementation of the product class changes, as long as the interface of the product (in other words, do not worry about the implementation details), such as

In iOS, the use of class clusters is relatively common, such as NSNumber, NSArray, NSString, etc., which is a kind of application of factory mode. It hides the specific implementation classes and only exposes simple interfaces

+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
Copy the code

demo

Viewing the Demo code
   interface IProduct {
        public void productMethod(a);
    }

    class Product implements IProduct {
        public void productMethod(a) {
            System.out.println("Products"); }}interface IFactory {
        public IProduct createProduct(a);
    }

    class Factory implements IFactory {
        public IProduct createProduct(a) {
            return newProduct(); }}public class Client {
        public static void main(String[] args) {
            IFactory factory = newFactory(); IProduct prodect = factory.createProduct(); prodect.productMethod(); }}Copy the code

Decorative pattern

To dynamically add additional functionality to an object without changing the original object.

UML diagrams

  • Component: Abstract Component that defines a set of abstract interfaces that specify the functionality of the decorated class.
  • ConcreteComponent: Implements all functions of this abstract component.
  • Decorator: Decorator role that holds a reference to an instance of a Component object.
  • ConcreteDecorator: a ConcreteDecorator implementor that implements the function defined by the decorator role

Usage scenarios

  • You need to extend the functionality of a class or add additional responsibilities to a class.
  • You need to add functions to an object dynamically, and these functions can be undone dynamically.
  • The need to add a very large amount of functionality from permutations and combinations of basic functionality makes inheritance unrealistic.
  • When the method of subclass generation cannot be used to extend

Such as:

A Category in iOS is a disguised decorator pattern. Its principle is to dynamically add methods to the original class at compile time, rather than having an instance of the original class. It is not a decorator in the strict sense, but it is similar to the idea of decorator, and can be used when there is little to add

demo

Viewing the Demo code
  1. Component
@protocol BattercakeProtocol <NSObject>
- (NSString *)descriptionString;
- (float)price;
@end
Copy the code
  1. ConcreteComponent
@interface Battercake : NSObject<BattercakeProtocol>
@end
@implementation Battercake
- (NSString *)descriptionString {
    return @ "pancakes";
}
- (float)price {
    return 8;
}
@end
Copy the code
  1. Decorator
// The core of the decorator pattern is the abstract decorator class design, which is typically coded as follows:
@interface AbstractDecorator : NSObject<BattercakeProtocol>

@property (nonatomic.strong) id<BattercakeProtocol>concreteComponent;

+ (instancetype)decoratorFor:(id<BattercakeProtocol>)concrete;

@end

@implementation AbstractDecorator

+ (instancetype)decoratorFor:(id<BattercakeProtocol>)concrete {
    AbstractDecorator *decorator = [self new];
    decorator.concreteComponent = concrete;
    return decorator;
}

- (NSString *)descriptionString {
    return [self.concreteComponent descriptionString];
}

- (float)price {
    return [self.concreteComponent price];
}

@end
Copy the code
  1. ConcreteDecorator
@interface EggDecorator : AbstractDecorator
@end
@implementation EggDecorator
- (NSString *)descriptionString {
    return [NSString stringWithFormat:@ % @ % @ ""[super descriptionString], @" Add an egg"];
}
- (float)price {
    return [super price] + 1;
}

@end
Copy the code
  1. Final call flow
id<BattercakeProtocol> battercake = [Battercake new];
battercake = [EggDecorator decoratorFor:battercake];
NSLog(@ % @, price: % @ "", [battercake descriptionString], @([battercake price]));
// Print the resultPancakes and an egg and an egg and a sausage, price:12 
Copy the code

The proxy pattern

The proxy mode is the delegate mode, which provides a proxy for an object and controls access to the original object.

  • Agreement: used to specify what the agent parties can do, what must be implemented
  • Delegate: What function is the agent assigned to perform according to the specified protocol
  • Agent: performs the functions required by the principal according to the specified agreement

Usage scenarios

When some function of a class needs to be implemented by another class, but it is uncertain which class will implement it

  • Custom delegate
# delegate defines the method
@protocol XXXDelegate<NSObject>
@optional
Copy the code
  • A custom delegate
  • Delegate provided by the system. UITableviewDelegate, UICollectionViewDelegate, etc

demo

For example, the real estate agent (agent) and the seller (entrusting party), the seller entrusts the agent to sell the house, and the agreement they both abide by is to sell the house

Viewing the Demo code
/ / agreement
@protocol SellHouseDelegate<NSObject>
@optional
- (void) sellHouse;/ / selling
@end 

// Client (seller)
@interface Seller : UIViewController
@property (nonatomic.weak) id <SellHouseDelegate>delegate;
@end
// Notify the Seller in the buyer.m file to find customers for yourself to sell the house
- (void)sellHouse{
if (self.delegate && [self.delegate respondsToSelector:@selector(sellHouse)]) {
   [selfThe delegate sellHouse]; }}// proxy
@implementation Agency<SellHouseDelegate>
- (void) sellHouse{
    // By the intermediary help to find customers to sell the house
 }
@end 

Copy the code

Observer model

Defines a one-to-many dependency between objects so that whenever an object’s state changes, its dependent objects are notified and automatically updated. The observer pattern is also called Publish/Subscribe, Model/View, Source/Listener or Dependents

UML diagrams

  • The object that changes is called a Subject
  • People who are notified to respond to changes are called observers.

Bell rings, students attend class; Bell ring again, students dismissed “. In this scenario, the bell is the object of the students’ observation and the students are the observers.

  • Subjec: The object, the observed. A collection of observers is defined to hold any number of observers and a method to manage them is provided. We also define the notify() method. This class can be an interface, an abstract class, or a concrete class;
  • ConcreteSubject: Concrete target class. When its state changes, each observer will be notified, and the class can decide whether to extend the target class according to the situation;
  • Observer: The Observer class responds to changes in the object of observation. Generally defined as an interface that declares the update method update();
  • ConcreteObserver: ConcreteObserver class, a class that actually responds to change. In general, it maintains a reference to a concrete object, and concretely stores the state of the ConcreteObserver (for example, when the bell rings and the student remembers the state of the concrete object) that matches the state of the concrete object. It implements the Update () method in the Observer interface. You can add yourself to or remove yourself from the observer queue by calling addObserver() and removeObserver() of the observing target object.
  • ConcreteObserver: ConcreteObserver

Usage scenarios

When an object changes, it needs to report its changes back. The main usage scenarios in iOS are the following two, which are not covered here

  • Notification
  • KVO

Command mode

Encapsulate a request as an object so that we can parameterize the client with different requests, or decouple the “behavior requester” from the “behavior implementer”? Abstract a set of behaviors as objects to achieve loose coupling between them. This is the command mode

UML diagrams

  • Command class: An abstract class that declares commands to be executed. Generally, it exposes a execute method to execute commands.
  • ConcreteCommand class: An implementation class of the Command class that implements methods declared in abstract classes.
  • Invoker class: Invoker, responsible for invoking commands.
  • Receiver class: Receiver, responsible for receiving and executing commands.

Usage scenarios

  • The NSinvocation class is designed with command mode. A Invocation object contains the target object, method selector, and method parameters
  • The target-Action mechanism in Cocoa is also an implementation of the command pattern

demo

Viewing the Demo code
  1. Defines the interface CommandProtocol
// Define the abstract class interface
@protocol CommandProtocol <NSObject>
@required
// Command execution
- (void)excute;
@end
Copy the code
  1. Define ConcrateCommand to adhere to the protocol that holds the receiver
@interface ConcrateCommand : NSObject<CommandProtocol>
- (instancetype)initWithReciever:(Reciever *)reciever;
@end

@interface ConcrateCommand(a)
@property(nonatomic.strong) Reciever* reciever;
@end
@implementation ConcrateCommand
- (instancetype)initWithReciever:(Reciever *)reciever
{
    self = [super init];
    if (self) {
        self.reciever = reciever;
    }
    return self; } - (void)execute{
    [self.reciever doSomething];
}
@end
Copy the code
  1. Receiver Reciver implements specific functionality
@interface Reciever : NSObject
// Specific methods- (void)doSomething;
@end

@implementation Reciever- (void)doSomething{
    NSLog(@" Receiver implements the function of a specific method");
}
@end
Copy the code
  1. Invoker requester that holds a reference to ConcrateCommand and implements ConcrateCommand’s method of complying with the protocol;
@interface Invoker(a)
@property (nonatomic.strong)Reciever  *reciever;
@property(nonatomic.strong)id <CommandProtocol> command;
@end

@implementation Invoker

- (instancetype)initWithCommand:(ConcrateCommand *)command
{
    self = [super init];
    if (self) {
    self.command = command;
    self.reciever = reciver;
    }
    return self; } - (void)doSomething{
    [self.command execute];
}

Copy the code
  1. The Client finally calls
// Create the receiver
Reciever  *reciver = [[Reciever alloc]init];
// Create command (separate)-> Unpair
ConcrateCommand *command = [[ConcrateCommand alloc]initWithReciever:reciver];
// Create the requester
Invoker  *invoker = [[Invoker alloc]initWitCommand:command];
[invoker doSomething];
Copy the code

The flyweight pattern

The share mode is mainly used to reduce the number of objects of the same class to reduce memory footprint and improve project fluency. There are two important concepts in the share pattern, namely internal state and external state:

  • Internal state: The shared part of a share object that does not change with the external environment
  • External state: Changes as the environment changes. A state that cannot be shared is an external state

Because the share pattern distinguishes between internal and external states, we can set different external states to make the same object have different properties

UML diagrams

  • Abstract Flyweight: The Flyweight object abstracts the base class or interface, and defines the interface or implementation of the external state and internal state of the object.
  • ConcreteFlyweight: A business that implements abstract role definitions. The role’s internal state processing should be independent of the environment, and there should not be an operation that changes the internal state while changing the external state;
  • FlyweightFactory: manages the pool of weightobjects and creates weightobjects;

Usage scenarios

In programming, you can use the meta-share pattern if you find that you need a large number of fine-grained class objects to represent data that have the same attributes except for a few different parameters

For example, UITableViewCell in iOS, UICollectionViewCell uses the share mode

demo

Viewing the Demo code
  1. Create a Cell class with internal state and methods for setting external state, representing the abstract meta-class.
@interface Cell : NSObject
@property(nonatomic.copy.readonly) NSString *cellID; // Internal state
- (void)setRowIndex:(NSInteger)rowIndex; // External state
@end

@implementation Cell
- (NSString *)cellID {
    return @"cellID";
}

- (void)setRowIndex:(NSInteger)rowIndex {
    NSLog(@"Cell reuse ID = %@, rowIndex = %ld".self.cellID, rowIndex);
}
@end
Copy the code
  1. Create ImageCell and TextCell classes, both inherited from the Cell class, to represent the concrete meta-class
// ImageCell
@interface ImageCell : Cell
@end
@implementation ImageCell
- (NSString *)cellID {
    return @"ImageCell";
}
@end

// TextCell
@interface TextCell : Cell
@end
@implementation TextCell
- (NSString *)cellID {
    return @"TextCell";
}
@end
Copy the code
  1. Finally, create a CellFactory class with a pool of enjoy elements to represent the enjoy factory class.
@interface CellFactory : NSObject
+ (instancetype)sharedInstance;
- (Cell *)getCellWithCellID:(NSString *)cellID;
@end

/ / m file
@interface CellFactory(a)
@property(nonatomic.strong) NSMutableDictionary *dict; / / the flyweight pool
@end

@implementation CellFactory
- (instancetype)init
{
    self = [super init];
    if (self) {
        _dict = [NSMutableDictionary dictionary];
    }
    return self;
}

+ (instancetype)sharedInstance { // Singleton mode
    static CellFactory *factory;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        factory = [CellFactory new];
    });
    return factory;
}

- (Cell *)getCellWithCellID:(NSString *)cellID { // Share the factory class core code
    if ([self.dict.allKeys containsObject:cellID]) {
        return [self.dict objectForKey:cellID];
    } else {
        if ([cellID isEqualToString:@"ImageCell"]) {
            ImageCell *imageCell = [ImageCell new];
            [self.dict setObject:imageCell forKey:cellID];
            return imageCell;
        } else if ([cellID isEqualToString:@"TextCell"]) {
            TextCell *textCell = [TextCell new];
            [self.dict setObject:textCell forKey:cellID];
            return textCell;
        } else {
            return nil; }}}@end
Copy the code
  1. use
CellFactory *factory = [CellFactory sharedInstance];
Cell *imageCell1 = [factory getCellWithCellID:@"ImageCell"];
Cell *imageCell2 = [factory getCellWithCellID:@"ImageCell"];
NSLog(@"imageCell1 = %p", imageCell1);
NSLog(@"imageCell2 = %p", imageCell2);
NSLog(@ "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
    
Cell *textCell1 = [factory getCellWithCellID:@"TextCell"];
Cell *textCell2 = [factory getCellWithCellID:@"TextCell"];
    
NSLog(@"textCell1 = %p", textCell1);
NSLog(@"textCell2 = %p", textCell2);
// Print the result
imageCell1 = 0x600002dbcc70
imageCell2 = 0x600002dbcc70
-------------------
textCell1 = 0x600002dac660
textCell2 = 0x600002dac660
Copy the code

Summary: Objects are reused just as well as the memory address of the object obtained from the primitive factory class.

This part of the reference: iOS design pattern 12 (Enjoy yuan mode)