24种设计模式代码实例学习(二)创建型模式

1,973 阅读28分钟

项目Demo

本文代码语言为Objective-C

接着上篇文章对设计模式七大原则的理解,这篇文章则是结合代码的例子,学习第一种模式:创建型模式

在面向对象编程中,创建型模式是用于实例化对象的设计模式。它们可以帮助我们有效地创建对象,而不会使代码变得复杂或难以维护。

在实际的开发过程中,正确使用创建型模式可以带来许多好处:

  1. 降低耦合性:创建型模式可以将对象的创建过程与使用过程分离,从而降低对象之间的耦合性。这使得代码更加灵活和易于维护。
  2. 提高复用性:创建型模式可以将对象的创建过程抽象出来,并使其可复用。这使得我们可以在整个应用程序中共享对象的创建逻辑,从而提高了代码的复用性。
  3. 隐藏对象的创建细节:使用创建型模式可以将对象的创建细节隐藏在模式内部,使得客户端无需知道对象创建的具体细节。这使得代码更加简洁、清晰,并提高了代码的可读性。
  4. 管理对象的生命周期:某些创建型模式(如工厂方法模式)可以管理对象的生命周期,从而确保对象的正确创建、使用和销毁。
  5. 改善代码的可测试性:使用创建型模式可以使代码更加易于测试。由于对象的创建过程被抽象出来并且与使用过程分离,因此可以更容易地编写测试用例

0 常见的创建型模式

  1. 简单工厂模式(Simple Factory Pattern)
  2. 工厂方法模式(Factory Method Pattern)
  3. 抽象工厂模式(Abstract Factory Pattern)
  4. 单例模式(Singleton Pattern)
  5. 原形模式(Prototype Pattern)
  6. 建造者模式(Builder Pattern)

1 简单工厂模式(Simple Factory Pattern)

solution2-en.png

在工厂模式中,我们在创建对象时,通过使用一个共同的接口来创建不同的对象,而无需暴露对象的创建逻辑

在简单工厂模式中,我们定义一个工厂类,该工厂类负责创建各种对象。我们只需调用工厂类的方法即可获取需要的对象,而无需直接使用 new 关键字创建对象。这样可以使代码更加灵活和易于维护,因为如果我们想要更改创建对象的逻辑,只需要更改工厂类中的代码即可。

例子

简单使用:Animal

假设我们有一个 Animal 类和两个子类 DogCat,我们想要使用工厂模式来创建它们的实例:

// Animal.h
@interface Animal : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end

// Dog.h
@interface Dog : Animal
@end

// Cat.h
@interface Cat : Animal
@end

// Animal.m
@implementation Animal
- (void)speak {
    NSLog(@"I am an animal");
}
@end

// Dog.m
@implementation Dog
- (void)speak {
    NSLog(@"I am a dog");
}
@end

// Cat.m
@implementation Cat
- (void)speak {
    NSLog(@"I am a cat");
}
@end

// AnimalFactory.h
@interface AnimalFactory : NSObject
+ (Animal *)createAnimalWithType:(NSString *)type;
@end

// AnimalFactory.m
@implementation AnimalFactory
+ (Animal *)createAnimalWithType:(NSString *)type {
    if ([type isEqualToString:@"dog"]) {
        return [[Dog alloc] init];
    } else if ([type isEqualToString:@"cat"]) {
        return [[Cat alloc] init];
    } else {
        return nil;
    }
}
@end

在上面的代码中,我们定义了一个 Animal 类和两个子类 DogCat。我们还定义了一个 AnimalFactory 工厂类,其中有一个 createAnimalWithType: 方法,该方法根据传入的 type 参数来创建不同类型的动物对象。当我们需要创建动物对象时,只需调用 AnimalFactorycreateAnimalWithType: 方法,传入所需的类型,即可获取相应的对象。

例如,我们可以通过以下代码创建一只狗:

Animal *dog = [AnimalFactory createAnimalWithType:@"dog"];
[dog speak];  // 输出 "I am a dog"

同样,我们可以通过以下代码创建一只猫:

Animal *cat = [AnimalFactory createAnimalWithType:@"cat"];
[cat speak];  // 输出 "I am a cat"

通过这种方式,我们可以在不暴露对象创建逻辑的情况下创建不同类型的对象,并且可以轻松地添加新类型的动物,只需在 AnimalFactory 中添加相应的创建逻辑即可。

在实际开发中,简单工厂模式常常用于以下场景:

  1. UIKit 中的控件创建:UIKit 中的各种控件(如 UIButtonUILabel 等)都是通过工厂模式创建的。
  2. 网络请求中的请求工厂:在进行网络请求时,可以通过请求工厂创建各种类型的请求对象,如 GET 请求、POST请求等。

UIKit 中的控件创建

在iOS开发中,UIKit框架提供了许多用于构建用户界面的控件,如 UIButton、UILabel、UITextField 等。这些控件的创建是通过工厂模式实现的。

在UIKit中,控件的创建都是由一个叫做 UIFactory 的工厂类负责的。UIFactory类提供了多个类方法,用于创建不同类型的控件。例如,UIFactory类中的 buttonWithType: 方法用于创建 UIButton 对象,示例代码如下:

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];

在上面的代码中,buttonWithType: 是一个工厂方法,它接受一个 UIButtonType 枚举类型的参数,并返回一个 UIButton 对象。在实际使用中,我们可以通过传递不同的参数,创建不同类型的按钮。

