This is the third article in the object-oriented design series on structural patterns in design patterns:

  • The appearance model
  • Adapter mode
  • The bridge model
  • The proxy pattern
  • Decorator pattern
  • The flyweight pattern

The previous two 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)

I. Appearance mode

define

Facade Pattern: A Facade Pattern defines a high-level interface that provides a unified interface for a set of interfaces in a subsystem. Appearance mode, also known as facade mode, is a structural design mode.

Through this high-level interface, clients can be decoupled from subsystems: clients can access subsystems not directly, but indirectly through facade classes; It also improves the independence and portability of subsystems.

Applicable scenario

  • Subsystems become more and more complex with the increase of service complexity, and clients need some subsystems to work together to complete a task.
  • In a multi-tiered system, the use of facade objects can be used as an entry point for each layer to simplify calls between layers.

Members and class diagrams

Members of the

The facade mode consists of three members of the client:

  • Client classes: Clients are classes that are intended to manipulate subsystems, and they are in direct contact with facade classes. Indirect contact with appearance classes

  • Facade classes: Facade classes know the responsibilities and interfaces of each subsystem, encapsulate the interfaces of the subsystem and provide them to the client

  • SubSystem Class: The SubSystem class implements the functionality of the SubSystem and knows nothing about the facade class

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

Model class diagram

The method1&2() methods in the figure above call the method1() and method2() methods of SubSystem1 and SubSystem2. The same applies to method2&3().

Code sample

Scenario overview

Simulate a smart home system. This smart home system can use a central remote control to operate some of the furniture it is connected to: lamps, speakers, air conditioners and so on.

Here we simply manipulate a few devices:

  • air conditioner
  • CD Player
  • DVD Player
  • The speakers
  • The projector

Scenario analysis

Sometimes we need a device that can perform two different operations at once; Multiple devices may also be required to work together to perform some tasks. Such as:

Assuming that we can turn on hot air directly with the remote control, there are actually two steps:

  1. Open air conditioning
  2. The air conditioner switches to hot air mode

We put these two steps together in one operation, in one step. Scenarios that simplify operations like this are better suited for appearance patterns.

Similarly, if we want to listen to music, there are four steps:

  1. Open the CD Player
  2. Open the speakers
  3. Connect the CD Player to the speaker
  4. Play CD Player

These steps can also be installed in a separate interface.

Similarly, there are more steps if we want to watch a DVD, which requires both sound and video output:

  1. Open the DVD player
  2. Open the speakers
  3. The stereo connects to the DVD Player
  4. Turn on the projector
  5. The projector connects to the DVD Player
  6. Play DVD Player

These interfaces can also be installed in a separate interface.

Finally, if we want to go out, we need to turn off all the household appliances, and we don’t need to turn them off one by one, we just need a main interface to turn off, because this main interface to turn off all the household appliances can be included in the main interface.

Therefore, these devices can be regarded as subsystems of the smart home system; The remote controller acts as the appearance class.

Let’s look at how to implement these designs in code.

Code implementation

Since all home appliances have on and off operations, we’ll create a home appliance base class called HomeDevice:

//================== HomeDevice.h ==================
// Device base class

@interface HomeDevice : NSObject

// Connect the power supply
- (void)on;

// Turn off the power
- (void)off;

@end
Copy the code

Then there are all the household appliance classes that inherit it:

Air conditioning class AirConditioner:

//================== AirConditioner.h ==================

@interface AirConditioner : HomeDevice

// High temperature mode
- (void)startHighTemperatureMode;

// Normal temperature mode
- (void)startMiddleTemperatureMode;

// Low temperature mode
- (void)startLowTemperatureMode;

@end
Copy the code

CDPlayer class:

//================== CDPlayer.h ==================

@interface CDPlayer : HomeDevice

- (void)play;

@end
Copy the code

DVDPlayer class:

//================== DVDPlayer.h ==================

@interface DVDPlayer : HomeDevice

- (void)play;

@end
Copy the code

Speaker class VoiceBox:

//================== VoiceBox.h ==================

@class CDPlayer;
@class DVDPlayer;

@interface VoiceBox : HomeDevice

// Connect to CDPlayer
- (void)connetCDPlayer:(CDPlayer *)cdPlayer;

// Disconnect from CDPlayer
- (void)disconnetCDPlayer:(CDPlayer *)cdPlayer;

// Connect to the DVD Player
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;

// Disconnect from the DVD Player
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;

@end
Copy the code

Class Projecter:

//================== Projecter.h ==================

@interface Projecter : HomeDevice

// Connect to the DVD Player
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;

// Disconnect from the DVD Player
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;

@end
Copy the code

Note that speakers can connect CD Player and DVD Player; The projector can only connect to a DVD Player

Now that we have all the appliance classes and their interfaces defined, let’s take a look at the appearance class HomeDeviceManager for this instance.

First let’s look at the interface the client expects the appearance class to implement:

//================== HomeDeviceManager.h ==================

@interface HomeDeviceManager : NSObject

//===== About the air conditioner interface =====

// The air conditioner blows cold
- (void)coolWind;

// The air conditioner blows hot air
- (void)warmWind;


//===== about CD Player interface =====

/ / play CD
- (void)playMusic;

// Turn off the music
- (void)offMusic;


//===== about interface to DVD Player =====

/ / play DVDS
- (void)playMovie;

/ / close the DVD
- (void)offMoive;


//===== about the master switch interface =====

// Turn on all household appliances
- (void)allDeviceOn;

// Turn off all household appliances
- (void)allDeviceOff;

@end
Copy the code

The above interfaces are divided into four categories:

  • About the air conditioner port
  • Interface for CD Player
  • Interface about DVD Player
  • Interface for the master switch

To facilitate readers’ understanding, the number of subsystem interfaces encapsulated by these four types of interfaces is gradually increasing.

Before looking at how these interfaces are implemented, let’s look at how the facade classes hold instances of these subsystem classes. In this code example, instances of these subsystem classes are created in the constructor of the facade class and are saved as member variables of the facade class.

//================== HomeDeviceManager.m ==================

@implementation HomeDeviceManager
{
    NSMutableArray *_registeredDevices;// All registered (regulated) household appliances
    AirConditioner *_airconditioner;
    CDPlayer *_cdPlayer;
    DVDPlayer *_dvdPlayer;
    VoiceBox *_voiceBox;
    Projecter *_projecter;
    
}

