项目Demo

在上一篇文章中,咱们介绍了创立型形式,在这一篇文章中,咱们将来介绍结构型形式。

结构型形式和创立型形式是规划形式中的两个首要分类。

创立型形式重视方针的创立机制,首要处理方针的实例化进程。创立型形式触及到方针的创立、组合和表明,以及躲藏方针的创立逻辑。

结构型形式重视的是方针之间的组合和关联办法,以构建更大的结构和功用。它们着重于类和方针之间的静态联系,以及如何将它们安排在一同构成更大的结构,以满意体系的需求。结构型形式能够协助咱们在坚持体系灵敏性和可扩展性的一同,供给明晰的方针安排结构

总的来说,创立型形式更重视的是方针创立的灵敏性和可保护性,结构型形式更重视的是体系结构的规划和安排。

0 常见的创立型形式

  1. 适配器形式(Adapter Pattern)
  2. 桥接形式(Bridge Pattern)
  3. 组合形式(Composite Pattern)
  4. 装修器形式(Decorator Pattern)
  5. 外观形式(Facade Pattern)
  6. 享元形式(Flyweight Pattern)
  7. 署理形式(Proxy Pattern)

这些结构型形式都有不同的运用场景和优势,能够依据详细的需求来挑选适宜的形式来改进软件规划的灵敏性、可保护性和可扩展性。

1 适配器形式(Adapter Pattern)

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

用于将一个类的接口转化成另一个类的接口,以便两个不兼容的类能够一同作业

适配器形式能够处理在体系中运用已有的类,但其接口与需求的接口不匹配的情况。

运用办法:适配器承继依靠已有的方针,完成想要的方针接口。用下面的两个比如说明这两种情况:

比如

承继

适配器能够经过承继已有的方针(旧接口),并完成方针接口来进行适配。经过承继,适配器获得了旧接口的功用,而且能够添加或重写办法来完成方针接口的要求

// 旧接口
@interface OldAPI : NSObject
- (void)legacyRequest;
@end
@implementation OldAPI
- (void)legacyRequest {
    NSLog(@"履行旧接口的恳求");
}
@end
// 方针接口
@protocol NewAPI <NSObject>
- (void)newRequest;
@end
// 适配器子类,承继自旧接口类并完成方针接口
@interface Adapter : OldAPI <NewAPI>
- (void)newRequest;
@end
@implementation Adapter
- (void)newRequest {
    [self legacyRequest]; // 调用旧接口的办法
    NSLog(@"履行适配后的新恳求");
}
@end

客户端代码:

id<NewAPI> api = [[Adapter alloc] init]; 
[api newRequest];

在这个比如中,OldAPI是旧的接口类,它有一个legacyRequest办法。NewAPI是方针接口,它界说了一个newRequest办法。适配器类Adapter承继自OldAPI,并完成了NewAPI接口。

经过承继,Adapter类承继了legacyRequest办法,然后在newRequest办法中调用了legacyRequest办法来履行旧接口的恳求,并添加了适配后的新恳求。

依靠

适配器也能够经过依靠已有的方针(旧接口),并将其作为成员变量来完成适配。适配器能够经过派遣调用已有方针的办法,并依据方针接口的要求对回来成果进行适配处理。

// 旧接口
@interface OldAPI : NSObject
- (void)legacyRequest;
@end
@implementation OldAPI
- (void)legacyRequest {
    NSLog(@"履行旧接口的恳求");
}
@end
// 方针接口
@protocol NewAPI <NSObject>
- (void)newRequest;
@end
// 适配器类,经过依靠旧接口类来完成适配
@interface Adapter : NSObject <NewAPI>
@property (nonatomic, strong) OldAPI *oldAPI;
- (void)newRequest;
@end
@implementation Adapter
- (void)newRequest {
    [self.oldAPI legacyRequest]; // 调用旧接口的办法
    NSLog(@"履行适配后的新恳求");
}
@end

客户端代码:

DependencyAdapter *adapter = [[DependencyAdapter alloc] init];
adapter.oldAPI = [[OldAPI alloc] init];
[adapter newRequest];

Adapter适配器类经过依靠旧接口类OldAPI来完成适配。适配器类持有一个OldAPI方针的实例,并完成了NewAPI接口。

在适配器的newRequest办法中,适配器经过调用持有的OldAPI方针的legacyRequest办法来履行旧接口的恳求,并在之后添加了适配后的新恳求的逻辑。

在客户端代码中,咱们创立了适配器类Adapter的实例,并将旧接口类OldAPI的实例设置为适配器类的oldAPI特点。然后调用适配器类的newRequest办法来建议新的恳求。

不管是承继仍是依靠,适配器都充当了一个中间层,经过转化和包装旧接口的功用,使其契合方针接口的要求。适配器将客户端的调用转化为对旧接口的调用,并对旧接口的回来成果进行适配,以满意客户端的希望。

适配器形式的缺陷:

  1. 添加代码杂乱性:适配器形式引进了额定的类和逻辑,添加了代码的杂乱性。适配器的存在或许会添加代码的了解和保护本钱。
  2. 运行时功用损耗:数据或办法的转化需求额定的处理步骤。
  3. 或许会导致过多的适配器类:在某些情况下,假如体系中存在很多不兼容的类,或许需求创立很多的适配器类来进行适配,这或许会导致类的数量过多,添加了体系的杂乱性。