除了 UIButton,其他的控件也是通过类似的方式创建的。例如,UILabel控件的创建代码如下:

UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];

在上面的代码中,initWithFrame: 是一个构造方法,它接受一个 CGRect 类型的参数,并返回一个 UILabel 对象。不同于 UIButtonUILabel并没有提供工厂方法来创建对象,而是通过构造方法创建对象。

上面的UILabel没有使用 UIFactory 类提供的工厂方法来创建 UILabel 对象,但是它的创建仍然属于工厂模式。这个创建过程背后依然有一个专门的工厂类(例如 UILabel 的初始化方法会调用父类 UIView 的初始化方法),并且这个工厂类也有着与工厂模式相似的特征

  1. 工厂类负责创建具体的对象:在 UIKit 中,UILabel 对象的创建过程由 UIView 类的初始化方法负责,UIViewUILabel 的父类,可以看做是 UILabel 对象的工厂类。
  2. 工厂类隐藏了对象的创建细节:我们并不需要知道 UILabel 对象是如何被创建的,只需要知道它的初始化方法即可。
  3. 工厂类可以提供多个不同的创建方法:在 UIKit 中,除了 [[UILabel alloc] initWithFrame:CGRectZero] 这种创建方法之外,还有很多其他的创建方法,例如 [[UILabel alloc] init][[UILabel alloc] initWithCoder:] 等等,这些方法都可以用于创建 UILabel 对象。

因此,即使我们没有使用 UIFactory 类提供的工厂方法来创建 UILabel 对象,UILabel 对象的创建过程依然属于工厂模式的范畴。

网络请求中的请求工厂

在进行网络请求时,可以通过请求工厂创建各种类型的请求对象,如 GET 请求、POST 请求等。通常情况下,网络请求的参数和请求头等信息都需要在发送请求前进行配置,为了避免在客户端代码中重复编写这些代码,可以使用简单工厂模式将请求对象的创建和配置过程封装起来,使得客户端代码更加简洁易于维护

请求工厂通常包含一个或多个工厂方法,用于创建不同类型的请求对象。在创建请求对象时,可以通过传递不同的参数,来区分不同类型的请求

以下是一个简单的网络请求工厂示例,也借助了开发中比较流行的网络库AFNetworking来实现:

首先先写出网络请求类:

    @interface NetworkManager : NSObject

    /// 创建单例
    + (instancetype _Nonnull)shareManager;

    /// 请求类型
    typedef NS_ENUM(NSUInteger, NetworkManagerRequestType) {
        NetworkManagerRequestTypeGet,
        NetworkManagerRequestTypePost,
        NetworkManagerRequestTypePut,
        NetworkManagerRequestTypeDelete
    };

    /// 统一请求方法 responseSerializer默认都为AFJSONResponseSerializer
    - (void)requestURL:(NSString * _Nonnull)url
                  type:(NetworkManagerRequestType)requestType
            parameters:(id _Nonnull)parameters
              progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
               success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
               failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure;

    @end

<!---->

    #import "NetworkManager.h"
    #import <AFNetworking/AFNetworking.h>

    @interface NetworkManager ()

    /// AFHTTPSessionManager
    @property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

    @end

    @implementation NetworkManager

    /// 创建单例
    + (instancetype _Nonnull)shareManager {
        static dispatch_once_t onceToken;
        static NetworkManager *_shareManager = nil;
        dispatch_once(&onceToken, ^{
            _shareManager = [[self alloc] init];
        });
        return _shareManager;
    }

    - (instancetype)init {
        self = [super init];
        if (self) {
            self.sessionManager = [AFHTTPSessionManager manager];
            self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
            // 默认上传JSON 格式
            self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
        }
        return self;
    }

    /// 统一请求方法 responseSerializer默认都为AFJSONResponseSerializer
    - (void)requestURL:(NSString * _Nonnull)url
                  type:(NetworkManagerRequestType)requestType
            parameters:(id _Nonnull)parameters
              progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
               success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
               failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure {
        switch (requestType) {
            case NetworkManagerRequestTypeGet:
                [self GETRequestURL:url
                         parameters:parameters
                           progress:progress
                            success:success
                            failure:failure];
                break;
            case NetworkManagerRequestTypePost:
                [self POSTRequestURL:url
                         parameters:parameters
                           progress:progress
                            success:success
                            failure:failure];
                break;
            case NetworkManagerRequestTypePut:
                [self PUTRequestURL:url
                         parameters:parameters
                           progress:progress
                            success:success
                            failure:failure];
                break;
            case NetworkManagerRequestTypeDelete:
                [self DELETERequestURL:url
                         parameters:parameters
                           progress:progress
                            success:success
                            failure:failure];
                break;
            default:
                break;
        }
    }

    /// GET 请求
    - (void)GETRequestURL:(NSString * _Nonnull)url
               parameters:(id _Nonnull)parameters
                 progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
                  success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
                  failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure {
        [self.sessionManager
         GET:url
         parameters:parameters
         progress:progress
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            if (success) {
                success(task, responseObject);
            }
        }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            if (failure) {
                failure(task, error);
            }
        }];
    }

    /// POST
    - (void)POSTRequestURL:(NSString * _Nonnull)url
               parameters:(id _Nonnull)parameters
                 progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
                  success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
                  failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure {
        [self.sessionManager
         POST:url
         parameters:parameters
         progress:progress
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            if (success) {
                success(task, responseObject);
            }
        }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            if (failure) {
                failure(task, error);
            }
        }];
    }

    /// PUT
    - (void)PUTRequestURL:(NSString * _Nonnull)url
               parameters:(id _Nonnull)parameters
                 progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
                  success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
                  failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure {
        [self.sessionManager PUT:url parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            success(task, responseObject);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            failure(task, error);
        }];
    }

    /// DELETE
    - (void)DELETERequestURL:(NSString * _Nonnull)url
                  parameters:(id _Nonnull)parameters
                    progress:(void (^_Nullable)(NSProgress * _Nullable progress))progress
                     success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success
                     failure:(void (^)(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error))failure {
        [self.sessionManager DELETE:url parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            success(task, responseObject);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            failure(task, error);
        }];
    }

    @end