- (instancetype)init{
    
    self = [super init];
    
    if (self) {
        
        _airconditioner = [[AirConditioner alloc] init];
        _cdPlayer = [[CDPlayer alloc] init];
        _dvdPlayer = [[DVDPlayer alloc] init];
        _voiceBox = [[VoiceBox alloc] init];
        _projecter = [[Projecter alloc] init];
        
        _registeredDevices = [NSMutableArray arrayWithArray:@[_airconditioner,
                                                              _cdPlayer,
                                                              _dvdPlayer,
                                                              _voiceBox,
                                                              _projecter]];
    }
    return self;
}
Copy the code

The _registeredDevices member variable is an array containing all subsystem instances associated with the appearance class instance.

There is more than one way to implement the association between subsystems and facade classes, which is not the focus of this article. For now, we just need to know that the facade class retains instances of these subsystems. In order, let’s first look at the realization of the interface of air conditioning:

//================== HomeDeviceManager.m ==================

// The air conditioner blows cold
- (void)coolWind{
    
    [_airconditioner on];
    [_airconditioner startLowTemperatureMode];
    
}

// The air conditioner blows hot air
- (void)warmWind{
    
    [_airconditioner on];
    [_airconditioner startHighTemperatureMode];
}
Copy the code

The interface for blowing cold air and hot air contains two interfaces of the air conditioner instance. The first is to start the air conditioner, and the second is the corresponding interface for cold air and hot air.

Let’s move on to implementing the CD Player interface:

//================== HomeDeviceManager.m ==================

- (void)playMusic{
    
    //1. Enable CDPlayer
    [_cdPlayer on];
    
    //2. Turn on the speaker
    [_voiceBox on];
    
    //3. Connect audio to CDPlayer
    [_voiceBox connetCDPlayer:_cdPlayer];
    
    / / 4. Play the CDPlayer
    [_cdPlayer play];
}

// Turn off the music
- (void)offMusic{
    
   //1. Cut the connection to the speaker
    [_voiceBox disconnetCDPlayer:_cdPlayer];
    
    //2. Turn off the speaker
    [_voiceBox off];
    
    / / 3. Switch off the CDPlayer
    [_cdPlayer off];
}
Copy the code

As mentioned in the scene analysis above, listening to music requires four steps: start CD Player and speaker, connect them, and play CD Player, which is more consistent with the scene in real life. Turning off music is also about disconnecting and then switching off the power (although switching off the power directly is also possible).

Let’s look at the implementation of the DVD Player interface:

//================== HomeDeviceManager.m ==================

- (void)playMovie{
    
    //1. Start DVD Player
    [_dvdPlayer on];
    
    //2. Turn on the speaker
    [_voiceBox on];
    
    //3. Connect audio to DVDPlayer
    [_voiceBox connetDVDPlayer:_dvdPlayer];
    
    //4. Turn on the projector
    [_projecter on];
    
    //5. Connect the projector to the DVDPlayer
    [_projecter connetDVDPlayer:_dvdPlayer];
    
    / / 6. Play DVDPlayer
    [_dvdPlayer play];
}


- (void)offMoive{

    //1. Disconnect the speaker from DVDPlayer
    [_voiceBox disconnetDVDPlayer:_dvdPlayer];
    
    //2. Turn off the speaker
    [_voiceBox off];
    
    //3. Disconnect the projector from the DVDPlayer
    [_projecter disconnetDVDPlayer:_dvdPlayer];
    
    //4. Turn off the projector
    [_projecter off];
    
    / / 5. Switch off the DVDPlayer
    [_dvdPlayer off];
}
Copy the code

Because DVD Player connects to both the speaker and projector, these two interfaces encapsulate more subsystem interfaces than CD Player does.

Finally, let’s look at the implementation of the master switch interface:

//================== HomeDeviceManager.m ==================

// Turn on all household appliances
- (void)allDeviceOn{
    
    [_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
        [device on];
    }];
}


// Turn off all household appliances
- (void)allDeviceOff{
    
    [_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
        [device off];
    }];
}
Copy the code

These two interfaces are for clients to turn on and off all devices. With these two interfaces, users do not have to turn on and off multiple devices one by one.

About the implementation of the two interfaces:

As mentioned above, this facade class holds all the devices that can be manipulated through an array member variable, _registeredDevices. So if we need to turn all the devices on or off we can just walk through this array and call on or off on each element. Because these elements inherit from HomeDevice, which means they all have on or off methods.

The advantage of this is that we do not need to list all the devices separately to call their interfaces separately; There is no need to change the implementation of these interfaces if some devices are added or removed later.

Let’s take a look at the corresponding class diagram of the demo.

Class diagram corresponding to the code

As you can see from the UML class diagram above, there is a lot of coupling between subsystems in this example; The interface of the appearance class HomeDeviceManager greatly simplifies the cost of using these subsystems.

advantages

  • Decoupling between the client and subsystem is realized: the client does not need to know the interface of the subsystem, which simplifies the call process of the client to call the subsystem and makes it easier to use the subsystem. At the same time convenient subsystem expansion and maintenance.
  • It follows Demeter’s rule (least know) : the subsystem only needs to expose interfaces that need to be called externally to the facade class, and its interfaces can be hidden.

disadvantages

  • Violation of the open close principle: Adding a new subsystem may require modifications to the facade or client code without introducing abstract facade classes.

Objective-c & Java practices

  • Objective-C:SDWebImageEncapsulation is responsible for the image download class and responsible for the image cache class, and external only to the client exposed to the simple download image interface.
  • Java:Spring-JDBCIn theJdbcUtilsEncapsulates theConnection.ResultSet.StatementIs provided to the client

Adapter mode

define

Adapter Pattern: The transformation of one interface into another interface desired by the customer so that classes that would otherwise not work together due to interface incompatibilities can work together. The adapter pattern, alias Wrapper pattern, is a structural design pattern.

Definition interpretation: Adapter patterns are divided into object adapters and class adapters.

  • Object adapter: forwards requests to the adaptor in a composite manner.
  • Class adapter: The target method calls are transferred to the method that calls the adaptor by the adapter class multiple inheriting the target interface and the adaptor.

Applicable scenario

  • We want to use an existing class, but the interface of this class does not meet our requirements, possibly because it is incompatible with other classes in the system that need to cooperate.
  • To create a class that is functionally reusable, this class may need to work with some future class with unknown interfaces.

Members and class diagrams

Members of the

The adapter pattern has three members:

  • Target: A class that the client wants to contact directly, providing the client with an interface to call
  • An Adaptee is an existing class that needs to be adapted
  • Adapter: The Adapter ADAPTS the interface of the Adaptee and that of the Target