总体而言,适配器形式在处理接口不兼容问题进步代码复用性方面具有明显的长处。但是,在规划和运用适配器时需求权衡好代码杂乱性、运行时功用以及适配器的数量,确保适配器形式的运用是合理的。

2 桥接形式(Bridge Pattern)

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

桥接形式用于把笼统化与完成化解耦,使得二者能够独立改动。他们别离后,能够独登时改动。桥接形式经过组合而不是承继来完成这种别离。

在桥接形式中,笼统部分(Abstraction)和完成部分(Implementation)别离界说了两个独立的类层次结构。笼统部分包括一个指向完成部分的引证,而且经过这个引证进行交互。这样,笼统部分就能够与不同的完成部分进行桥接,而不会与特定的完成部分耦合。

桥接形式中的“桥”便是协议接口,契合依靠倒转准则和面向接口的编程。

比如

简略比如

// 完成部分的接口
@protocol Implementor <NSObject>
- (void)doSomething;
@end
// 完成部分的详细完成类A
@interface ConcreteImplementorA : NSObject <Implementor>
@end
@implementation ConcreteImplementorA
- (void)doSomething {
    NSLog(@"Concrete Implementor A is doing something.");
}
@end
// 完成部分的详细完成类B
@interface ConcreteImplementorB : NSObject <Implementor>
@end
@implementation ConcreteImplementorB
- (void)doSomething {
    NSLog(@"Concrete Implementor B is doing something.");
}
@end
// 笼统部分
@interface Abstraction : NSObject
@property (nonatomic, strong) id<Implementor> implementor;
- (void)doAction;
@end
@implementation Abstraction
- (void)doAction {
    [self.implementor doSomething];
}
@end

客户端代码:

// 创立详细的完成方针A
ConcreteImplementorA *implementorA = [[ConcreteImplementorA alloc] init];
// 创立笼统部分方针,并将详细的完成方针A桥接进去
Abstraction *abstraction = [[Abstraction alloc] init];
abstraction.implementor = implementorA;
// 调用笼统部分的操作
[abstraction doAction];
// 创立详细的完成方针B
ConcreteImplementorB *implementorB = [[ConcreteImplementorB alloc] init];
// 将详细的完成方针B桥接进笼统部分方针
abstraction.implementor = implementorB;
// 调用笼统部分的操作
[abstraction doAction];

在上述代码中,咱们有一个完成部分的接口 Implementor,以及两个详细完成类 ConcreteImplementorAConcreteImplementorB。笼统部分 Abstraction 持有一个 Implementor 引证,并经过它调用完成部分的操作。

在客户端代码中,咱们首要创立了一个详细的完成方针 ConcreteImplementorA,然后创立了笼统部分方针 Abstraction,并将完成方针A桥接进去。当调用笼统部分的操作[abstraction doAction] 时,实践上是经过笼统部分与详细完成方针A的桥接,履行了完成方针A的详细操作,输出了 “Concrete Implementor A is doing something.”。

接下来,咱们创立了另一个详细的完成方针 ConcreteImplementorB,并将其桥接进笼统部分方针。再次调用笼统部分的操作 [abstraction doAction] 时,因为完成方针B替换了完成方针A的桥接,输出变为了 “Concrete Implementor B is doing something.”。

经过这个比如,咱们能够看到桥接形式的长处:笼统部分和完成部分能够独登时扩展和改动,它们之间的联系是经过桥接而不是承继建立的。 这种别离答应咱们在不影响其他部分的情况下修正笼统部分或完成部分,然后进步了体系的灵敏性和可扩展性。 在iOS开发中,桥接形式能够运用于以下方面:

  1. 网络恳求库: 当你需求进行网络恳求时,能够运用桥接形式将网络恳求库与详细的网络协议(如HTTP、WebSocket等)进行解耦。你能够界说一个笼统的网络恳求接口,并针对每种详细的网络协议完成一个详细的网络恳求类。经过桥接形式,能够灵敏地切换或添加新的网络协议完成。
  2. 视图操控器和视图之间的桥接: 在iOS运用中,视图操控器担任办理视图的显现和交互逻辑,而视图担任界面的展现。你能够运用桥接形式来将视图操控器与详细的视图完成进行解耦。经过界说一个笼统的视图接口,并针对不同的界面元素(如按钮、标签等)完成详细的视图类,能够完成视图操控器和视图的独立改动和组合。

网络恳求库

// 笼统的网络恳求接口
@protocol NetworkRequestProtocol <NSObject>
- (void)sendRequestWithURL:(NSURL *)url;
@end
// 详细的网络恳求类 - 运用HTTP协议
@interface HTTPRequest : NSObject <NetworkRequestProtocol>
@end
@implementation HTTPRequest
- (void)sendRequestWithURL:(NSURL *)url {
    NSLog(@"Sending HTTP request to URL: %@", url);
    // 发送HTTP恳求的详细完成
}
@end
// 详细的网络恳求类 - 运用WebSocket协议
@interface WebSocketRequest : NSObject <NetworkRequestProtocol>
@end
@implementation WebSocketRequest
- (void)sendRequestWithURL:(NSURL *)url {
    NSLog(@"Sending WebSocket request to URL: %@", url);
    // 发送WebSocket恳求的详细完成
}
@end
// 运用网络恳求的客户端类
@interface NetworkClient : NSObject
@property (nonatomic, strong) id<NetworkRequestProtocol> request;
- (void)makeRequestWithURL:(NSURL *)url;
@end
@implementation NetworkClient
- (void)makeRequestWithURL:(NSURL *)url {
    [self.request sendRequestWithURL:url];
}
@end

