架构/设计

独孤九剑--设计模式(iOS创建型篇)

2023-02-27  本文已影响0人  _小沫

独孤九剑--设计模式(iOS结构型篇)
独孤九剑--设计模式(iOS行为型篇)

前言

如果把开发看做是武林世界,底层原理、算法等就是内功心法,那编程语言、设计模式等无疑是外功招式;只专注一门达到精通级别,有人也能独领风骚;内外兼修那才是至尊王者方能称霸武林;

设计模式就是独孤九剑,剑谱由前辈实践总结,一招一式精妙绝伦,照着修炼亦可大有所为;

设计模式

设计模式就是前人经过实践,归纳总结的一套针对特定问题的代码设计解决方案;它就是一套解决特定问题的模板,在遇到类似问题时,可以直接套用对应的模板,高效、高质量的完成开发;

作用
设计原则

设计模式进行设计时需要遵循六大设计原则:

UML图

学习、使用设计模式需要读懂UML图(侧重类图),有必要的还需要自己能画图;

类型

主流的设计模式有20多种,下面只介绍下在iOS中可能比较重要的一些设计模式

单例模式 Singleton Pattern

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

适用场景

单例模式不应滥用,单例模式的对象是唯一的且可以全局使用,涉及到数据更改容易导致数据错乱、线程不安全等问题,将很难维护;另一个问题是单例会隐性地让毫不相关的类产生耦合等问题;

单例模式应该是iOS开发者最熟悉、最常用的一种模式,很容易理解,这里就不多费笔墨;但是对于熟悉的东西往往容易忽略一些细节;

Objective-C单例的简单实现

@implementation CameraManager

+ (CameraManager *)defaultManager {
    static CameraManager *shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (shareInstance == nil) {
            shareInstance = [[CameraManager alloc] init];
        }
    });
    
    return  shareInstance;
}

@end

可能很多小伙伴编码就到此结束,但这里有个潜在的问题;其他语言,如C++,java等,构造方法可以隐藏。OC中的方法,实际上都是公开的,虽然我们提供了一个方便的类方法的访问入口,但是里面的alloc方法依旧是可以调用到的。也就是说依旧可以使用alloc的方式创建对象,每次alloc都是一个新的实例,也就违背单例模式只有一个实例的作用。

Objective-C单例的完善

  1. 无论是采用哪种方式创建,保证给出的对象是同一个;
    在对象创建的时候,无论是alloc还是new,都会调用到 allocWithZone方法。在通过拷贝创建对象时,都会会调用到copyWithZonemutableCopyWithZone方法。因此,可以重写这些方法,让创建的对象唯一。
+ (CameraManager *)defaultManager {
....
            shareInstance = [[super allocWithZone:nil] init];
....
    return  shareInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [CameraManager defaultManager];
}
  1. 通过其他方式创建对象时,直接抛错禁止创建
    可以是编译时抛错,还可以是运行时抛出异常;
@interface CameraManager : NSObject
+ (CameraManager *)defaultManager;

+ (instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));
+ (instancetype) new __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
@end
- (instancetype)init {
    NSAssert(NO, @"There can only be one CameraManager instance.");
    // 或者
//    [NSException raise:NSInternalInconsistencyException format:@"There can only be one CameraManager instance."];
    
    return nil;
}

系统库中就有很多单例应用:UIApplication、NSFileManager、NSNotificationCenter 等等...
实测发现NSFileManager、NSNotificationCenter都没有处理其他alloc的情况;UIApplication使用的是运行时抛错的方式禁用其他创建方式;

Swift单例的实现

基于语言特性,Swift实现单例将特别简单、完美

class CameraManager {
    static let defaultManager = CameraManager()
    
    private init() {}
}

工厂模式

工厂模式主要是为创建对象提供过渡接口,将对象的创建具体细节使用工厂类封装屏蔽起来,提高调用的灵活性;另外对象的创建和对象本身业务处理分离,降低了系统的耦合度,使得两者修改起来都相对容易;