Model class diagram

As mentioned above, the adapter pattern is divided into the class adapter pattern and the object adapter pattern, so UML class diagrams for both refinement patterns are provided here.

Object adapter pattern:

In an object adapter, the object of the adaptor is held by the adapter. When the adapter’s request method is called, the adaptor’s corresponding method is called from within that method.

Adapter-like pattern:

The multi-inheritance approach is adopted in the class adapter: the adapter inherits both the target class and the adaptor class, thus holding the methods of both.

Multiple inheritance can be implemented in Objective-C by following multiple protocols, using only object adapters in the code examples for this pattern.

Code sample

Scenario overview

Simulate a scenario where a cache component is replaced: Now client has depends on the old cache component interface, and later found that a new slow component of better performance, need to replace the old cache components into a new cache components, but the new cache component interfaces do not agree with the old cache interface, so now the client is not directly to work with new cache components.

Scenario analysis

Since the client relies on the interface of the old cache component in many places, it would be cumbersome to replace the interface of the new cache component in those places, and would have to do the same thing again in case the old cache component is replaced or a new cache component is replaced later, which is obviously not elegant.

The adaptor pattern is therefore a good fit for this scenario: create an adapter so that the client that used to interface with the old cache can work with the new cache component.

In this case, the new cache component is the Adaptee, and the old cache component (interface) is the Target, because it interacts directly with the client. We need to create an adapter class, Adaptor, to let the client work with the new cache component. Let’s see how the above problem is solved using code:

Code implementation

First we create the old cache component and let the client use it normally. Create the OldCacheProtocol interface for the old cache component:

The corresponding Java interface, in Objective-C, is called a protocol, or protocol.

//================== OldCacheProtocol.h ==================

@protocol OldCacheProtocol <NSObject>

- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key;

- (id)old_getCacheObjectForKey:(NSString *)key;

@end
Copy the code

You can see that the interface contains two methods that operate on the cache, prefixed with old.

We’ll simply create a cache component class, OldCache, that implements the OldCacheProtocol interface:

//================== OldCache.h ==================

@interface OldCache : NSObject <OldCacheProtocol>

@end


    
//================== OldCache.m ==================
    
@implementation OldCache

- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
    
    NSLog(@"saved cache by old cache object");
    
}

- (id)old_getCacheObjectForKey:(NSString *)key{
    
    NSString *obj = @"get cache by old cache object";
    NSLog(@ "% @",obj);
    return obj;
}

@end
Copy the code

For readers’ convenience, the new and OldCache components are named NewCache and OldCache. The implementation code is also relatively simple, since it is not the focus of this article, just distinguish between the interface names.

Now let’s let the client use the old cache component:

//================== client.m ==================

@interface ViewController(a)

@property (nonatomic.strong) id<OldCacheProtocol>cache;

@end

@implementation ViewController


- (void)viewDidLoad {
    
    [super viewDidLoad];
 
    // Use the old cache
    [self useOldCache];

    // Use the cache component operation
    [self saveObject:@"cache" forKey:@"key"];
    
}

// Instantiate the old cache and store it in the cache property
- (void)useOldCache{

    self.cache = [[OldCache alloc] init];
}

// Use the cache object
- (void)saveObject:(id)object forKey:(NSString *)key{

    [self.cache old_saveCacheObject:object forKey:key];
}
Copy the code
  • The client in this case isViewControllerIt holds a complianceOldCacheProtocolAn instance of the protocol, that is, it currently depends onOldCacheProtocolThe interface.
  • useOldCacheThe instantiate () method is used to instantiate the old cache and save it incacheIn the properties.
  • saveObject:forKey:The method is to actually use a cache object to hold the cache.

Run and print the result. The output is saved cache by old Cache object. It now appears that the client is ok to use the old cache.

Now we need to add a new cache component: first define the new cache component’s interface NewCacheProtocol:

//================== NewCacheProtocol.h ==================

@protocol NewCacheProtocol <NSObject>

- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key;

- (id)new_getCacheObjectForKey:(NSString *)key;

@end
Copy the code

As you can see, the NewCacheProtocol and OldCacheProtocol interfaces are roughly similar, but the names are still different, and different method prefixes are used to distinguish them.

Let’s look at how the new cache component implements this interface:

//================== NewCache.h ==================

@interface NewCache : NSObject <NewCacheProtocol>

@end


    
//================== NewCache.m ==================
@implementation NewCache

- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key{
    
    NSLog(@"saved cache by new cache object");
}

- (id)new_getCacheObjectForKey:(NSString *)key{
    
    NSString *obj = @"saved cache by new cache object";
    NSLog(@ "% @",obj);
    return obj;
}
@end
Copy the code

Now we have the new cache component, but the client class currently relies on the old interface, so the adapter class should come in:

//================== Adaptor.h ==================

@interface Adaptor : NSObject <OldCacheProtocol>

- (instancetype)initWithNewCache:(NewCache *)newCache;

@end


    
//================== Adaptor.m ==================
    
@implementation Adaptor
{
    NewCache *_newCache;
}

- (instancetype)initWithNewCache:(NewCache *)newCache{
    
    self = [super init];
    if (self) {
        _newCache = newCache;
    }
    return self;
}

- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
    
    //transfer responsibility to new cache object
    [_newCache new_saveCacheObject:obj forKey:key];
}

- (id)old_getCacheObjectForKey:(NSString *)key{
    
    //transfer responsibility to new cache object
    return [_newCache new_getCacheObjectForKey:key];
    
}
@end
Copy the code
  • First, the adapter class also implements the interface of the older cache component; The goal is that it can also receive the client’s methods for manipulating the old cache component.
  • The adapter constructor then passes in an instance of the new component class; The purpose is to forward the command to the new cache component class and invoke its corresponding method after receiving the client’s command to operate on the old cache component.
  • Finally, let’s look at how the adapter class implements the interfaces of the two older cache componentsold_saveCacheObject:forKey:Method to make the new cache component object call the correspondingnew_saveCacheObject:forKey:Methods; Again, in theold_getCacheObjectForKeyMethod to make the new cache component object call the correspondingnew_getCacheObjectForKey:Methods.

The adapter class is now defined. Finally, let’s look at how the adapter is used inside the client:

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

- (void)viewDidLoad {

    [super viewDidLoad];
 
    // Use the new cache component
    [self useNewCache];
    
    [self saveObject:@"cache" forKey:@"key"];
}

- (void)useOldCache{
    
    self.cache = [[OldCache alloc] init];
}