客户端代码:

NSURL *url = [NSURL URLWithString:@"https://example.com"];
NetworkClient *client = [[NetworkClient alloc] init];
// 运用HTTP协议发送恳求
client.request = [[HTTPRequest alloc] init];
[client makeRequestWithURL:url];
// 运用WebSocket协议发送恳求
client.request = [[WebSocketRequest alloc] init];
[client makeRequestWithURL:url];

在上述代码中,笼统的网络恳求接口 NetworkRequestProtocol 界说了发送恳求的办法 sendRequestWithURL:。详细的网络恳求类 HTTPRequestWebSocketRequest 别离完成了这个接口,每个类担任不同的网络协议的详细完成。

客户端类 NetworkClient 持有一个 NetworkRequestProtocol 的引证,并经过该引证进行网络恳求。经过在客户端中切换详细的恳求类,例如运用 HTTPRequestWebSocketRequest,能够在运行时切换不同的网络协议的完成。

视图操控器和视图之间的桥接

// 笼统的视图款式接口
@protocol ViewStyleProtocol <NSObject>
- (UIColor *)backgroundColor;
- (UIColor *)textColor;
@end
// 详细的浅色主题款式
@interface LightThemeStyle : NSObject <ViewStyleProtocol>
@end
@implementation LightThemeStyle
- (UIColor *)backgroundColor {
    return [UIColor whiteColor];
}
- (UIColor *)textColor {
    return [UIColor blackColor];
}
@end
// 详细的深色主题款式
@interface DarkThemeStyle : NSObject <ViewStyleProtocol>
@end
@implementation DarkThemeStyle
- (UIColor *)backgroundColor {
    return [UIColor blackColor];
}
- (UIColor *)textColor {
    return [UIColor whiteColor];
}
@end
// 视图操控器类
@interface ViewController : UIViewController
@property (nonatomic, strong) id<ViewStyleProtocol> viewStyle;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 创立并装备不同的视图款式
    self.viewStyle = [[LightThemeStyle alloc] init];
    // 设置视图布景色彩
    self.view.backgroundColor = [self.viewStyle backgroundColor];
    // 创立并装备视图控件,运用视图款式的文本色彩
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
    label.textColor = [self.viewStyle textColor];
    label.text = @"Hello, Bridge Pattern!";
    [self.view addSubview:label];
}
@end

客户端代码:

ViewStyleViewController *viewController = [[ViewStyleViewController alloc] init];
// 创立并装备不同的视图款式
viewController.viewStyle = [[DarkThemeStyle alloc] init];
// 设置视图布景色彩
viewController.view.backgroundColor = [viewController.viewStyle backgroundColor];
// 创立并装备视图控件,运用视图款式的文本色彩
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
label.textColor = [viewController.viewStyle textColor];
label.text = @"Hello, Bridge Pattern!";
[viewController.view addSubview:label];

在上述代码中,笼统的视图款式接口 ViewStyleProtocol 界说了获取布景色彩和文本色彩的办法。详细的浅色主题款式 LightThemeStyle 和深色主题款式 DarkThemeStyle 别离完成了这个接口,供给了不同主题的款式。

ViewController持有一个ViewStyleProtocol的引证viewStyle ,经过该引证能够获取视图款式的布景色彩和文本色彩。在 viewDidLoad 办法中,依据当时的视图款式设置视图的布景色彩,并创立并装备相应的视图控件。

在客户端代码中,咱们创立一个 ViewController 方针,并经过装备 viewStyle 特点和设置视图的布景色彩来运用不同的视图款式。

经过桥接形式,咱们能够在运行时挑选不同的视图款式,而不需求修正视图操控器的其他部分。这种规划使得图操控器与视图款式之间解耦,供给了更好的可扩展性和灵敏性。

3 组合形式(Composite Pattern)

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

组合形式能够将方针组合成树形结构,以表明”整体-部分”的层次结构。组合形式使得客户端能够以一致的办法处理单个方针和组合方针,然后使得代码愈加简练和可扩展。

在组合形式中,有两种根本类型的方针:叶节点(Leaf)和组合节点(Composite)。叶节点表明树结构中的最小单位,它们没有子节点。而组合节点则包括叶节点和其他组合节点,构成了树结构的层次联系。

比如

下面这个比如中将会演示如何运用组合形式来表明一个文件体系的树形结构。

首要,界说一个笼统基类 FileSystemComponent,它包括了一同的行为和特点,包括称号、添加子节点、删除子节点、获取子节点等:

// FileSystemComponent.h
@interface FileSystemComponent : NSObject
@property (nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name;
- (void)addComponent:(FileSystemComponent *)component;
- (void)removeComponent:(FileSystemComponent *)component;
- (NSArray<FileSystemComponent *> *)getChildren;
@end
// FileSystemComponent.m
@implementation FileSystemComponent
- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}
- (void)addComponent:(FileSystemComponent *)component {
    // 默许完成为空,只要组合节点需求完成该办法
}
- (void)removeComponent:(FileSystemComponent *)component {
    // 默许完成为空,只要组合节点需求完成该办法
}
- (NSArray<FileSystemComponent *> *)getChildren {
    // 默许完成为空,只要组合节点需求完成该办法
    return nil;
}
@end