工厂模式可以分为三类:简单工厂模式、工厂方法模式、抽象工厂模式

简单工厂模式 Simple Factory Pattern

简单工厂模式专门定义一个工厂类来负责创建其他类的实例,工厂类可以根据参数的不同返回不同类的实例。被创建的实例通常都具有共同的父类(或实现同一个接口/协议)。

UML类图
factory-simple.png
示例

App需要使用不同的外接相机,根据连接的wifi名区分不同相机并实例化:
Product: 相机公有协议:

@protocol CameraProtocol <NSObject>

@optional
- (void)connect;
- (void)take;

@end

ConcreteProduct: 各个具体相机对象

@implementation XhwCamera
- (void)connect {
    NSLog(@"xhw connect");
}

- (void)take {
    NSLog(@"xhw take");
}
@end

@implementation AzyCamera
- (void)connect {
    NSLog(@"azy connect");
}

- (void)take {
    NSLog(@"azy take");
}
@end
// 相机工厂类 负责创建不同相机对象 (对应Factory)
@implementation CameraFactory
+ (id<CameraProtocol>)createCameraWifi:(NSString *)wifi {
    if ([wifi isEqualToString:@"xhw"]) {
        return [[XhwCamera alloc] init];
    } else if ([wifi isEqualToString:@"azy"]) {
        return [[AzyCamera alloc] init];
    }
    
    return nil;
}
@end

调用方使用:

id<CameraProtocol> camera = [CameraFactory createCameraWifi:@"xhw"];
[camera connect];
[camera take];
适用场景

基于以上优缺点,简单工厂模式适用场景:

  1. 使用方只知道传入工厂类的参数,并不关心创建对象的逻辑时;
  2. 工厂类负责创建的产品类比较少时。

工厂方法模式 Factory Method Pattern

工厂方法模式是在简单工厂模式的基础上,进一步抽象化和推广的模式。工厂方法模式里不再只由一个工厂类决定实例化哪些产品类,而是把产品类的创建交给抽象工厂的子类去完成。

简单工厂模式中工厂类做了大量的创建逻辑,一个工厂耦合多个产品类;工厂方法模式抽象了工厂类,具体的工厂类只负责创建一个产品类,避免了简单工厂的一些缺点;

UML类图
factory-method.png

Product:抽象产品角色;ConcreteProduct:具体产品角色
Factory:抽象工厂角色;ConcreteFactory:具体工厂角色

示例

还是简单工厂那个例子,现在将其重构为工厂方法:

抽象工厂:

@protocol CameraFactoryProtocol <NSObject>
// 实例方法、类方法均可以

// 实例方法
//- (id<CameraProtocol>)createCamera;

// 类方法 创建具体的相机(产品)
+ (id<CameraProtocol>)createCamera;

@end

具体的工厂(有多少种产品就多少种工厂):

@implementation XhwCameraFactory
+ (id<CameraProtocol>)createCamera {
    return [[XhwCamera alloc] init];
}
@end


@implementation AzyCameraFactory
+ (id<CameraProtocol>)createCamera {
    return [[AzyCamera alloc] init];
}
@end

具体产品类不用更改;
调用方使用:

id<CameraProtocol>camera = [XhwCameraFactory createCamera];
[camera connect];
[camera take];
适用场景

基于以上优缺点,工厂方法模式适用场景:

  1. 基于简单工厂的适用场景;
  2. 工厂只会对应一种产品;
  3. 后续可能扩展产品

抽象工厂模式 Abstract Factory Pattern

工厂方法模式在一定程度上优化了简单工厂的缺点,但是其一个具体工厂只能创建一种具体产品;抽象工厂模式就是对工厂方法模式的进一步优化;
抽象工厂模式设计了多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类可以派生出多个具体工厂类。每个具体工厂类可以创建多个具体产品的实例;

UML类图
factory-abstrac.png

AbstractFactory:抽象工厂;ConcreteFactory:具体工厂
AbstractProduct:抽象产品;Product:具体产品