// Use the new cache component
- (void)useNewCache{
    
    self.cache = [[Adaptor alloc] initWithNewCache:[[NewCache alloc] init]];
}

// Use the cache object
- (void)saveObject:(id)object forKey:(NSString *)key{
    
    [self.cache old_saveCacheObject:object forKey:key];
}
Copy the code

As you can see, on the client side, only one change is needed: save our defined adapter class in the original cache property (implementation of the useNewCache method). The actual caching method, saveObject:forKey, doesn’t need to be changed at all.

As you can see, with the adapter pattern, there is no need to change the way the client calls the old cache component interface; With a little work, you can switch back and forth between the old and new cache components without the need for the original client’s operations on the cache.

The reason for this flexibility is that initially the client only relied on the interface implemented by the old caching component class, not the type of the old caching component class. The viewController property is @property (nonatomic, strong) id cache; . Because of this, our new adapter instance can be used directly here, since the adapter class also implements the interface. Instead, if our cache property reads @property (nonatomic, strong) OldCache *cache; That is, the client relies on the type of the old cache component, and our adapter class cannot be placed here so easily. Therefore, in order for our programs to be better modified and extended in the future, relying on interfaces is a prerequisite.

Let’s take a look at the corresponding class diagram for this code example:

Class diagram corresponding to the code

advantages

  • Comply with the open closed principle: Use adapters without changing existing classes, improving class reuse.
  • Decouple the target class from the adapter class to improve program extensibility.

disadvantages

  • Increased system complexity

Objective-c & Java practices

  • Objective-c: The practice of the adapter pattern has not been discovered yet, please leave a comment if you know
  • Java: in the JDKXMLAdapterThe adapter pattern is used.

Bridge mode

define

Bridge Pattern (Simple Factory Pattern) : Separate the abstract part from its implementation part so that they can all change independently.

The core of the bridging pattern is that two abstractions are related together as a composition, so that their implementations are not dependent on each other.

Applicable scenario

The bridge pattern is best used when a system has two dimensions that vary independently and both dimensions need to be extended.

Let’s look at the members and class diagram of the simple factory pattern.

Members and class diagrams

Members of the

Bridge mode has only three members:

  • Abstraction class: The Abstraction class maintains a reference to the object of an implementation part and declares the interface that calls the object of the implementation part.
  • RefinedAbstraction: Extend the abstract class that defines methods relevant to the real business.
  • Implementor: An implementation class interface defines the interface that is part of the implementation.
  • ConcreteImplementor Is an object that implements the interface of an implementation class.

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

Model class diagram

As can be seen from the class diagram, Abstraction holds the Implementor, but the two implementation classes are not dependent on each other. This is the core of the bridge pattern.

Code sample

Scenario overview

Create a few different shapes with different colors.

Three shapes:

  • A square
  • A rectangle
  • The prototype

Three colors:

  • red
  • green
  • blue

Scenario analysis

According to the above requirements, some friends may be designed like this:

  • A square
    • Red square
    • Green square
    • Blue square
  • A rectangle
    • Red rectangle
    • Green rectangle
    • Blue rectangle
  • circular
    • The red circle
    • The green circle
    • The blue circle

Such a design can indeed fulfill the above requirements. But imagine if you added a color or a shape later, would there be more classes? If the number of categories of the shape is M and the number of categories of the color is N, the total number of categories created in this way is m*n, and when M or N are very large, the result of multiplying them becomes large.

Let’s look at this scenario: shapes and colors are unrelated, they can expand and change independently, and this combination is best done in bridge mode.

According to the members of the bridge mode mentioned above:

  • An abstract class is an abstract class of a graph
  • An extended abstract class is a subclass that inherits a graphical abstract class: shapes
  • The implementation class interface is the color interface
  • The concrete implementation class is the class that inherits the color interface: various colors

Let’s look at the design in code.

Code implementation

First we create the base Shape class Shape:

//================== Shape.h ==================

@interface Shape : NSObject
{
    @protected Color *_color;
}

- (void)renderColor:(Color *)color;

- (void)show;

@end


    

//================== Shape.m ==================
    
@implementation Shape

- (void)renderColor:(Color *)color{
    
    _color = color;
}

- (void)show{
    NSLog(@"Show %@ with %@"[self class],[_color class]);
}

@end
Copy the code

As can be seen from the above code:

  • The shape of the classShapeholdColorClass. The two are combined as a composition. andShapeClass is defined for external pass inColorMethod of instancerenderColor:: Receives incoming messages from outside the methodColorInstance and save it.
  • Another public interfaceshowActually print the name of the graph and its matching color for our subsequent verification.

Next we create three different shapes classes that inherit from Shape:

Square:

//================== Square.h ==================

@interface Square : Shape

@end


    
    
//================== Square.m ==================
    
@implementation Square

- (void)show{
    
    [super show];
}

@end
Copy the code

Rectangle class:

//================== Rectangle.h ==================

@interface Rectangle : Shape

@end

    
    
    
//================== Rectangle.m ==================
    
@implementation Rectangle

- (void)show{
    
    [super show];
}

@end
Copy the code

Round:

//================== Circle.h ==================

@interface Circle : Shape

@end
    

    
    
//================== Circle.m ==================  
    
@implementation Circle

- (void)show{
    
    [super show];
}

@end
Copy the code

Remember the Color class held by Shape above? It is the parent of all color classes:

//================== Color.h ==================   

@interface Color : NSObject

@end
    
    


//================== Color.m ================== 
    
@implementation Color

@end
Copy the code

Next we create three Color classes that inherit from the Color class:

Red:

//================== RedColor.h ==================

@interface RedColor : Color

@end


    
    
//================== RedColor.m ==================  
    
@implementation RedColor

@end
Copy the code

Green:

//================== GreenColor.h ==================

@interface GreenColor : Color

@end


    
    
//================== GreenColor.m ==================
@implementation GreenColor

@end
Copy the code

Blue class:

//================== BlueColor.h ==================

@interface BlueColor : Color

@end


    
 
//================== BlueColor.m ==================
    
@implementation BlueColor

@end
Copy the code

OK, now that all the shapes and color classes have been created, let’s look at how the client can use them to combine different shapes with colors:

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


//create 3 shape instances
Rectangle *rect = [[Rectangle alloc] init];
Circle *circle = [[Circle alloc] init];
Square *square = [[Square alloc] init];
    
//create 3 color instances
RedColor *red = [[RedColor alloc] init];
GreenColor *green = [[GreenColor alloc] init];
BlueColor *blue = [[BlueColor alloc] init];
    