然后,界说叶节点 File 类,表明文件:

// File.h
@interface File : FileSystemComponent
@end
// File.m
@implementation File
@end

接下来,界说组合节点 Directory 类,表明文件夹:

// Directory.h
@interface Directory : FileSystemComponent
@end
// Directory.m
@implementation Directory {
    NSMutableArray<FileSystemComponent *> *_children;
}
- (instancetype)initWithName:(NSString *)name {
    self = [super initWithName:name];
    if (self) {
        _children = [NSMutableArray array];
    }
    return self;
}
- (void)addComponent:(FileSystemComponent *)component {
    [_children addObject:component];
}
- (void)removeComponent:(FileSystemComponent *)component {
    [_children removeObject:component];
}
- (NSArray<FileSystemComponent *> *)getChildren {
    return [_children copy];
}
@end

现在,咱们能够运用组合形式来构建一个文件体系的树形结构。例如:

Directory *root = [[Directory alloc] initWithName:@"root"];
Directory *documents = [[Directory alloc] initWithName:@"Documents"];
File *readme = [[File alloc] initWithName:@"readme.txt"];
[documents addComponent:readme];
[root addComponent:documents];
Directory *pictures = [[Directory alloc] initWithName:@"Pictures"];
File *photo1 = [[File alloc] initWithName:@"photo1.jpg"];
File *photo2 = [[File alloc] initWithName:@"photo2.jpg"];
[pictures addComponent:photo1];
[pictures addComponent:photo2];
[root addComponent:pictures];
[root addComponent:[[File alloc] initWithName:@"notes.txt"]];

在上面的代码中,咱们创立了一个文件体系的树形结构。root 是根目录,包括了 Documents 文件夹和 Pictures 文件夹,以及一个名为 notes.txt 的文件。Documents 文件夹中包括一个名为 readme.txt 的文件,而 Pictures 文件夹中包括两个图片文件 photo1.jpgphoto2.jpg

经过组合形式,咱们能够以一致的办法处理文件和文件夹,不管它们是叶节点仍是组合节点。例如,咱们能够经过递归遍历整个文件体系来打印出一切的文件和文件夹:

- (void)printFileSystem:(FileSystemComponent *)component  {
    NSLog(@"%@", component.name);
    if ([component isKindOfClass:[Directory class]]) {
        Directory *directory = (Directory *)component;
        NSArray<FileSystemComponent *> *children = [directory getChildren];
        for (FileSystemComponent *child in children) {
            [self printFileSystem:child];
        }
    }
}

调用:

[self printFileSystem:root];

经过上述代码,咱们能够遍历整个文件体系,并打印出文件和文件夹的结构。

组合形式的长处是,它简化了对整体和部分之间的处理。客户端能够一致地对待单个方针和组合方针,而不需求关怀它们的详细类型。这种一致性使得代码愈加灵敏和可扩展,能够轻松地添加、删除和修正组合节点,而无需修正客户端代码

在iOS开发中,组合形式能够在多种场景中运用。下面是一些常见的运用场景:

  1. 视图层次结构:在iOS中,视图层次结构是一个典型的树形结构。UIView类能够看作是组合形式中的组合节点,它能够包括其他UIView实例作为子视图(叶节点)。经过运用组合形式,能够以一致的办法处理视图层次结构中的单个视图和组合视图。
  2. 文件体系操作:在处理文件体系相关操作时,组合形式也非常有用。例如,你能够运用组合形式来构建一个文件办理器运用程序,以便处理文件和文件夹的创立、删除、复制等操作。组合形式使得你能够一致地对待文件和文件夹,不管是单个文件仍是包括其他文件和文件夹的文件夹。
  3. 数据结构处理:在处理具有层次结构的数据时,组合形式能够派上用场。例如,你或许需求处理一个包括多级分类的数据调集,每个分类又能够包括子分类。经过运用组合形式,能够轻松地表明和操作这种层次结构的数据。

也即:UIView中常用的addSubview办法其实便是组合形式。

经过这个办法,咱们能够将一个 UIView 方针作为子视图添加到另一个 UIView 方针中,构成视图层次结构。

在组合形式中,UIView 方针能够看作是组合形式中的组合节点,它能够包括其他 UIView 方针作为子视图(叶节点)。经过运用 addSubview: 办法,咱们能够将叶节点视图添加到组合节点视图中,构建杂乱的视图层次结构。

组合形式具有以下长处:

  1. 一致接口:组合形式供给了一个一致的接口,使得客户端能够以相同的办法处理单个方针和组合方针。这简化了客户端代码,并进步了代码的可读性和可保护性
  2. 简化客户端代码:运用组合形式,客户端不需求关怀处理单个方针仍是组合方针,能够经过一致的接口直接操作整个方针层次结构。这样能够简化客户端代码,削减条件判别和类型查看的逻辑。
  3. 可扩展性:组合形式支持动态的添加、删除和修正方针,使得方针层次结构能够很容易地进行扩展和改动。这种灵敏性使得咱们能够构建杂乱的方针结构,并在不影响现有代码的情况下进行修正和调整。
  4. 递归处理:组合形式运用递归的办法处理整个方针层次结构,能够方便地对整个层次结构进行遍历和操作。