工厂类:

    #import "NetworkManager.h"

    NS_ASSUME_NONNULL_BEGIN

    @interface NetworkRequestFactory : NSObject

    + (NetworkManager *)creatNetworkManager;

    @end

    @implementation NetworkRequestFactory

    + (NetworkManager *)creatNetworkManager {
        return [NetworkManager shareManager];
    }

    @end

使用时可以这样调用:

    NetworkManager *networkManager = [NetworkRequestFactory
    creatNetworkManager];
    [networkManager
     requestURL:@"http://example.com/api"
     type:NetworkManagerRequestTypeGet
     parameters:nil
     progress:nil
     success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable
    responseObject) {
        
    }
     failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull
    error) {
        
    }];

这样可以通过简单的代码调用,方便地创建不同类型的网络请求,并且可以方便地切换底层网络库的实现。

2 工厂方法模式(Factory Method Pattern)

工厂方法模式和简单工厂模式类似。

简单工厂模式和工厂方法模式的区别在于创建对象的方式

简单工厂模式使用一个共享的工厂类来创建所有类型的对象,客户端通过传递参数来指定要创建的对象类型。

而工厂方法模式将每种具体产品的创建逻辑委托给对应的工厂类,客户端通过选择特定的工厂类来创建特定类型的对象

简单工厂模式适用于对象类型较少且创建逻辑相对简单的情况。工厂方法模式适用于对象类型较多或创建逻辑较为复杂的情况,它更符合开闭原则,允许新增具体产品和工厂类,而无需修改现有代码。

例子

// 产品接口
@protocol ShapeProtocol <NSObject>
- (void)draw;
@end

// 具体产品A
@interface Circle : NSObject <ShapeProtocol>
@end

@implementation Circle
- (void)draw {
    NSLog(@"绘制圆形");
}
@end

// 具体产品B
@interface Rectangle : NSObject <ShapeProtocol>
@end

@implementation Rectangle
- (void)draw {
    NSLog(@"绘制矩形");
}
@end

// 抽象工厂
@protocol ShapeFactoryProtocol <NSObject>
- (id<ShapeProtocol>)createShape;
@end

// 具体工厂A
@interface CircleFactory : NSObject <ShapeFactoryProtocol>
@end

@implementation CircleFactory
- (id<ShapeProtocol>)createShape {
    return [[Circle alloc] init];
}
@end

// 具体工厂B
@interface RectangleFactory : NSObject <ShapeFactoryProtocol> 

@end

@implementation RectangleFactory

- (id<ShapeProtocol>)createShape { 
    return [[Rectangle alloc] init]; 
} 

@end

客户端使用:

id<ShapeFactoryProtocol> factory = [[CircleFactory alloc] init]; 
id<ShapeProtocol> shape = [factory createShape]; 
[shape draw];

3 抽象工厂模式(Abstract Factory Pattern)

abstract-factory-en-3x.png

它提供一个接口用于创建一系列相关或依赖对象的家族,而不需要明确指定它们的具体类。

在抽象工厂模式中,抽象工厂接口定义了一组方法,用于创建一系列相关对象的工厂。这些相关对象可以是一组不同但相关的对象,例如在GUI界面中,按钮、标签、输入框等都属于相关的对象家族,它们通常会按照某种风格设计,如iOS 风格、Android 风格等。在抽象工厂模式中,每个具体工厂类都实现了这个抽象工厂接口,以便能够按照某种特定的规则或风格创建相关对象的家族。

可以说抽象工厂模式是工厂模式的一个扩展,它提供了一种更高层次的抽象,能够创建一组相关的对象家族,而不仅仅是一个对象

同时,抽象工厂模式也遵循了开闭原则,因为它允许在不修改现有代码的情况下添加新的具体工厂和产品系列,从而提高了代码的可维护性和可扩展性。

例子

我们要写两个风格的UI,分别是iOS 风格和Android 风格,就像上面图片中的球形风格和金字塔风格,都是一样的产品,但是是不同风格,使用抽象工厂模式的代码如下,

首先,我们定义一个抽象工厂接口,它包含两个方法:创建按钮和创建标签。

@protocol AbstractFactoryProtocol <NSObject>

- (UIButton *)createButton;
- (UILabel *)createLabel;

@end

然后,我们创建两个具体工厂类,它们实现了这个抽象工厂接口,并分别创建了iOS 和Android 风格的按钮和标签。

// iOS风格的具体工厂类
@interface iOSFactory : NSObject <AbstractFactoryProtocol>

@end

@implementation iOSFactory