//rect & red color
[rect renderColor:red];
[rect show];
    
//rect & green color
[rect renderColor:green];
[rect show];
    
    
//circle & blue color
[circle renderColor:blue];
[circle show];
    
//circle & green color
[circle renderColor:green];
[circle show];
    
    
    
//square & blue color
[square renderColor:blue];
[square show];
    
//square & red color
[square renderColor:red];
[square show];
Copy the code

In the above code, we first identified all the shape classes and color class instances, and then freely combined to form different shape + color combinations.

Let’s look at the combination effect through the printed result below:

Show Rectangle with RedColor
Show Rectangle with GreenColor
Show Circle with BlueColor
Show Circle with GreenColor
Show Square with BlueColor
Show Square with RedColor
Copy the code

You can see from the printed interface that the combined result is fine.

Compared to the above design without the bridge pattern, the sum of the classes required to use the bridge pattern is M + N: the value of m or n is much less than m * n when the value of m or n is large (instead of bridging, inheritance is used).

And if shapes and colors are added later, using bridging mode makes it easy to match the old shapes and colors with the new shapes and colors without interfering with the new classes.

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

Class diagram corresponding to the code

As you can see from the UML class diagram, the design is built from the Shape and Color classes of two abstraction layers. Because both sides of the dependency are abstract classes (rather than concrete implementations), and the two are linked together in a composite way, it is very easy to extend without interfering with each other. This is a good reference for our future code design.

advantages

  • It is extensible and conforms to the open and closed principle: it separates abstraction from implementation so that the two can change independently

disadvantages

  • Before design, two dimensions that vary independently need to be identified.

Objective-c & Java practices

  • Objective-c: No bridge mode practices found yet, please leave a comment if you know
  • Java:Spring-JDBCIn theDriveManagerthroughregisterDriverMethod to register different types of drivers

4. Proxy mode

define

Proxy Pattern: Provides a Proxy for an object that controls access to the original object.

With the proxy pattern, the client directly accesses the proxy, which acts as an intermediary between the client and the target object.

Applicable scenario

In some cases, a client may not want or be able to refer to an object directly, and an indirect reference can be made through a third party called a “proxy.”

Because a proxy object can act as an intermediary between the client and the target object, it can be used to remove content and services that the client cannot see or to add additional services that the client needs.

Depending on the business, there may also be different types of agents:

  • Remote proxy: Provides local representation for objects located at different addresses or in a network.
  • Virtual proxy: Creates heavy objects as required.
  • Protected proxy: Controls access to the original object based on different access permissions.

Let’s look at the members and class diagram of the proxy pattern.

Members and class diagrams

Members of the

The proxy mode has four members including the client:

  • Client: The Client intends to access the real principal interface
  • Abstract Topic (Subejct) : Abstract topic defines the interface that the client needs to access
  • Proxy: A Proxy inherits from an abstract topic so that it holds a reference to an instance of the real target and the client accesses the Proxy directly
  • RealSubject: a RealSubject is a proxied object that inherits from an abstract subject, its instance is held by the broker, its interface is wrapped in the broker’s interface, and the client cannot access the RealSubject directly.

In fact, I am not quite sure why the proxy mode is called Subject and RealSubject.

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

Model class diagram

As you can see from the class diagram, the factory class provides a static method: a string passed in to produce its corresponding product.

Code sample

Scenario overview

Here is an example of a buyer buying a house through a buying agent.

Now generally, we do not directly contact the landlord to buy a house, but first contact the intermediary, the relevant contract and some matters to buy a house can first communicate with the intermediary.

In this example, we have the buyer pay directly to the agent, who then collects a portion of the broker’s fee and gives the rest to the landlord.

Scenario analysis

The agent acts as the landlord’s agent and has direct contact with the buyer. Besides, the agent needs to do other things before the actual transaction (collect the agent fee, check the authenticity of the listing for the buyer, etc.), so the agent model is suitable for this scenario.

According to the proxy mode members above:

  • The client is the buyer

  • An agent is an intermediary

  • The real subject is the landlord

  • Both agents and landlords implement methods to collect money. We can define an abstract subject class that has a public interface to collect money.

Code implementation

First let’s define the PaymentInterface that the landlord and agent need to implement (in the class diagram, it inherits some common object, I prefer to use interface).

//================== PaymentInterface.h ==================

@protocol PaymentInterface <NSObject>

- (void)getPayment:(double)money;

@end
Copy the code

This interface declares the method getPayment that both the broker and the landlord need to implement:

Next we declare the proxy class HouseProxy:

//================== HouseProxy.h ==================

@interface HouseProxy : NSObject<PaymentInterface>

@end

    


//================== HouseProxy.m ==================
const float agentFeeRatio = 0.35;

@interface HouseProxy(a)

@property (nonatomic.copy) HouseOwner *houseOwner;

@end

@implementation HouseProxy

- (void)getPayment:(double)money{
    
    double agentFee = agentFeeRatio * money;
    NSLog(@"Proxy get payment : %.2lf",agentFee);
    
    [self.houseOwner getPayment:(money - agentFee)];
}

- (HouseOwner *)houseOwner{
    
    if(! _houseOwner) { _houseOwner = [[HouseOwner alloc] init]; }return _houseOwner;
}

@end
Copy the code

In HouseProxy, it holds the instance of the landlord, that is, the principal. Then, in the getPayment: method of the host instance, the getPayment: method is called. And we can see that when we call the getPayment: method of the landlord instance, the agent first gets the broker fee (agentFeeRatio is defined as 0.35, i.e. 35% of the broker fee).

Some of these operations, in addition to the getPayment: method of the host instance, are the point of the agent: it can do some additional things before and after the agent actually does something. For example, like AOP programming, define similar before***Method or after**Method methods, etc.

Finally, let’s look at how landlords implement getPayment: :

//================== HouseOwner.h ==================

@interface HouseOwner : NSObject<PaymentInterface>

@end

    

    
//================== HouseOwner.m ==================
    
@implementation HouseOwner

- (void)getPayment:(double)money{
    
    NSLog(@"House owner get payment : %.2lf",money);
}

@end
Copy the code

The HouseOwner class implements the getPayment: method in its own way.

Most of the time the principal (principal) can do things in their own way, and give some extra things to the agent to do, so as to keep the original class function pure, in accordance with the open closed principle.

Let’s take a look at the client and print results:

Client code:

//================== client.m ==================

HouseProxy *proxy = [[HouseProxy alloc] init];
[proxy getPayment:100];
Copy the code