组合形式也存在一些缺陷:

  1. 约束类型的组合:在组合形式中,叶节点和组合节点具有相同的接口,但是并不意味着一切操作关于叶节点和组合节点都是有意义的。有些操作或许只适用于叶节点,而有些操作或许只适用于组合节点。
  2. 方针层次结构的杂乱性:跟着方针层次结构的添加,特别是在处理大型和杂乱的层次结构时,组合形式或许会导致方针层次结构变得杂乱和难以了解。需求合理地区分方针的责任和功用,以防止层次结构的过度杂乱化。
  3. 或许带来功用丢失:因为组合形式触及递归操作和遍历整个方针层次结构,或许会在一些情况下带来一定的功用丢失。特别是在处理大型层次结构时,需求细心考虑功用问题,并依据实践情况进行优化。

总的来说,组合形式在处理方针层次结构时供给了一种简练和灵敏的办法,但在运用时需求留意合理区分方针的责任和功用,以及在功用方面的考虑。依据详细的运用场景和需求,权衡利弊后挑选是否 运用组合形式。

4 装修器形式(Decorator Pattern)

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

装修器形式最大的特点便是在不改动现有方针结构的情况下,动态地向方针添加新的行为或功用

装修器形式经过将方针包装在一个装修器方针中,来扩展方针的功用。

在iOS开发中,装修器形式常常用于以下情况:

  1. 动态扩展方针的功用:当咱们需求在不修正现有代码的情况下,为一个方针添加额定的功用时,能够运用装修器形式。这样能够防止修正原有代码,一同完成功用的灵敏组合。
  2. 方针功用的组合:当一个方针或许有多个功用组合的情况下,装修器形式能够让咱们经过不同的装修器进行功用的组合。

装修器形式也能够与其他规划形式结合运用,例如结合工厂形式来创立装修器方针。

比如

咱们就来举比如展现装修器形式和工厂形式结合运用能够完成更灵敏的方针创立和装修进程:

首要,咱们界说一个笼统的形状接口 Shape

@protocol Shape <NSObject>
- (void)draw;
@end

然后,咱们创立详细的形状类,如矩形和圆形:

@interface Rectangle : NSObject <Shape>
@end
@implementation Rectangle
- (void)draw {
    NSLog(@"制作矩形");
}
@end
@interface Circle : NSObject <Shape>
@end
@implementation Circle
- (void)draw {
    NSLog(@"制作圆形");
}
@end

接下来,咱们创立详细的装修器类 ColorDecoratorBorderDecorator,它们完成了 Shape 接口,并在 draw 办法中添加了色彩和边框的制作:

@interface ColorDecorator : NSObject <Shape>
@property (nonatomic, strong) id<Shape> shape;
- (instancetype)initWithShape:(id<Shape>)shape;
@end
@implementation ColorDecorator
- (instancetype)initWithShape:(id<Shape>)shape {
    self = [super init];
    if (self) {
        self.shape = shape;
    }
    return self;
}
- (void)draw {
    [self.shape draw];
    NSLog(@"添加色彩");
}
@end
@interface BorderDecorator : NSObject <Shape>
@property (nonatomic, strong) id<Shape> shape;
- (instancetype)initWithShape:(id<Shape>)shape;
@end
@implementation BorderDecorator
- (instancetype)initWithShape:(id<Shape>)shape {
    self = [super init];
    if (self) {
        self.shape = shape;
    }
    return self;
}
- (void)draw {
    [self.shape draw];
    NSLog(@"添加边框");
}
@end

接下来,咱们界说一个装修器工厂类 ShapeDecoratorFactory,用于创立装修器方针:

@interface ShapeDecoratorFactory : NSObject
+ (id<Shape>)decoratedShapeWithType:(NSString *)type;
@end
@implementation ShapeDecoratorFactory
+ (id<Shape>)decoratedShapeWithType:(NSString *)type {
    id<Shape> shape;
    if ([type isEqualToString:@"rectangle"]) {
        shape = [[Rectangle alloc] init];
    } else if ([type isEqualToString:@"circle"]) {
        shape = [[Circle alloc] init];
    }
    // 创立装修器方针,并回来装修后的形状
    shape = [[ColorDecorator alloc] initWithShape:shape];
    shape = [[BorderDecorator alloc] initWithShape:shape];
    return shape;
}
@end

客户端代码:

id<Shape> rectangle = [ShapeDecoratorFactory decoratedShapeWithType:@"rectangle"];
[rectangle draw];
id<Shape> circle = [ShapeDecoratorFactory decoratedShapeWithType:@"circle"];
[circle draw];

接下来,咱们能够运用装修器工厂类 ShapeDecoratorFactory 来创立装修后的形状方针。以下是一个示例的运用代码:

objectiveCopy code
id<Shape> rectangle = [ShapeDecoratorFactory decoratedShapeWithType:@"rectangle"];
[rectangle draw];
id<Shape> circle = [ShapeDecoratorFactory decoratedShapeWithType:@"circle"];
[circle draw];

输出成果将会是:

制作矩形
添加色彩
添加边框
制作圆形
添加色彩
添加边框