- (UIButton *)createButton {
    return [UIButton buttonWithType:UIButtonTypeSystem];
}

- (UILabel *)createLabel {
    return [[UILabel alloc] init];
}

@end

// Android风格的具体工厂类
@interface AndroidFactory : NSObject <AbstractFactoryProtocol>

@end

@implementation AndroidFactory

- (UIButton *)createButton {
    return [UIButton buttonWithType:UIButtonTypeRoundedRect];
}

- (UILabel *)createLabel {
    return [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
}

@end

最后,我们使用这些具体工厂类来创建具体的按钮和标签对象,而不需要知道它们的具体类。

    id <AbstractFactoryProtocol> factory = [[iOSFactory alloc] init];
    // 使用iOS风格的按钮和标签
    [factory creatButton];
    [factory creatLabel];
    // 使用Android风格的按钮和标签
    [factory creatButton];
    [factory creatLabel];

通过使用抽象工厂模式,我们可以轻松地扩展工厂和产品系列,而不需要修改现有的客户端代码。例如,我们可以创建一个新的具体工厂类,以创建Windows风格的按钮和标签。

抽象工厂模式的优点:

  1. 将对象的创建与使用分离,客户端只需要知道如何使用这些对象,而不需要关心它们是如何创建的。
  2. 可以轻松地在程序中切换不同的产品簇,只需要改变具体工厂类即可,无需修改客户端代码。
  3. 可以确保一组相关的产品只能由同一个工厂创建,避免了不同产品之间的兼容性问题。
  4. 可以提高代码的扩展性和可维护性,如果需要添加一个新的产品簇,只需要添加一个新的具体工厂类即可

抽象工厂模式的缺点:

  1. 新增加一个产品簇比较困难,需要修改抽象工厂接口和所有的具体工厂类。
  2. 如果产品簇过多,会导致抽象工厂接口和所有的具体工厂类变得非常复杂,不易维护。
  3. 由于抽象工厂模式需要定义抽象工厂接口和抽象产品接口,因此代码的抽象层次比较高,有一定的学习成本。

总之,抽象工厂模式适合于那些需要一整套一起使用的对象(比如QQ 换皮肤),并且需要能够方便地切换不同的产品簇的场景,但是如果产品簇过多或者需要频繁添加新的产品簇,可能会导致代码变得复杂。

产品簇

指的是一组相关联的产品,它们通常用于解决同一个问题或者满足同一种需求。例如,在电商网站上,一个产品簇可以包括商品、订单、收货地址等多个相关联的产品;在游戏开发中,一个产品簇可以包括角色、武器、装备等多相关联的产品。

4 单例模式(Singleton Pattern)

800px-Singleton_UML_class_diagram.svg.png

单例模式是一种创建对象的设计模式,它确保一个类只有一个实例,并提供全局访问点以访问该实例。

单例模式通常用于需要全局访问且只需要一个实例的场景。下面是一些常见的用例:

  1. 配置对象:在应用程序中,可能需要一个全局配置对象,用于存储应用程序的设置和配置选项。使用单例模式可以确保只有一个配置对象,并使其在整个应用程序中易于访问。
  2. 数据库连接对象:在使用数据库的应用程序中,可能需要一个全局的数据库连接对象,用于执行查询和更新操作。使用单例模式可以确保只有一个数据库连接对象,并使其在整个应用程序中易于访问。
  3. 日志对象:在应用程序中,可能需要一个全局的日志对象,用于记录应用程序的运行时信息。使用单例模式可以确保只有一个日志对象,并使其在整个应用程序中易于访问。

总之,单例模式在需要全局访问且只需要一个实例的场景下非常有用。但是,使用单例模式也存在一些缺点,例如可能导致代码耦合性增强、单例对象的生命周期可能过长等问题。因此,在使用单例模式时需要谨慎考虑其适用性和使用方式。

例子

Objective-C中,单例模式可以通过使用静态变量和类方法来实现。以下是一个简单用于网络请求中的例子:

    @interface NetworkSingleton : NSObject

    /// 创建单例
    + (instancetype _Nonnull)shareManager;

    @end

    @implementation NetworkSingleton

    /// 创建单例
    + (instancetype _Nonnull)shareManager {
        static dispatch_once_t onceToken;
        static NetworkSingleton *_shareManager = nil;
        dispatch_once(&onceToken, ^{
            _shareManager = [[self alloc] init];
        });
        return _shareManager;
    }

    @end

5 原形模式(Prototype Pattern)

prototype-3x.png

原形模式允许通过复制现有对象来创建新对象,而不是通过实例化新的对象。这种模式通常用于创建复杂的对象,因为创建这些对象需要大量时间和资源。

原型模式通常在以下场景中使用:

  1. 创建复杂对象:如果要创建的对象比较复杂,例如具有多个子对象或需要大量计算才能创建,那么使用原型模式可以避免重复的计算和复杂的对象构建过程。
  2. 提高性能:使用原型模式可以提高性能,因为复制现有对象比创建新对象要
  3. 保护对象:有时候,为了保护对象的不变性,希望对象不能被修改。在这种情况下,使用原型模式可以确保不会修改原始对象,因为只能复制它而不是直接修改它。
  4. 动态配置对象:如果要在运行时动态配置对象,例如根据用户输入或环境变量来配置对象的属性,那么使用原型模式可以方便地生成新对象。

它通常与工厂模式和建造者模式一起使用,以提供更好的灵活性和可维护性。

在实际的开发中,可能会遇到需要创建复杂对象的场景,例如:

  1. 图形化界面:开发一个图形化界面,可能需要创建许多复杂的图形对象,例如窗口、按钮、标签、文本框等。
  2. 游戏开发:可能需要创建许多复杂的游戏对象,例如角色、怪物、道具等。
  3. 数据库访问层:需要创建许多复杂的数据对象,例如表格、列、行等。
  4. 网络通信:开发一个网络应用程序,可能需要创建许多复杂的网络对象,例如请求、响应、协议等。

在这些场景中,复杂对象通常包含多个子对象或属性,并且可能需要执行多个计算或操作才能构建。使用原型模式可以避免在每次需要创建对象时执行这些计算或操作,并且可以提高性能和可维护性。

Objective-C中,原型模式通常使用NSCopying协议来实现。NSCopying协议定义了一个copyWithZone方法,该方法返回对象的副本。

例子

图形化界面

在 iOS 开发中,创建一个复杂的界面可能涉及许多对象,例如UIViewUILabelUIButton等。我们可以使用原型模式来创建这些对象,而不需要每次都手动创建和配置它们。

    @interface MyView : UIView <NSCopying>

    @property (nonatomic, strong) UILabel *titleLabel;
    @property (nonatomic, strong) UIButton *button;

    @end

    @implementation MyView

    // 实现复制方法
    - (instancetype)copyWithZone:(NSZone *)zone {
        MyView *copyView = [[[self class] allocWithZone:zone] init];
        copyView.titleLabel = self.titleLabel;
        copyView.button = self.button;
        return copyView;
    }

    @end

使用时:

    // 创建原型对象
    MyView *prototypeView = [[MyView alloc] init];
    prototypeView.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
    prototypeView.button = [[UIButton alloc] initWithFrame:CGRectMake(0, 60, 100, 50)];
    // 复制原型对象,创建新的对象
    MyView *newView = [prototypeView copy];
    newView.titleLabel.text = @"Hello word";
    [newView.button setTitle:@"Click" forState:UIControlStateNormal];

在上面的例子中,我们创建了一个包含多个子对象的 UIView子类 MyView,并定义了一个复制方法来实现对象的复制。然后,我们创建了一个原型对象 prototypeView,并设置了它的子对象 titleLabelbutton。最后,我们复制了原型对象,并在新对象 newView 中修改了 titleLabelbutton 的属性。

原型模式与工厂模式的结合使用

工厂模式用于创建复杂对象,而原型模式则可以用于快速复制这些复杂对象。 这种结合使用可以提高对象的创建效率和性能

例如,在一个电子商务网站中,我们可能需要创建多种不同类型的产品,并根据用户的需求动态选择产品类型。在这种情况下,我们可以使用工厂模式创建原型实例,然后使用原型实例的复制方法创建新的对象实例

// Product类
@interface Product : NSObject <NSCopying>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGFloat price;

@end

@implementation Product

- (id)copyWithZone:(NSZone *)zone {
    Product *productCopy = [[[self class] allocWithZone:zone] init];
    productCopy.name = self.name;
    productCopy.price = self.price;
    return productCopy;
}

@end

// ProductFactory类
@interface ProductFactory : NSObject

@property (nonatomic, strong) Product *productPrototype;

- (Product *)createProduct;

@end

@implementation ProductFactory

- (Product *)createProduct {
    return [self.productPrototype copy];
}

@end

// 使用示例
Product *prototype = [[Product alloc] init];
prototype.name = @"iPhone";
prototype.price = 1000.0;

ProductFactory *factory = [[ProductFactory alloc] init];
factory.productPrototype = prototype;

Product *product1 = [factory createProduct]; // 创建新的产品实例1
product1.price = 1200.0;

Product *product2 = [factory createProduct]; // 创建新的产品实例2
product2.name = @"iPad";

NSLog(@"Prototype: %@", prototype);
NSLog(@"Product 1: %@", product1);
NSLog(@"Product 2: %@", product2);

在上述代码示例中,我们定义了一个Product类和一个ProductFactory类。Product类表示产品对象,它实现了NSCopying协议以支持复制。ProductFactory类表示产品工厂,它持有一个Product原型实例,并提供一个createProduct方法来创建新的产品实例。在使用示例中,我们先创建了一个Product原型实例prototype,然后将其设置到ProductFactory实例的productPrototype属性中。我们使用ProductFactory实例的createProduct方法来创建新的产品实例product1product2,它们分别复制了prototype的属性,并进行了修改。

原型模式与建造者模式的结合使用

建造者模式用于创建复杂对象,它通过分步骤的方式来构建对象,而原型模式可以用于快速复制已有的对象实例,使得在建造对象时更加高效。在这种结合使用的情况下,建造者模式通常用于创建原型实例,并使用原型实例的复制方法创建新的对象实例。这种结合使用可以提高对象的创建效率和灵活性。

例如,在一个游戏中,我们可能需要创建多种不同类型的角色,并根据用户的需求动态选择角色类型和属性。在这种情况下,我们可以使用建造者模式来创建原型实例,然后使用原型实例的复制方法创建新的对象实例。

    @interface Character : NSObject <NSCopying>

    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger level;
    @property (nonatomic, assign) CGFloat health;
    @property (nonatomic, assign) CGFloat mana;

    @end

    @implementation Character

    - (id)copyWithZone:(NSZone *)zone {
        Character *characterCopy = [[[self class] allocWithZone:zone] init];
        characterCopy.name = self.name;
        characterCopy.level = self.level;
        characterCopy.health = self.health;
        characterCopy.mana = self.mana;
        return characterCopy;
    }

    @end

    @protocol CharacterProtocol <NSObject>

    - (void)buildName:(NSString *)name;
    - (void)buildLevel:(NSInteger)level;
    - (void)buildHealth:(CGFloat)health;
    - (void)buildMana:(CGFloat)mana;

    @end

    @interface CharacterBuilder : NSObject <CharacterProtocol>

    @property (nonatomic, strong) Character *character;

    @end

    @implementation CharacterBuilder

    - (instancetype)init {
        self = [super init];
        if (self) {
            self.character = [[Character alloc] init];
        }
        return self;
    }

    - (void)buildName:(NSString *)name {
        self.character.name = name;
    }

    - (void)buildLevel:(NSInteger)level {
        self.character.level = level;
    }

    - (void)buildHealth:(CGFloat)health {
        self.character.health = health;
    }

    - (void)buildMana:(CGFloat)mana {
        self.character.mana = mana;
    }

    @end

在使用时:

    CharacterBuilder <CharacterProtocol> *characterBuilder = [[CharacterBuilder alloc] init];
    [characterBuilder buildName:@"Jane"];
    [characterBuilder buildLevel:10];
    [characterBuilder buildHealth:100.0];
    [characterBuilder buildMana:50.0];

    Character *prototype = characterBuilder.character;
    Character *characterCopy1 = [prototype copy];
    characterCopy1.name = @"Pike";

    Character *characterCopy2 = [prototype copy];
    characterCopy2.health = 120.0;

    NSLog(@"Prototype: %@", prototype);
    NSLog(@"characterCopy1: %@", characterCopy1);
    NSLog(@"characterCopy2: %@", characterCopy2);

网络请求

原型模式在网络请求中也可以使用,通常用于缓存请求结果以提高应用程序的性能。在某些情况下,应用程序需要从网络中获取大量的数据,并在应用程序中多次使用相同的数据。如果每次都从网络中获取数据,这将会降低应用程序的性能,并且可能会因为网络延迟导致应用程序的响应速度变慢。在这种情况下,使用原型模式来缓存请求结果可以提高应用程序的性能。

以下是使用AFNetworking实现原型模式的示例代码:

    @interface NetworkRequest : NSObject <NSCopying>

    @property (nonatomic, copy) NSString *urlString;
    @property (nonatomic, strong) NSDictionary *parameters;

    - (void)sendRequestWithSuccess:(void (^)(id responseObject))success
                           failure:(void (^)(NSError *error))failure;

    @end

    #import "NetworkRequest.h"
    #import <AFNetworking/AFNetworking.h>

    @implementation NetworkRequest

    - (void)sendRequestWithSuccess:(void (^)(id _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure {
        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        [manager
         GET:self.urlString
         parameters:self.parameters
         progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            if (success) {
                success(responseObject);
            }
        }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            if (failure) {
                failure(error);
            }
        }];
    }

    - (id)copyWithZone:(NSZone *)zone {
        NetworkRequest *copy = [[[self class] allocWithZone:zone] init];
        copy.urlString = self.urlString;
        copy.parameters = self.parameters;
        return copy;
    }

    @end

然后,创建一个NetworkRequestCache对象,用于缓存已经发送过的网络请求。

    @interface NetworkRequestCache : NSObject

    /// 缓存信息
    @property (nonatomic, strong) NSMutableDictionary *requestCache;

    + (instancetype)sharedCache;

    /// 缓存
    - (void)cacheRequest:(NetworkRequest *)request;

    - (NetworkRequest *)getCacheRequestForUrlString:(NSString *)urlString parameters:(NSDictionary *)parameters;

    @end

    @implementation NetworkRequestCache

    + (instancetype)sharedCache {
        static NetworkRequestCache *shareInstance = nil;
        static dispatch_once_t once_token;
        dispatch_once(&once_token, ^{
            shareInstance = [[NetworkRequestCache alloc] init];
        });
        return shareInstance;
    }

    - (instancetype)init {
        self = [super init];
        if (self) {
            self.requestCache = [NSMutableDictionary dictionary];
        }
        return self;
    }

    - (NSString *)cacheKeyForUrlString:(NSString *)urlString parameters:(NSDictionary *)parameters {
        NSMutableString *cacheKey = [NSMutableString stringWithString:urlString];
        NSArray *keys = [[parameters allKeys] sortedArrayUsingSelector:@selector(compare:)];
        for (NSString *key in keys) {
            id value = parameters[key];
            [cacheKey appendFormat:@":%@%@", key, value];
        }
        return [cacheKey copy];
    }

    - (void)cacheRequest:(NetworkRequest *)request {
        NSString *cacheKey = [self cacheKeyForUrlString:request.urlString parameters:request.parameters];
        [self.requestCache setObject:request forKey:cacheKey];
    }

    - (NetworkRequest *)getCacheRequestForUrlString:(NSString *)urlString parameters:(NSDictionary *)parameters {
        NSString *cache = [self cacheKeyForUrlString:urlString parameters:parameters];
        NetworkRequest *cacheRequest = [self.requestCache objectForKey:cache];
        if (cacheRequest) {
            return [cacheRequest copy];
        } else {
            return nil;
        }
    }

    @end

其中:

- (NSString *)cacheKeyForUrlString:(NSString *)urlString parameters:(NSDictionary *)parameters {
    NSMutableString *cacheKey = [NSMutableString stringWithString:urlString];
    NSArray *keys = [[parameters allKeys] sortedArrayUsingSelector:@selector(compare:)];
    for (NSString *key in keys) {
        id value = parameters[key];
        [cacheKey appendFormat:@":%@%@", key, value];
    }
    return [cacheKey copy];
}

cacheKeyForUr1String:parameters: 方法根据 URL 字符串和参数生成缓存键。它创建一个可变字符串,初始化为 URL 字符串,并按排序顺序附加每个参数键值对。缓存键的格式为urlString:keyivaluel:key2value2:..该方法返回缓存键字符串的副本。

    - (void)cacheRequest:(NetworkRequest *)request {
        NSString *cacheKey = [self cacheKeyForUrlString:request.urlString parameters:request.parameters];
        [self.requestCache setObject:request forKey:cacheKey];
    }

`cacheRequest:` 方法接受一个 `NetworkRequest` 对象作为参数,并将其缓存起来。它调用 `cacheKeyForUrlString:parameters:` 方法**生成该请求的缓存键**,并将请求对象与缓存键关联起来存储在 `_requestCache` 字典中。

    - (NetworkRequest *)getCacheRequestForUrlString:(NSString *)urlString parameters:(NSDictionary *)parameters {
        NSString *cache = [self cacheKeyForUrlString:urlString parameters:parameters];
        NetworkRequest *cacheRequest = [self.requestCache objectForKey:cache];
        if (cacheRequest) {
            return [cacheRequest copy];
        } else {
            return nil;
        }
    }

getRequestForUrlString:parameters: 方法根据提供的 URL 字符串和参数检索缓存的请求对象。它使用与 cacheRequest: 方法相同的逻辑生成缓存键。然后在 _requestCache 字典中查找与缓存键关联的请求对象。如果找到缓存的请求对象,则使用 copy 方法返回请求对象的副本,以确保原始的缓存请求对象保持不变。如果未找到缓存的请求,则返回 nil

使用:

    NetworkRequest *prototypeRequest = [[NetworkRequest alloc] init];
    NSString *urlString = @"https://api.example.com/data";
    NSDictionary *parameters = @{@"param1": @"value1", @"param2": @"value2"};
    prototypeRequest.urlString = urlString;
    prototypeRequest.parameters = parameters;
    // 复制
    NetworkRequest *newRequest = [prototypeRequest copy];
    newRequest.parameters = @{@"param3": @"value3"};
    [newRequest sendRequestWithSuccess:^(id  _Nonnull responseObject) {
        NSLog(@"Request succeeded with response: %@", responseObject);
    } failure:^(NSError * _Nonnull error) {
        NSLog(@"Request failed with error: %@", error);
    }];
    // 将请求对象缓存起来
    [[NetworkRequestCache sharedCache] cacheRequest:newRequest];
    // 获取缓存请求数据
    NetworkRequest *networkRequest = [[NetworkRequestCache sharedCache]
    getCacheRequestForUrlString:urlString parameters:parameters];

总体而言,NetworkRequestCache 类提供了一种根据 URL 字符串和参数缓存和检索 NetworkRequest 对象的机制。这样可以重复使用请求对象,并避免重复创建和配置网络请求对象。缓存功能有助于提高性能和效率,特别是在多次使用相同的 URL 和参数进行请求的情况下。

6 建造者模式(Builder Pattern)

builder-en.png

建造者模式(Builder Pattern)可以将对象的构建过程与其表示分离,从而使同样的构建过程可以创建不同的表示。这种模式通常用于需要复杂对象的构建过程,并且需要将构建过程的细节隐藏起来,以便于用户只需要关心所需的结果即可。

在建造者模式中,通常会定义一个Builder接口或者抽象类,其中包含了一系列构建复杂对象所需的方法。具体的构建器类实现了Builder接口或抽象类,并根据实际情况来实现每个方法,最终构建出一个具体的产品对象。同时,建造者模式还定义了一个Director类,用于协调Builder对象的构建过程,并且隔离客户端和产品对象的直接联系。

建造者模式通常适用于以下场景:

  1. 当创建一个复杂对象时,需要分步骤地进行创建,且需要控制每个步骤的顺序和内容时,可以使用建造者模式。
  2. 当需要创建的对象具有复杂的内部结构时,可以使用建造者模式来隔离复杂的对象创建过程,从而使其更易于理解和维护。
  3. 当需要创建多个具有相似属性的对象时,可以使用建造者模式来避免重复的构造代码,并且可以更容易地扩展和修改这些对象的构造过程。
  4. 当需要创建一个不可变的对象时,可以使用建造者模式来强制其必须通过构造器来创建,从而避免了对象在创建后被修改的可能性。

例子

举一个具体的例子,比如我们需要构建一份简历,其中包括个人信息、教育经历、工作经历等。如果使用建造者模式,我们可以将这份简历的构建过程分为多个步骤,并由具体的构建器来负责每个步骤的实现。这样做的好处是,我们可以根据不同的需求来构建不同风格的简历,而不需要在客户端代码中重复编写相似的代码。

    // Person 类
    @interface Person : NSObject

    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *email;
    @property (nonatomic, copy) NSString *phone;
    @property (nonatomic, copy) NSString *address;

    @end

    @implementation Person

    @end

    // Education 类
    @interface Education : NSObject

    @property (nonatomic, copy) NSString *degree;
    @property (nonatomic, copy) NSString *school;
    @property (nonatomic, copy) NSString *date;

    @end

    @implementation Education

    @end

    // WorkExperience 类
    @interface WorkExperience : NSObject

    @property (nonatomic, copy) NSString *company;
    @property (nonatomic, copy) NSString *position;
    @property (nonatomic, copy) NSString *date;

    @end

    @implementation WorkExperience

    @end

    // 建造者接口
    @protocol ResumeBuilderProtocol <NSObject>

    - (void)buildBasicInfoWithName:(NSString *)name email:(NSString *)email phone:(NSString *)phone address:(NSString *)address;
    - (void)buildEducationWithDegree:(NSString *)degree school:(NSString *)school date:(NSString *)date;
    - (void)buildWorkExperienceWithCompany:(NSString *)company position:(NSString *)position date:(NSString *)date;
    - (NSString *)getResult;
    @end

    // 简历建造者类
    @interface ResumeBuilder : NSObject <ResumeBuilderProtocol>

    @property (nonatomic, strong) Person *person;
    @property (nonatomic, strong) Education *education;
    @property (nonatomic, strong) WorkExperience *workExperience;

    @end

    @implementation ResumeBuilder

    - (void)buildBasicInfoWithName:(NSString *)name email:(NSString *)email phone:(NSString *)phone address:(NSString *)address {
        self.person = [Person new];
        self.person.name = name;
        self.person.email = email;
        self.person.phone = phone;
        self.person.address = address;
    }

    - (void)buildEducationWithDegree:(NSString *)degree school:(NSString *)school date:(NSString *)date {
        self.education = [Education new];
        self.education.degree = degree;
        self.education.school = school;
        self.education.date = date;
    }

    - (void)buildWorkExperienceWithCompany:(NSString *)company position:(NSString *)position date:(NSString *)date {
        self.workExperience = [WorkExperience new];
        self.workExperience.company = company;
        self.workExperience.position = position;
        self.workExperience.date = date;
    }

    - (NSString *)getResult {
        NSMutableString *result = [NSMutableString string];
        [result appendFormat:@"Name: %@\n", self.person.name];
        [result appendFormat:@"Email: %@\n", self.person.email];
        [result appendFormat:@"Phone: %@\n", self.person.phone];
        [result appendFormat:@"Address: %@\n", self.person.address];
        [result appendFormat:@"\n"];
        [result appendFormat:@"Education:\n"];
        [result appendFormat:@"Degree: %@\n", self.education.degree];
        [result appendFormat:@"School: %@\n", self.education.school];
        [result appendFormat:@"Date: %@\n", self.education.date];
        [result appendFormat:@"\n"];
        [result appendFormat:@"Work Experience:\n"];
        [result appendFormat:@"Company: %@\n", self.workExperience.company];
        [result appendFormat:@"Position: %@\n", self.workExperience.position];
        [result appendFormat:@"Date: %@\n", self.workExperience.date];
        return result;
    }

现在我们使用 ResumeDirector 类来调用建造者来创建简历,具体代码如下:

// 简历指导者类
@interface ResumeDirector : NSObject

@property (nonatomic, strong) id<ResumeBuilderProtocol> builder;

- (instancetype)initWithBuilder:(id<ResumeBuilder>)builder;
- (NSString *)construct;

@end

@implementation ResumeDirector

- (instancetype)initWithBuilder:(id<ResumeBuilder>)builder {
    if (self = [super init]) {
        _builder = builder;
    }
    return self;
}

- (NSString *)construct {
    [self.builder buildBasicInfoWithName:@"John"
                                   email:@"john@example.com"
                                   phone:@"1234567890"
                                 address:@"123 Main St."];
    [self.builder buildEducationWithDegree:@"Bachelor"
                                     school:@"University"
                                       date:@"2010-2014"];
    [self.builder buildWorkExperienceWithCompany:@"ABC Company"
                                        position:@"Software Engineer"
                                            date:@"2014-2016"];
    return [self.builder getResult];
}

@end

这个 ResumeDirector 类接受一个建造者对象,然后在 construct 方法中按照特定的顺序调用建造者的方法来构建简历。现在我们可以使用这个 ResumeDirector 来构建简历:

    id <ResumeBuilderProtocol> builder = [[ResumeBuilder alloc] init];
    ResumeDirector *director = [[ResumeDirector alloc] initWithBuilder:builder];
    NSString *str = [director construct];
    NSLog(@"builderPattern:%@", str);

输出结果为:

Name: John
Email: john@example.com
Phone: 1234567890
Address: 123 Main St.

Education:
Degree: Bachelor
School: University
Date: 2010-2014

Work Experience:
Company: ABC Company
Position: Software Engineer
Date: 2014-2016

可以看到,这个例子中,Director 类将简历的构建过程封装起来,客户端代码只需要通过 Director 来构建简历,而不用了解具体的建造者对象是如何构建简历的。 这样,当简历的构建过程发生变化时,只需要修改建造者和指导者类就可以了,而不需要修改客户端代码。这样,可以提高代码的可维护性和扩展性。

项目Demo

24-Design-Patterns

下一篇

24种设计模式代码实例学习(三)结构型模式