示例

基于工厂方法模式的例子,现在增加一个需求:除了连接相机外还需要支持连接不同厂家的音响设备;还是Xhw和Azy两个厂家,按照工厂方法模式的设计,那么就需要新建一套音响抽象工厂类、2个具体的音响工厂类、1个抽象产品类、2个具体产品类;调用方使用时,也同样需要先创建另一个工厂;
其实不管是相机设备,还是音响设备,虽然是不同产品但是都是相同厂家;相机和音响都属于外接设备,完全可以在高一层的抽象出外接设备工厂;这时相当于把相机和音响当做了一个产品族

实现代码:

  1. 抽象工厂 (更高抽象)
@protocol PeripheralFactory <NSObject>

// 外接设备目前支持 Camera、Speaker两种产品
- (id<CameraProtocol>)createCamera;
- (id<SpeakerProtocol>)createSpeaker;

@end
  1. 新建新增的抽象产品
@protocol SpeakerProtocol <NSObject>

- (void)connect;
- (void)play;

@end
  1. 具体工厂 (创建对应的产品)
@implementation XhwFactory
- (nonnull id<CameraProtocol>)createCamera {
    return [[XhwCamera alloc] init];
}

- (nonnull id<SpeakerProtocol>)createSpeaker {
    return [[XhwSpeaker alloc] init];
}
@end


@implementation AzyFactory
- (nonnull id<CameraProtocol>)createCamera {
    return [[AzyCamera alloc] init];
}

- (nonnull id<SpeakerProtocol>)createSpeaker {
    return [[AzySpeaker alloc] init];
}
@end

使用:

AzyFactory *fc = [[AzyFactory alloc] init];
id<CameraProtocol> camera = [fc createCamera];
[camera connect];
id<SpeakerProtocol> speaker = [fc createSpeaker];
[speaker connect];
[speaker play];
适用场景

先引入2个概念:

产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

基于以上优缺点,抽象工厂模式适用场景:

  1. 基于工厂方法模式的适用场景;
  2. 系统有多个系列产品,而系统中只消费其中某一系列产品

iOS系统中的工厂模式

iOS中有类簇的概率,其实它就是使用了工厂模式;
如 NSArray,NSMutableArray;NSNumber等;

    id arrAlloc = [NSArray alloc];
    NSArray *arr = [arrAlloc init];
    id marrAlloc = [NSMutableArray alloc];
    NSMutableArray *marr = [marrAlloc init];
    NSLog(@"%@", NSStringFromClass([arrAlloc class]));
    NSLog(@"%@", NSStringFromClass([arr class]));
    NSLog(@"%@", NSStringFromClass([marrAlloc class]));
    NSLog(@"%@", NSStringFromClass([marr class]));

输出结果:

__NSPlaceholderArray
__NSArray0
__NSPlaceholderArray
__NSArrayM

NSArray真实类型是__NSArray0,NSMutableArray真实类型
__NSArrayM;在alloc分配内存后,创建的是一个中间对象__NSPlaceholderArray,等到init时才会动态决定创建对应的对象;

Apple并没有开源Foundation源码,但是可以通过GNUStep的Foundation代码窥探其实现,可以发现的确是将创建对象的工作转交给了中间对象;

造者模式 Builder Pattern

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节;

UML类图
builder.png

Builder:抽象建造者,ConcreteBuilder:具体建造者
Director:指挥者,Product:产品角色

示例

我们再完善下上面示例的外接相机代码;
外接相机在connect前,需要保证内部模块都初始化完成;相机现在分为3大部分:拍摄模块,wifi模块,存储模块;

@interface XhwCamera : NSObject<CameraProtocol>

// 实际开发中这3部分比较复杂, 现在为了演示简单都使用字符串替代
@property (nonatomic, strong) NSString *shootModule;
@property (nonatomic, strong) NSString *wifiModule;
@property (nonatomic, strong) NSString *storageModule;

// 拍摄配置 如分辨率、帧率等
@property (nonatomic, strong) NSString *shootConfig;