装修器形式具有以下长处:

  1. 动态扩展功用:装修器形式答应在不修正现有代码的情况下,动态地为方针添加额定的功用。经过装修器形式,能够在运行时对方针进行灵敏的功用组合,完成功用的动态扩展。
  2. 遵从敞开封闭准则:装修器形式能够遵从敞开封闭准则,即对扩展敞开,对修正封闭。经过装修器形式,能够经过添加装修器来扩展方针的功用,而无需修正原有方针的代码。
  3. 防止承继的杂乱性:装修器形式经过组合和委托的办法,防止了运用很多的子类来完成功用的扩展。相比于承继,装修器形式愈加灵敏,而且能够在运行时动态地组合功用。
  4. 单一责任准则:装修器形式能够将功用的细粒度别离,每个装修器类只重视特定的功用扩展,使得每个类都具有单一责任。

但是,装修器形式也存在一些缺陷:

  1. 添加杂乱性:运用装修器形式会添加额定的类和方针,导致代码结构变得愈加杂乱,了解和保护本钱添加。
  2. 装修器顺序依靠:假如装修器的顺序不正确,或许会影响功用的正确履行。特别是当多个装修器一同修正同一个办法时,装修器的顺序非常重要。
  3. 不适合很多装修器的情况:假如需求运用很多的装修器来装修方针,会导致装修器的层级嵌套过深,代码变得杂乱且难以保护。

因而,假如需求动态扩展方针的功用且坚持代码的灵敏性,装修器形式是一种有效的规划形式挑选。

5 外观形式(Facade Pattern)

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

外观形式供给了一个简略而一致的接口,用于拜访杂乱体系中的一组接口。

外观形式躲藏了体系的杂乱性,并为客户端供给了一个更简略和更直接的拜访办法。它能够将一个杂乱的子体系封装起来,客户端只需求经过与外观方针进行交互,而无需直接与子体系中的各个方针进行交互

比如

在iOS开发中,外观形式常用于简化杂乱的体系或框架,并供给一个更简略的接口供客户端运用。

也便是说,在运用第三方库的运用中,对第三方库进行封装,便于客户端的运用,便是外观形式的一种体现。

这儿以封装盛行的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 <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);
        }
    }];
}
@end

网络恳求工厂:

@interface NetworkRequestFactory : NSObject
+ (NetworkManager *)creatNetworkManager;
@end
@implementation NetworkRequestFactory
+ (NetworkManager *)creatNetworkManager {
    return [NetworkManager shareManager];
}

现在,咱们能够经过工厂类NetworkManagerFactory来创立NetworkManager方针,并运用外观形式供给的一致接口来进行网络恳求。以下是运用示例:

NetworkManager *networkManager = [NetworkRequestFactory creatNetworkManager];
[networkManager requestURL:@"http://example.com/api"
                      type:NetworkManagerRequestTypeGet
                parameters:nil
                  progress:nil
                   success:^(NSURLSessionDataTask *task, id responseObject) {
                       // 恳求成功处理
                   }
                   failure:^(NSURLSessionDataTask *task, NSError *error) {
                       // 恳求失利处理
                   }];

外观形式有以下长处:

  1. 简化接口:外观形式供给了一个简化的接口,躲藏了底层体系的杂乱性,使得客户端更容易运用和了解体系功用。
  2. 解耦合:外观形式将客户端与底层体系解耦,客户端只需求经过外观类与体系进行交互,而不需求直接与体系的各个子组件进行交互。
  3. 进步灵敏性:经过外观类作为中间层,能够灵敏地修正底层体系的完成细节,而不影响客户端的代码。
  4. 进步可保护性:外观形式将底层体系的杂乱性封装在一个类中,使得体系结构愈加明晰,易于了解和保护。

外观形式也有一些缺陷:

  1. 不契合开闭准则当底层体系发生改动时,或许需求修正外观类的代码,这违反了对修正封闭的准则。
  2. 或许引进功用问题:外观形式的封装会添加一层额定的直接调用,或许会对体系的功用产生一定影响。
  3. 或许导致过度规划:过度运用外观形式或许导致体系结构过于杂乱,添加不必要的代码杂乱性和保护本钱。

6 享元形式(Flyweight Pattern)

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

享元形式旨在经过同享方针削减内存运用和进步功用。它适用于存在很多类似方针的情况,经过同享这些方针的公共部分来削减内存耗费

在享元形式中,方针分为两种类型:内部状况(Intrinsic State)和外部状况(Extrinsic State)。内部状况是能够被同享的,它包括方针的固有特点,不会因外部环境的改动而改动。外部状况是不可同享的,它取决于方针被运用的上下文。

在iOS开发中,享元形式能够运用于以下场景:

  1. 视图复用:在运用UITableViewUICollectionView等可重用视图的情况下,能够运用享元形式来同享可重用视图的实例。这样能够防止频频地创立和毁掉视图方针,进步翻滚功用和响应速度。
  2. 图画缓存:在需求频频加载和显现很多图画的运用中,能够运用享元形式来同享已加载的图画实例。这样能够防止重复加载相同的图画,削减内存耗费,并进步功用。
  3. 字符串常量池:在运用中运用的字符串常量(例如过错提示、网络恳求URL等)能够被视为享元方针。经过同享这些字符串常量的实例,能够节省内存并进步字符串比较的效率。

总归,享元形式在iOS开发中能够用于任何需求同享很多类似方针下降内存耗费的场景。

比如

视图复用

首要是咱们熟知的UITableViewUICollectionView