The client above paid the intermediary $100.

Let’s take a look at the print:

Proxy get payment: 35.00 House owner get payment: 65.00Copy the code

As expected, the broker collected 35 percent of the broker’s fee and gave the rest to the landlord.

Class diagram corresponding to the code

As you can see from the UML class diagram, instead of using an abstract topic object, an interface is used to implement the intermediary and landlord separately.

advantages

  • Reduce the coupling degree of the system: the proxy mode can coordinate the caller and the called, and reduce the coupling degree of the system to a certain extent.
  • Different types of proxies can have different controls over client access to target objects:
    • Remote proxy enables clients to access objects on remote machines, which may have better computing performance and processing speed, and can quickly respond to and process client requests.
    • By using a small object to represent a large object, virtual agents can reduce the consumption of system resources, optimize the system, and improve the speed of operation.
    • A protection agent can control client access to real objects.

disadvantages

  • Adding a proxy object between the client and the proxied object may slow down client requests.

Objective-c & Java practices

  • IOS SDK: NSProxy can forward messages for held objects
  • JDK: under AOPJDKDynamicAopProxyIs a wrapper around the JDK’s dynamic proxy

5. Decorator mode

define

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

Applicable scenario

  • Adding responsibilities (functions) to an object dynamically can also be removed dynamically.
  • When the system cannot be extended by inheritance or inheritance is not conducive to system expansion and maintenance.

Members and class diagrams

Members of the

Decorator mode has four members:

  1. Abstract Components: An abstract Component defines an object (interface) to which responsibilities can be dynamically added.
  2. Concrete Component: Concrete components are instances of abstract components.
  3. Decorators: The Decorator class also inherits from the abstract component. It holds an instance of the concrete component object and implements an interface consistent with the abstract component interface.
  4. Concrete Decorator: Concrete Decorator is responsible for adding additional responsibilities to a Concrete build object instance.

Model class diagram

Code sample

Scenario overview

Simulation of salad production: salad is composed of salad base and sauce, different salad base and sauce collocation can form different salad.

The bottom of the salad The price
vegetables 5
chicken 10
beef 16
The sauce The price
vinegar 2
Peanut butter 4
The blueberry sauce 6

Note: The same salad base can be served with multiple sauces, and more than one portion can be served.

Scenario analysis

Because after selecting a salad base, you can add different amounts and types of sauces at will, that is, adding new objects to the original salad object, it is better to use the decorator model: the sauce is the decorator, and the salad base is the component to be decorated.

Let’s look at how to implement this scenario in code.

Code implementation

First we define the abstract component, the base of the Salad class:

//================== Salad.h ==================

@interface Salad : NSObject

- (NSString *)getDescription;

- (double)price;

@end
Copy the code

The getDescription and price methods are used to describe the current configuration of the salad and its price (because these two numbers change as the decorator decorates them).

Next we declare the decorator’s base class, SauceDecorator. According to the decorator design pattern class diagram, this class inherits from the salad class:

//================== SauceDecorator.h ==================

@interface SauceDecorator : Salad

@property (nonatomic.strong) Salad *salad;

- (instancetype)initWithSalad:(Salad *)salad;

@end

    

//================== SauceDecorator.m ==================
    
@implementation SauceDecorator

- (instancetype)initWithSalad:(Salad *)salad{
    
    self = [super init];
    
    if (self) {
        self.salad = salad;
    }
    return self;
}

@end
Copy the code

Pass an instance of the Salad class into the decorator constructor and save it for use when decorating it.

Now that the base classes for the abstract component and decorator are created, let’s create the concrete component and decorator. First we create concrete artifacts:

  • Vegetable salad
  • Chicken salad
  • Beef salad

VegetableSalad:

//================== VegetableSalad.h ==================

@interface VegetableSalad : Salad

@end


 
//================== VegetableSalad.m ==================
    
@implementation VegetableSalad

- (NSString *)getDescription{
    return @"[Vegetable Salad]";
}

- (double)price{
    return 5.0;
}

@end
Copy the code

The getDescription method returns a description of the base of the salad. The price method then returns its corresponding price.

Similarly, we continue to create chicken salad base and beef salad base according to the price list:

Chicken salad base:

//================== ChickenSalad.h ==================

@interface ChickenSalad : Salad

@end


    
//================== ChickenSalad.m ==================
@implementation ChickenSalad

- (NSString *)getDescription{
    return @"[Chicken Salad]";
}

- (double)price{
    return 10.0;
}

@end
Copy the code

Beef salad base:

//================== BeefSalad.h ==================

@interface BeefSalad : Salad

@end


    
//================== BeefSalad.m ==================
    
@implementation BeefSalad


- (NSString *)getDescription{
    return @"[Beef Salad]";
}

- (double)price{
    return 16.0;
}

@end
Copy the code

Now that all decorators are created, let’s create the sauce class (i.e., concrete decorator) according to the sauce price list:

  • vinegar
  • Peanut butter
  • The blueberry sauce

First look at the VinegarSauceDecorator:

//================== VinegarSauceDecorator.h ==================

@interface VinegarSauceDecorator : SauceDecorator

@end

    

//================== VinegarSauceDecorator.m ==================    
    
@implementation VinegarSauceDecorator

- (NSString *)getDescription{
    return [NSString stringWithFormat:@"%@ + vinegar sauce"[self.salad getDescription]];
}

- (double)price{
    return [self.salad price] + 2.0;
}

@end
Copy the code

Rewrote the getDescription method and added its own decoration, adding a + Vinegar sauce string to the original description. The original description is available because the decorator’s object (the method defined in the decorator base class) is already available in the constructor. In the same way, the price has increased its own price on the original basis.

Now that we know the design of the specific decorator, and so on, let’s see how peanut butter and blueberry sauce are defined:

Peanut butter PeanutButterSauceDecorator class:

//================== PeanutButterSauceDecorator.h ==================     

@interface PeanutButterSauceDecorator : SauceDecorator

@end


    
//================== PeanutButterSauceDecorator.m ==================     
@implementation PeanutButterSauceDecorator

- (NSString *)getDescription{
    return [NSString stringWithFormat:@"%@ + peanut butter sauce"[self.salad getDescription]];
}

- (double)price{
    return [self.salad price] + 4.0;
}

@end
Copy the code

Decorator BlueBerrySauceDecorator:

//================== BlueBerrySauceDecorator.h ==================     

@interface BlueBerrySauceDecorator : SauceDecorator