@end

简单做法
在Camera对象init时,将这3部分一起初始化;
但想象下,如果shootModule,wifiModule,storageModule都比较复杂,需要写一大堆配置代码时Camera的init将也会臃肿复杂:

implementation XhwCamera

- (instancetype)init {
    //...
    //...
    _shootModule = ...
    
    //...
    //...
    _wifiModule = ...
}

当shootModule,wifiModule,storageModule需要调整依赖关系或者顺序关系时,还有当这些模块初始化需要由使用者传入参数时,XhwCamera将也很难维护;

使用建造者模式

  1. 抽象建造者,声明建造需要的接口
@protocol CameraBuilderProtocol <NSObject>

- (void)buildShootModule;       // 拍摄模块
- (void)buildWifiModule;       // wifi连接模块
- (void)buildStorageModule;   // 存储模块

// 返回构建对象
- (id<CameraProtocol>)build;

@end
  1. 具体建造者:xhw相机
@implementation XhwCameraBuilder

- (instancetype)init {
    if (self = [super init]) {
        _camera = [[XhwCamera alloc] init];
    }
    
    return self;
}

- (void)buildShootModule {
    _camera.shootConfig = @"默认配置";
    //.... 复杂过程
    _camera.shootModule = @"拍摄模块初始完成";
}

- (void)buildStorageModule {
    //.... 复杂过程
    _camera.storageModule = @"存储模块初始完成";
}

- (void)buildWifiModule {
    //.... 复杂过程
    _camera.wifiModule = @"wifi模块初始完成";
}

- (nonnull id<CameraProtocol>)build {
    return _camera;
}

@end
  1. 构建的Director,控制builder项及顺序、依赖等
@implementation CameraDirector

- (void)constuctBuilder:(id<CameraBuilderProtocol>)builder {
    [builder buildShootModule];
    [builder buildWifiModule];
    [builder buildStorageModule];
}

@end
  1. 使用
XhwCameraBuilder *builder = [[XhwCameraBuilder alloc] init];
CameraDirector *director = [[CameraDirector alloc] init];
[director constuctBuilder:builder];
id<CameraProtocol> camera = [builder build];
[camera connect];

完整的建造者模式
如果拍摄的配置需要由客户设置,而不是使用默认的;builder需要支持参数设置;

以拍摄增加 分辨率、帧率 配置为例,代码重构如下:

  1. builder增加设置的接口
- (void)setShootConfig:(NSString *)config {
    _camera.shootConfig = config;
}
  1. CameraDirector增加传入参数
- (void)constuctBuilder:(id<CameraBuilderProtocol>)builder resolution:(NSString *)resolution frameRate:(NSString *)frameRate {
    NSString *config = [NSString stringWithFormat:@"拍摄配置:分辨率=%@,帧率=%@", resolution, frameRate];
    // buildShootModule初始化需要在shootConfig之后 这个由Director控制了
    [builder setShootConfig:config];
    [builder buildShootModule];
    [builder buildWifiModule];
    [builder buildStorageModule];
}

另一种方式,可以为不同的配置创建不同的具体建造者;
在使用时,只需要更换不同的建造者就可以创建不同配置的产品实例;
比如新建一个 高配 的相机builder,相机所有配置都是用更高性能的设置,

@implementation XhwCameraHeightBuilder

- (void)buildShootModule {
    _camera.shootConfig = @"拍摄配置:分辨率=1280x720,帧率=30";
    _camera.shootModule = @"拍摄模块初始完成";
}

- (void)buildStorageModule {
    // ...默认高级配置
    _camera.storageModule = @"存储模块初始完成";
}
适用场景

基于以上优缺点,该模式适用场景:

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个子模块(或成员属性)。
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  3. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

完整代码


参考:
https://www.jianshu.com/p/6e5eda3a51af
https://design-patterns.readthedocs.io/zh_CN/latest/index.html

上一篇下一篇

猜你喜欢

热点阅读