UITableView会依据需求复用Cell方针来显现不同的数据行,以节省内存和进步功用。

UITableView的完成中,每个可见的Cell都是一个UITableViewCell方针。当用户翻滚UITableView时,旧的Cell会被回收并用于展现新的数据行。这种复用机制正是享元形式的一种运用。

下面是一个简略的示例,展现了如安在UITableView中运用享元形式:

@interface CustomTableViewCell : UITableViewCell
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIImageView *iconImageView;
@end
@implementation CustomTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // 初始化Cell的子视图,例如titleLabel和iconImageView
    }
    return self;
}
@end

在上述代码中,CustomTableViewCell是自界说的UITableViewCell子类,它包括了Cell的内部状况,例如titleLabeliconImageView。这些视图将在每个复用的Cell上显现不同的数据。

UITableView的数据源办法cellForRowAtIndexPath:中,咱们能够运用享元形式来获取可复用的Cell方针并设置相应的数据:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *reuseIdentifier = @"CustomCell";
    CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (cell == nil) {
        cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
    }
    // 设置Cell的数据
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}
- (void)configureCell:(CustomTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    // 依据indexPath设置Cell的数据,例如标题和图画
    cell.titleLabel.text = [self.dataArray[indexPath.row] title];
    cell.iconImageView.image = [UIImage imageNamed:[self.dataArray[indexPath.row] imageName]];
}

标识符:在享元形式中,内部状况用于标识方针并进行同享。关于相同的内部状况,应该回来同一个同享方针。

在上述代码中,咱们运用dequeueReusableCellWithIdentifier:办法来获取可复用的Cell方针。假如没有可复用的Cell方针,则创立一个新的Cell方针并将其注册到重用行列中。

经过运用这种办法,UITableView会自动复用之前滚出屏幕的Cell方针,只需更新Cell上的数据,而不需求每次翻滚都创立新的Cell方针。这样能够大大削减内存耗费,并进步UITableView的功用。

总结:UITableViewCell复用机制是享元形式的一种运用,它经过复用Cell方针来下降内存耗费和进步功用。经过重用相同类型的Cell,咱们能够防止频频创立和毁掉Cell方针,然后优化UITableView的翻滚功用和响应速度。

图画缓存

当需求频频加载和显现很多图画的运用中,运用享元形式能够有效地进行图画缓存,防止重复加载相同的图画,削减内存耗费,并进步功用。

下面是一个简略的示例,展现了如何运用享元形式进行图画缓存:

@interface ImageCache : NSObject
@property (nonatomic, strong) NSCache *cache;
+ (instancetype)sharedCache;
- (UIImage *)getImageWithName:(NSString *)imageName;
- (void)setImage:(UIImage *)image withName:(NSString *)imageName;
@end
@implementation ImageCache
+ (instancetype)sharedCache {
    static ImageCache *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ImageCache alloc] init];
        sharedInstance.cache = [[NSCache alloc] init];
    });
    return sharedInstance;
}
- (UIImage *)getImageWithName:(NSString *)imageName {
    return [self.cache objectForKey:imageName];
}
- (void)setImage:(UIImage *)image withName:(NSString *)imageName {
    [self.cache setObject:image forKey:imageName];
}
@end

在上述代码中,咱们创立了一个ImageCache类,它作为图画的享元工厂和缓存。ImageCache类运用NSCache来存储和办理图画实例。

在运用中需求加载和显现图画时,能够运用ImageCache来获取图画实例:

ImageCache *imageCache = [ImageCache sharedCache];
UIImage *image = [imageCache getImageWithName:@"image1"];
if (image == nil) {
    // 假如缓存中没有图画,则加载图画并存储到缓存中
    image = [UIImage imageNamed:@"image1"];
    [imageCache setImage:image withName:@"image1"];
}

在上面的比如中,cache便是是缓存池.

在上述代码中,咱们首要经过sharedCache办法获取单例的ImageCache实例。然后,咱们运用getImageWithName:办法来获取指定称号的图画实例。假如缓存中没有该图画实例,则加载图画并将其存储到缓存中。

在后续的代码中,咱们能够运用获取到的图画实例进行显现操作,而不需求重复加载相同的图画。经过运用图画缓存,咱们防止了重复加载图画,削减了内存耗费,并进步了图画加载的功用和响应速度

享元形式具有以下长处:

  1. 削减内存耗费:经过同享方针实例,能够大大削减体系中类似方针的数量,然后下降内存耗费。同享的方针能够被多个客户端一同运用,防止了创立很多类似方针的开支。
  2. 进步功用:因为削减了方针的数量,享元形式能够进步体系的功用。重复运用同享方针能够防止频频的方针创立和毁掉操作,然后削减了体系的开支。
  3. 状况外部化:享元形式将方针的内部状况和外部状况别离。内部状况是能够同享的,而外部状况是改动的,能够依据需求传递给享元方针。这种状况的外部化能够简化方针的逻辑,使方针更易于运用和保护。
  4. 进步可保护性和可扩展性:经过将方针的状况外部化,享元形式使得方针的逻辑愈加明晰和简练。这样能够进步代码的可保护性和可扩展性,使体系更容易了解和修正。