@end


 
//================== BlueBerrySauceDecorator.m ==================     
    
@implementation BlueBerrySauceDecorator
    
- (NSString *)getDescription{
    
    return [NSString stringWithFormat:@"%@ + blueberry sauce"[self.salad getDescription]];
}

- (double)price{
    
    return [self.salad price] + 6.0;
}

@end
Copy the code

OK, now that all the classes are defined, to verify that the implementation is correct, let’s try a few different salads with the client side:

  1. Vegetables and a single vinaigrette salad (7 yuan)
  2. Beef and double peanut butter salad (24 yuan)
  3. Chicken with a single serving of peanut butter and a single serving of blueberry dressing salad (20 RMB)

First, let’s look at the first collocation:

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

//vegetable salad add vinegar sauce
Salad *vegetableSalad = [[VegetableSalad alloc] init];
NSLog(@ "% @",vegetableSalad);

vegetableSalad = [[VinegarSauceDecorator alloc] initWithSalad:vegetableSalad];
NSLog(@ "% @",vegetableSalad);
Copy the code

This salad is: [Vegetable salad] and the price is: [Vegetable Salad] + vinegar sauce and the price is: 7.00

In the code above, we first created the vegetable base and then let the vinaigrette decorate it (passing the instance of the vegetable base into the vinaigrette decorator constructor). Finally we print the vegetable base object, the description and the price and decoration did change, indicating that there is no problem with our code.

Then we look at the second collocation:

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

//beef salad add two peanut butter sauce:
Salad *beefSalad = [[BeefSalad alloc] init];
NSLog(@ "% @",beefSalad);

beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@ "% @",beefSalad);

beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@ "% @",beefSalad);
Copy the code

[Beef Salad] and the price is 16.00 [Beef Salad] + peanut butter sauce and the price is: [Beef Salad] + peanut butter sauce + peanut butter sauce and the price is: 15.00

Similar to the code implementation above, create the salad base (beef base in this case) and then add the dressing. Since there are two decorations, write the code for peanut butter again. Compare the results of each print with the price list above to see that the output is correct.

In this example, the same sauce was added twice, and finally we look at the third combination, which added two different sauces:

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

//chiken salad add peanut butter sauce and blueberry sauce
Salad *chikenSalad = [[ChickenSalad alloc] init];
NSLog(@ "% @",chikenSalad);

chikenSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@ "% @",chikenSalad);

chikenSalad = [[BlueBerrySauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@ "% @",chikenSalad);
Copy the code

[Chicken Salad] + peanut butter sauce and the price is: 10.00 [Chicken Salad] + peanut butter sauce + blueberry sauce and the price is: 15.00

Compare the results of each print with the price list above to see that the output is correct.

At this point, the scenario simulation ends. Imagine if you could add other Salad bases and sauces in the future, inheriting the Salad class and the SauceDecorator class, without changing the existing code. And more salads can be made with different combinations.

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

Class diagram corresponding to the code

advantages

  • More flexible than inheritance: different from inheritance that works at compile time; Decorator pattern extends the functionality of an object at run time. Alternatively, different decorators can be selected at run time through configuration files to achieve different behavior. It can also achieve different effects through different combinations.
  • Accord with “open close principle” : decorator and decorator can change independently. Users can add new decoration classes as needed, and then combine them when used, without changing the original code.

disadvantages

  • The decorator pattern requires the creation of concrete decorator classes, which can add complexity to the system.

Objective-c & Java practices

  • There are no practices in Objective-C for decorator mode yet
  • In the JDK:BufferReaderinheritedReaderIn theBufferReaderIs passed into the constructor ofReader, to achieve the decoration

Six. Enjoy yuan mode

define

Flyweight Pattern: use sharing technology to reuse a large number of fine-grained objects, reduce the occupation of program memory, improve the performance of the program.

Definition interpretation:

  • The purpose of sharing mode is to reuse a large number of fine – grained objects using sharing technology to improve performance.
  • The key of sharing objects is to distinguish Internal State from External State.
    • Internal state is the state that is stored inside the share object and does not change with the environment, so internal state can be shared.
    • An external state is a state that changes with the environment and cannot be shared. The external state of the share object must be saved by the client and passed in as needed after the share object is created. One external state is independent of another.

Applicable scenario

  • The system has a large number of similar objects that have some external state.
  • The share pattern should only be worthwhile if the share object is reused many times. Using the share pattern requires maintaining a pool of share objects, which requires resources, so,

Members and class diagrams

Members of the

There are three members of the Enjoy mode:

  • FlyweightFactory: The FlyweightFactory provides a pool to store weightobjects. When a user needs an object, the user obtains it from the pool. If the pool does not exist, a new weightobject is created and returned to the user, and the new object is saved in the pool
  • Abstract weights: Abstract weights define the interfaces that concrete weights need to implement.
  • ConcreteFlyweight: ConcreteFlyweight implements an interface that abstracts the metaclass definition.

Model class diagram

Code sample

Scenario overview

Here we use the example used in Chapter 21 of Objective-C Programming Principles: Parsing Design Patterns for iOS: showing images of hundreds of flowers in different sizes and positions on a single page with only six flower styles.

Take a look at the screenshot:

Scenario analysis

Since we need to create a lot of objects here, and these objects have internal states that can be shared (six image contents) and different external states (random, hundreds of position coordinates and image sizes), the share mode is a good fit.

According to the members of the share mode mentioned above:

  • We need to create a factory class to according to the type of flowers to return the cost object (the object including internal can share pictures and external state position and size) : every time when a new type of object to generate a flower he keep it up, because next time if you need this type of flower inside can directly use the image object.
  • Abstract meta-classes are native to Objective-CUIImageView, it can display pictures
  • A concrete metaclass can define its own class to inherit fromUIImageViewBecause we can add more attributes directly later.

Let’s see how this works in code:

Code implementation

First we create a factory that returns an internal flower image object based on the type of flower passed in. Here we can use the native UIImage object, which is the image object. And the factory holds a pool of picture objects:

  • When a flower of this type is created for the first time, the factory creates a new flower interior image object and stores this object in the pool.
  • When a flower interior image object of this type already exists in the pool, the factory returns the flower interior image object directly from the pool.

Let’s look at how the code is implemented:

//================== FlowerFactory.h ================== 

typedef enum 
{
  kAnemone,
  kCosmos,
  kGerberas,
  kHollyhock,
  kJasmine,
  kZinnia,
  kTotalNumberOfFlowerTypes
    
} FlowerType;

@interface FlowerFactory : NSObject 

- (FlowerImageView *) flowerImageWithType:(FlowerType)type

@end




//================== FlowerFactory.m ================== 
    
@implementation FlowerFactory
{
    NSMutableDictionary *_flowersPool;
}

- (FlowerImageView *) flowerImageWithType:(FlowerType)type
{
    
  if (_flowersPool == nil){
      
     _flowersPool = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
  }
  
  // Try to get the flower interior image object corresponding to the incoming type
  UIImage *flowerImage = [_flowersPool objectForKey:[NSNumber numberWithInt:type]];
  
  // If there is no image of the corresponding type, one is generated
  if (flowerImage == nil) {NSLog(@"create new flower image with type:%u",type);
      
    switch (type){
            
      case kAnemone:
        flowerImage = [UIImage imageNamed:@"anemone.png"];
        break;
      case kCosmos:
        flowerImage = [UIImage imageNamed:@"cosmos.png"];
        break;
      case kGerberas:
        flowerImage = [UIImage imageNamed:@"gerberas.png"];
        break;
      case kHollyhock:
        flowerImage = [UIImage imageNamed:@"hollyhock.png"];
        break;
      case kJasmine:
        flowerImage = [UIImage imageNamed:@"jasmine.png"];
        break;
      case kZinnia:
        flowerImage = [UIImage imageNamed:@"zinnia.png"];
        break;
      default:
        flowerImage = nil;
        break;
    
    }
      
    [_flowersPool setObject:flowerImage forKey:[NSNumber numberWithInt:type]];
      
  }else{
      // If there is a corresponding type of image, use it directly
      NSLog(@"reuse flower image with type:%u",type);
  }
    
  // Create a flower object, assign a value to the flower interior image object and return it
  FlowerImageView *flowerImageView = [[FlowerImageView alloc] initWithImage:flowerImage];
    
  return flowerImageView;
}
Copy the code
  • The factory class defines six image types
  • The factory class holds_flowersPoolA private member variable that holds the newly created image.
  • flowerImageWithType:The implementation of: combined_flowersPool: when_flowersPoolIf there is no corresponding image, a new image is created and returned. Otherwise directly from_flowersPoolGet the corresponding image and return.

Then we define these flower objects: FlowerImageView

//================== FlowerImageView.h ================== 

@interface FlowerImageView : UIImageView 

@end


//================== FlowerImageView.m ================== 
    
@implementation FlowerImageView

@end
Copy the code

You can actually use UIImageView directly in this, but the reason I created a subclass is to extend the properties that are unique to these flowers.

Notice the difference between the flower object and the image object inside the flower: The FlowerImageView object is the UIImage that contains the image object inside the flower. Because in Objective-C, UIImage is a property of UIImageView that FlowerImageView inherits, so in this case the FlowerImageView directly contains UIImage.

Let’s see how the client uses the FlowerFactory and FlowerImageView classes:

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

// First build a factory to produce flower interior image objects
FlowerFactory *factory = [[FlowerFactory alloc] init];

for (int i = 0; i < 500; ++i)
{
    // Randomly pass in a flower type and let the factory return the flower object corresponding to that type
    FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
    FlowerImageView *flowerImageView = [factory flowerImageWithType:flowerType];

    // Create the external property value of the flower object (random position and size)
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
    CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
    NSInteger minSize = 10;
    NSInteger maxSize = 50;
    CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;

    // Assign the position and size to the flower object
    flowerImageView.frame = CGRectMake(x, y, size, size);

    // Display the flower object
    [self.view addSubview:flowerImageView];
}
Copy the code

Inside the above code is generated 500 flower position and size are random internal image objects. The main difference between the 500 flowers is their position and size; There are only 6 flower picture objects used by them, so a special Factory can be used to generate and manage these few flower internal picture objects, as can be seen from the Factory’s printing:

create new flower image with type:1
create new flower image with type:3
create new flower image with type:4
reuse flower image with type:3
create new flower image with type:5
create new flower image with type:2
create new flower image with type:0
reuse flower image with type:5
reuse flower image with type:5
reuse flower image with type:4
reuse flower image with type:1
reuse flower image with type:3
reuse flower image with type:4
reuse flower image with type: 0Copy the code

As can be seen from the print results above, after all six kinds of pictures are created, the generated pictures can be directly taken when obtaining again, which reduces the memory overhead to a certain extent.

Let’s take a look at the CORRESPONDING UML class diagram for this code example.

Class diagram corresponding to the code

The thing to notice here

  • Factory and flower objects are combinatorial:FlowerFactroyGenerated multipleFlowerImageViewObject, the inner picture object of the flower, the relationship between the two is strong, because in this example both would be meaningless if they existed separately, so the combined relationship (solid diamond) was used in the UML class diagram.
  • The abstract meta-class isUIImageView, one of its internal objects isUIImage(These are both objective-C native classes for images).
  • The objects the client relies on are the factory object and the flower object, and the internal picture object for the flowerUIImageCan be ignorant because it is beingFlowerFactroyCreate and beFlowerImageViewIn possession. (But becauseUIImageisFlowerImageViewA property that can be referenced externally, so that the client can still access itUIImageThis is a native implementation of Objective-C. We can use the free mode without exposing the internal attributes.

advantages

  • Using the free element module can reduce the number of objects in the memory, so that only one copy of the same object or similar object can be saved in the memory, reducing the memory usage of the system and improving performance.
  • The external state of the share schema is relatively independent and does not affect its internal state, allowing share objects to be shared in different environments.

disadvantages

  • Using the share pattern requires the separation of internal and external state, which complicates the logic of the program.

  • Reuse of objects in buffer pools requires consideration of threads.

Objective-c & Java practices

  • The iOS SDKUITableViewCellReuse pooling is an example of using the share pattern.
  • Java: in the JDKIntegerOf the classvalueOfMethod if the range of values passed in[IntegerCache.low,IntegerCache.high]Is directly fetched from the cache. Otherwise, create a new oneInteger.

This is the end of the introduction of structural patterns in design patterns, readers can combine UML class diagrams and demo code to understand the characteristics and differences between each design pattern, hope readers can learn.

In addition, the code and class diagrams for this blog are stored in my GitHub library: Object-oriented Design Chapter 2.2

The previous two 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)

The next article in this series, the fourth in the object-oriented series, covers behavioral patterns in object-oriented design patterns.

Design Patterns for Object-oriented Design (II) : Structural Patterns (with Demo and 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
  • Moocs network practical course: Java design mode intensive Debug mode + memory analysis

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you