但是,享元形式也有一些约束和缺陷:

  1. 同享方针的状况必须是可同享的:享元形式要求方针的内部状况能够被同享,而外部状况能够在运用时传递。假如方针的状况不可同享,那么无法运用享元形式来完成方针的同享和复用。
  2. 添加了体系的杂乱性:享元形式将方针分为内部状况和外部状况,而且需求额定的逻辑来办理同享方针的创立和拜访。这样会添加体系的杂乱性,需求细心考虑方针的状况和同享机制的规划。
  3. 或许导致线程安全问题:假如多个线程一同拜访和修正同享方针的状况,或许会导致线程安全问题。在运用享元形式时,需求留意对同享方针的状况进行正确的同步和办理,以防止并发拜拜访题

综上所述,享元形式适用于需求同享很多类似方针,而且能够供给内存和功用优化的场景。

7 署理形式(Proxy Pattern)

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

署理形式经过创立一个署理方针来操控对另一个方针的拜访。

署理方针充当原始方针的中间人,客户端经过署理方针与原始方针进行交互,署理方针能够在不修正原始方针的情况下添加额定的功用

署理形式的首要意图是完成对方针的直接拜访,以供给更灵敏的操控和扩展。署理形式常见的运用场景包括:

  1. 长途署理:答应经过网络拜访长途方针,躲藏了底层的杂乱性
  2. 虚拟署理:用于懒加载方针,只要在需求时才会创立实在方针。
  3. 安全署理:操控对方针的拜访权限
  4. 智能署理:在拜访方针时履行额定的逻辑,如缓存、日志记载等。

看到这儿,咱们会自然想到其他两种类似的形式:适配器形式装修器形式,他们都是尽量不使客户端和原完成层进行直接通信,经过额定的一层进行沟通,在这一层中,或许还会添加其他功用,那么他们之间不同在哪里呢?

署理形式的首要意图是供给对方针的直接拜访,并操控对原始方针的拜访。署理方针充当了原始方针的代表,能够在不修正原始方针的情况下添加额定的功用或操控拜访。

装修器形式的首要意图是动态地为方针添加额定的功用,而不改动其接口。

适配器形式的首要意图是将一个接口转化成另一个接口,以使得本来不兼容的类能够一同作业。

能够看到,署理形式最大的不同是:操控拜访。不像适配器形式相同,改动接口;也不像装修器形式相同,重点在于添加功用,而且一个装修器还能对多个原始现层代码,随时改动。

比如

假定咱们有一个下载器方针(Downloader)担任从互联网上下载文件。现在咱们希望在每次下载前后记载下载日志。咱们能够运用署理形式来完成:

// Downloader.h
@protocol Downloader <NSObject>
- (void)downloadFile:(NSString *)url;
@end
// RealDownloader.h
#import "Downloader.h"
@interface RealDownloader : NSObject <Downloader>
@end
// RealDownloader.m
#import "RealDownloader.h"
@implementation RealDownloader
- (void)downloadFile:(NSString *)url {
    NSLog(@"RealDownloader: Downloading file from %@", url);
}
@end
// DownloaderProxy.h
#import "Downloader.h"
@interface DownloaderProxy : NSObject <Downloader>
@property (nonatomic, strong) RealDownloader *realDownloader;
@end
// DownloaderProxy.m
#import "DownloaderProxy.h"
@implementation DownloaderProxy
- (void)downloadFile:(NSString *)url {
    NSLog(@"DownloaderProxy: Preparing to download file from %@", url);
    if (self.realDownloader == nil) {
        self.realDownloader = [[RealDownloader alloc] init];
    }
    [self.realDownloader downloadFile:url];
    NSLog(@"DownloaderProxy: File download complete");
}
@end

客户端代码:

id<Downloader> downloader = [[DownloaderProxy alloc] init]; 
[downloader downloadFile:@"http://example.com/file.txt"];

在上述示例中,Downloader是一个协议,界说了下载文件的办法downloadFile:RealDownloader是实践履行下载操作的方针,它完成了Downloader协议。DownloaderProxy是署理方针,也完成了Downloader协议。

当客户端调用署理方针的downloadFile:办法时,署理方针在下载前后别离输出日志信息,并将实践的下载操作委托给RealDownloader方针履行。

署理形式有以下长处:

  1. 阻隔客户端和实在方针:署理形式经过署理方针将客户端与实在方针阻隔开来,客户端不直接与实在方针交互,而是经过署理方针进行直接拜访。这样能够削减客户端与实在方针之间的耦合,进步体系的灵敏性和可保护性。
  2. 操控对实在方针的拜访:署理方针能够在拜访实在方针之前或之后履行额定的操作,例如权限操控、缓存、推迟加载等。这样能够对实在方针的拜访进行操控和办理,添加了体系的安全性和可控性
  3. 供给额定的功用:署理方针能够在不修正实在方针的情况下,扩展或增强其功用。例如,署理方针能够添加日志记载、功用计算、异常处理等功用,然后为客户端供给更多的服务。

署理形式也有一些缺陷:

  1. 添加了体系杂乱性:引进署理方针会添加体系中的类和方针数量,导致体系的杂乱性添加。过多的署理方针或许会使代码结构变得杂乱,难以了解和保护。
  2. 添加了恳求的处理时间:因为署理形式引进了额定的方针,每个恳求都需求经过署理方针的处理,或许会添加一定的处理时间。
  3. 或许下降直接拜访的效率:因为署理形式是经过直接拜访完成的,所以在直接拜访实在方针时或许会下降一些效率。

项目Demo

24-Design-Patterns