【iOS】工厂模式

文章目录

  • 前言
  • 设计模式的三大原则
  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式
  • 关于三兄弟的升级与降级
  • 注意


前言

上文讲完了iOS的架构模式,接下来聊一聊设计模式,设计模式有许多,主要介绍一下工厂模式

设计模式的三大原则

  • S 单一职责原则
    告诉我们实现类要职责单一;
    例如UIView负责处理事件传递响应,CALayer负责动画与视图的显示
  • O 开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
// 基本的绘制类
@interface Shape : NSObject
- (void)draw;
@end// 扩展新的形状,无需修改现有代码
@interface Circle : Shape
@end@implementation Circle
- (void)draw {// 绘制圆形的逻辑
}
@end
  • L 里氏替换原则告诉我们不要破坏继承体系;
@interface Bird : NSObject
- (void)fly;
@end@interface Duck : Bird
@end@implementation Duck
- (void)fly {// 实现飞行
}
@end
  • D 迪米特法则。 一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
// 班级类
@interface Class : NSObject
- (NSArray *)getAllStudents;
@end// 学校类
@interface School : NSObject
@property (strong, nonatomic) Class *class;
- (NSArray *)getAllStudentsInClass;
@end@implementation School
- (NSArray *)getAllStudentsInClass {return [self.class getAllStudents];
}
@end
  • I 接口隔离原则: 使用多个专门的协议、而不是一个庞大臃肿的协议
@protocol Printer
- (void)printDocument;
@end@protocol Scanner
- (void)scanDocument;
@end@interface MultiFunctionMachine : NSObject <Printer, Scanner>
@end@implementation MultiFunctionMachine
- (void)printDocument {// 打印文档
}- (void)scanDocument {// 扫描文档
}
@end
  • D 依赖倒置原则。抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的

定义协议(接口)

首先,我们定义一个文件读取的协议,这个协议负责声明读取文件内容的方法。

// FileReaderProtocol.h
#import <Foundation/Foundation.h>@protocol FileReaderProtocol <NSObject>
- (NSString *)readContent;
@end

具体实现类

接下来,实现这个协议。这里我们可以有多种实现方式,例如从本地文件系统读取,或是从网络获取。

// LocalFileReader.h
#import <Foundation/Foundation.h>
#import "FileReaderProtocol.h"@interface LocalFileReader : NSObject <FileReaderProtocol>
@property (strong, nonatomic) NSString *filePath;
- (instancetype)initWithFilePath:(NSString *)filePath;
@end// LocalFileReader.m
#import "LocalFileReader.h"@implementation LocalFileReader- (instancetype)initWithFilePath:(NSString *)filePath {self = [super init];if (self) {_filePath = filePath;}return self;
}- (NSString *)readContent {NSError *error;NSString *content = [NSString stringWithContentsOfFile:self.filePathencoding:NSUTF8StringEncodingerror:&error];if (error) {NSLog(@"Error reading file: %@", error.localizedDescription);return nil;}return content;
}@end

简单工厂模式

简单工厂模式并不是一个正式的设计模式,更多的是一种变成习惯
其主要通过传入参数给唯一的工厂类来创建不同类型的对象

但是这样就会出现一个问题,当我们需要添加新的类型,也就是添加新的产品时,我们必须去修改工厂类,这就违反了开闭原则

// 创建产品协议
@protocol Product <NSObject>
- (void)use;
@end// 具体产品A
@interface ProductA : NSObject <Product>
- (void)use;
@end@implementation ProductA
- (void)use {NSLog(@"Using Product A");
}
@end// 具体产品B
@interface ProductB : NSObject <Product>
- (void)use;
@end@implementation ProductB
- (void)use {NSLog(@"Using Product B");
}
@end// 简单工厂
@interface ProductFactory : NSObject
+ (id<Product>)productWithType:(NSString *)type;
@end@implementation ProductFactory
+ (id<Product>)productWithType:(NSString *)type {if ([type isEqualToString:@"A"]) {return [ProductA new];} else if ([type isEqualToString:@"B"]) {return [ProductB new];}return nil;
}
@end// 使用
ProductFactory *factory = [ProductFactory new];
id<Product> productA = [ProductFactory productWithType:@"A"];
[productA use];
// 首先创建一个工厂,根据传入的参数觉得工厂生产什么产品

在这里插入图片描述

工厂方法模式

工厂方法模式同样只是生产一种产品,但是一种产品可以有多个品牌

举个例子,我们的可乐工厂可以生产可乐,其即可以生产百事可乐,也可以生产可口可乐

首先我们需要定义一个创建对象的接口,但让子类决定要实例化的类是哪一个。工厂方法让类的实例化延迟到子类进行。
也就是抽象工厂是父类,具体工厂是子类,只有确定了具体工厂才能决定生产的产品是哪一种品牌的

// 定义产品接口
@protocol Logger <NSObject>
- (void)logMessage:(NSString *)message;
@end// 具体产品类 FileLogger
@interface FileLogger : NSObject <Logger>
- (void)logMessage:(NSString *)message;
@end@implementation FileLogger
- (void)logMessage:(NSString *)message {NSLog(@"File logger: %@", message);
}
@end// 具体产品类 NetworkLogger
@interface NetworkLogger : NSObject <Logger>
- (void)logMessage:(NSString *)message;
@end@implementation NetworkLogger
- (void)logMessage:(NSString *)message {NSLog(@"Network logger: %@", message);
}
@end// 抽象工厂类
@interface LoggerFactory : NSObject
- (id<Logger>)createLogger;
@end// 具体工厂类 FileLoggerFactory
@interface FileLoggerFactory : LoggerFactory
- (id<Logger>)createLogger;
@end@implementation FileLoggerFactory
- (id<Logger>)createLogger {return [[FileLogger alloc] init];
}
@end// 具体工厂类 NetworkLoggerFactory
@interface NetworkLoggerFactory : LoggerFactory
- (id<Logger>)createLogger;
@end@implementation NetworkLoggerFactory
- (id<Logger>)createLogger {return [[NetworkLogger alloc] init];
}
@end// 使用
LoggerFactory *factory;
if (useFileLogging) {factory = [[FileLoggerFactory alloc] init];
} else {factory = [[NetworkLoggerFactory alloc] init];
}
id<Logger> logger = [factory createLogger];
[logger logMessage:@"This is a test log."];

在这个例子中,LoggerFactory 是一个抽象类,定义了一个名为 createLogger 的方法,但并没有实现它。这个方法的具体实现由 FileLoggerFactoryNetworkLoggerFactory 这两个子类根据不同的业务逻辑来提供。这样,每个工厂子类决定了要实例化的具体 Logger 类,父类不需要知道具体的实现细节。

这种模式的优势在于,它支持易于扩展和修改的开放封闭原则,同时减少了类间的耦合。

我们再来看一个场景:
假设我们正在开发一个简单的绘图应用,该应用可以绘制不同类型的图形。我们可以使用工厂方法模式来创建不同类型的图形对象。

步骤 1: 定义图形接口和具体图形类。

// Shape.h
#import <Foundation/Foundation.h>@protocol Shape <NSObject>
- (void)draw;
@end// Circle.h
#import "Shape.h"@interface Circle : NSObject <Shape>
@end@implementation Circle
- (void)draw {NSLog(@"Drawing a circle.");
}
@end// Rectangle.h
#import "Shape.h"@interface Rectangle : NSObject <Shape>
@end@implementation Rectangle
- (void)draw {NSLog(@"Drawing a rectangle.");
}
@end

步骤 2: 定义抽象工厂类和具体工厂类。

// ShapeFactory.h
#import <Foundation/Foundation.h>
#import "Shape.h"@interface ShapeFactory : NSObject
- (id<Shape>)createShape;
@end// CircleFactory.h
#import "ShapeFactory.h"
#import "Circle.h"@interface CircleFactory : ShapeFactory
@end@implementation CircleFactory
- (id<Shape>)createShape {return [[Circle alloc] init];
}
@end// RectangleFactory.h
#import "ShapeFactory.h"
#import "Rectangle.h"@interface RectangleFactory : ShapeFactory
@end@implementation RectangleFactory
- (id<Shape>)createShape {return [[Rectangle alloc] init];
}
@end

步骤 3: 使用工厂类。

// main.m
#import <Foundation/Foundation.h>
#import "CircleFactory.h"
#import "RectangleFactory.h"int main(int argc, const char * argv[]) {@autoreleasepool {// 创建圆形工厂和矩形工厂ShapeFactory *circleFactory = [[CircleFactory alloc] init];ShapeFactory *rectangleFactory = [[RectangleFactory alloc] init];// 使用工厂方法创建形状id<Shape> circle = [circleFactory createShape];id<Shape> rectangle = [rectangleFactory createShape];// 绘制形状[circle draw];[rectangle draw];}return 0;
}

主程序依赖于抽象接口(Shape 协议和 ShapeFactory 类),而不是具体的类

我们来理解一下这句话

想象我们正在开发一个图形软件,软件需要支持多种类型图形,我们此时不需要知道操作的图形具体是什么,使用接口来定义这些操作,可以让你的软件支持任意类型的图形

定义抽象接口(如 Shape)

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

实现具体类(如 Circle 和 Rectangle):

@interface Circle : NSObject <Shape>
@end@implementation Circle
- (void)draw {NSLog(@"Drawing a circle.");
}
@end@interface Rectangle : NSObject <Shape>
@end@implementation Rectangle
- (void)draw {NSLog(@"Drawing a rectangle.");
}
@end

CircleRectangle 类都实现了 Shape 协议,但具体的绘制逻辑根据图形的类型不同而不同。

抽象工厂模式

抽象工厂模式与工厂模式关键区别在于抽象工厂是用来创建一系列产品的,每个产品可以属于不同的产品类别,而工厂方法一般只创建一种产品。

场景:
假设我们有一个应用程序,需要支持多种界面风格,如“暗黑模式”和“亮色模式”,每种风格都有一套不同的界面组件(如按钮、文本框等)。我们可以使用抽象工厂模式来根据用户的选择动态提供相应的组件。

  • 步骤 1: 定义抽象产品和具体产品

首先,我们定义两种产品接口:Button 和 TextField,以及它们的具体实现。

// Button.h
@protocol Button <NSObject>
- (void)display;
@end// DarkButton.h
#import "Button.h"@interface DarkButton : NSObject <Button>
@end@implementation DarkButton
- (void)display {NSLog(@"Displaying dark button");
}
@end// LightButton.h
#import "Button.h"@interface LightButton : NSObject <Button>
@end@implementation LightButton
- (void)display {NSLog(@"Displaying light button");
}
@end// TextField.h
@protocol TextField <NSObject>
- (void)display;
@end// DarkTextField.h
#import "TextField.h"@interface DarkTextField : NSObject <TextField>
@end@implementation DarkTextField
- (void)display {NSLog(@"Displaying dark text field");
}
@end// LightTextField.h
#import "TextField.h"@interface LightTextField : NSObject <TextField>
@end@implementation LightTextField
- (void)display {NSLog(@"Displaying light text field");
}
@end
  • 步骤 2: 定义抽象工厂和具体工厂

定义一个抽象工厂接口,GUIFactory,以及为每种界面风格实现一个具体工厂。

// GUIFactory.h
#import "Button.h"
#import "TextField.h"@protocol GUIFactory <NSObject>
- (id<Button>)createButton;
- (id<TextField>)createTextField;
@end// DarkGUIFactory.h
#import "GUIFactory.h"
#import "DarkButton.h"
#import "DarkTextField.h"@interface DarkGUIFactory : NSObject <GUIFactory>
@end@implementation DarkGUIFactory
- (id<Button>)createButton {return [DarkButton new];
}
- (id<TextField>)createTextField {return [DarkTextField new];
}
@end// LightGUIFactory.h
#import "GUIFactory.h"
#import "LightButton.h"
#import "LightTextField.h"@interface LightGUIFactory : NSObject <GUIFactory>
@end@implementation LightGUIFactory
- (id<Button>)createButton {return [LightButton new];
}
- (id<TextField>)createTextField {return [LightTextField new];
}
@end
  • 步骤 3: 使用抽象工厂

客户端代码现在可以根据用户的偏好选择使用合适的工厂,而不需要关心具体的产品如何创建。

// main.m
#import <Foundation/Foundation.h>
#import "DarkGUIFactory.h"
#import "LightGUIFactory.h"int main(int argc, const char * argv[]) {@autoreleasepool {id<GUIFactory> factory;BOOL useDarkMode = YES;  // 用户选择使用暗黑模式if (useDarkMode) {factory = [[DarkGUIFactory alloc] init];} else {factory = [[LightGUIFactory alloc] init];}id<Button> button = [factory createButton];id<TextField> textField = [factory createTextField];[button display];[textField display];}return 0;
}

关于三兄弟的升级与降级

  • 单一类型产品比较少时,用简单工厂模式。
  • 单一类型产品各种定制比较多时,用工厂模式。
  • 多种类型产品时,使用抽象工厂模式。

注意

我们注意到定义产品类前我们通常会先定义一个抽象产品接口,也就是协议,例如:

// 定义产品接口
@protocol Logger <NSObject>
- (void)logMessage:(NSString *)message;
@end// 具体产品类 FileLogger
@interface FileLogger : NSObject <Logger>
- (void)logMessage:(NSString *)message;
@end

同时还有在主程序中创建对象

// 使用工厂方法创建形状id<Shape> circle = [circleFactory createShape];id<Shape> rectangle = [rectangleFactory createShape];

这就符合我们的依赖倒置原则

主程序依赖于抽象接口(Shape 协议和 ShapeFactory 类),而不是具体的类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/12411.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PBR系列-光之简史

作者&#xff1a;游梦 ​ 欢迎进入官网体验使用&#xff1a;Mapmost——让人与机器联合创作成为新常态 ​说到PBR理论分为三大理论&#xff1a;物理光源、物理材质与物理相机&#xff0c;三者都与光有着千丝万缕的关系&#xff0c;原打算这期讲解物理材质&#xff0c;在梳理知…

OpenHarmony 3.1 Release实战开发 + Linux 原厂内核Launcher起不来问题分析报告

1、关键字 Launcher 无法启动&#xff1b;原厂内核&#xff1b;Access Token ID&#xff1b; 2、问题描述 芯片&#xff1a;rk3566&#xff1b;rk3399 内核版本&#xff1a;Linux 4.19&#xff0c;是 RK 芯片原厂发布的 rk356x 4.19 稳定版内核 OH 版本&#xff1a;OpenHa…

elementui,iview等 表格单元格合并之固定列

要的效果如下 需要合并 show weak 及 Siginin这三列 上代码 <template><Table:columns"columns":span-method"handleSpan":data"data"bordersize"small"ref"table"></Table> </template> <sc…

R实验 基础(一)

实验目的&#xff1a; 了解实验报告书的书写要求&#xff1b;掌握R、RStudio的下载与安装&#xff1b;熟悉R的界面及基本操作&#xff1b;进一步熟悉R和RStudio的界面及基本操作&#xff1b;初步了解R的绘图和程序包的下载、安装和加载使用。 实验内容&#xff1a; 了解实验报…

Python sort() 和 sorted() 的区别应用实例详解

大家好&#xff0c;今天针对 Python 中 sort() 和 sorted() 之间的区别&#xff0c;来一个实例详细解读。sort — 顾名思义就是排序的意思&#xff0c;它可以接收的对象为可迭代的数据类型。今天以列表为例子演示两者的不同点、相同点&#xff0c;以及其中一些常用的高级参数使…

【知识碎片】2024_05_14

本篇记录了两道关于位运算的选择题&#xff0c;和一道有点思维的代码题。 C语言碎片知识 求函数返回值&#xff0c;传入 -1 &#xff0c;则在64位机器上函数返回&#xff08; &#xff09; int func(int x) {int count 0;while (x){count;x x&(x - 1);//与运算} return c…

24/05/14总结

签到2&#xff1a; 签到界面上有时间显示&#xff0c;签到码输入框&#xff0c;开始签到&#xff0c;当倒计时结束&#xff0c;老师端和学生端都会显示签到结果&#xff0c;所以签到结果需要建表&#xff1a;&#xff08;签到了的学生和未签到的学生&#xff0c; 这次签到的时间…

详述进程的地址空间

进程的地址空间 合法的地址 (可读或可写) 代码 (main, %rip 会从此处取出待执行的指令)&#xff0c;只读数据 (static int x)&#xff0c;读写堆栈 (int y)&#xff0c;读写运行时分配的内存 (???)&#xff0c;读写动态链接库 (???) 非法的地址 NULL&#xff0c;导致 se…

【微命令】git config如何配置全局的用户和邮箱?(--global user.name、user.email;git config --help)

虽然经常用&#xff0c;也经常忘记&#xff0c;特此记录。 命令 git config --global user.name "myname" git config --global user.email test163.com另外一种方式 help git config --help |grep email | grep name直接help查看

Git系列:git log 掌握版本控制的精髓

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【leetcode面试经典150题】-27. 移除元素

88.合并两个有序数组 1 题目介绍1 个人解题思路1.1 解题代码1.2 思路解析 2、分析官方题解2.1 单侧双指针2.2 双侧双指针 1 题目介绍 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外…

Echarts结课之小杨总结版

Echarts结课之小杨总结版 前言基础回顾框架sale框架代码&#xff1a; user框架基础代码&#xff1a; inventory框架基础代码&#xff1a; total框架基础代码&#xff1a; 基础设置1.标题(Title)2.图例(Legend)实现 3.工具提示(Tooltip)实现 4.X轴(X Axis) 和 Y轴(Y Axis)5.数据…

「Qt Widget中文示例指南」如何实现一个快捷编辑器(二)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 快捷编辑器示例展示…

Leetcode 第 129 场双周赛题解

Leetcode 第 129 场双周赛题解 Leetcode 第 129 场双周赛题解题目1&#xff1a;3127. 构造相同颜色的正方形思路代码复杂度分析 题目2&#xff1a;3128. 直角三角形思路代码复杂度分析 题目3&#xff1a;3129. 找出所有稳定的二进制数组 I思路代码复杂度分析 题目4&#xff1a;…

电子邮箱是什么?怎么申请一个电子邮箱?

电子邮箱是我们沟通的工具&#xff0c;细分为免费版电子邮箱和付费版电子邮箱。怎么申请一个属于自己的电子邮箱&#xff1f;今天小编就分享一下电子邮箱注册教程&#xff0c;手把手教您注册一个电子邮箱。 一、电子邮箱的定义 电子邮箱&#xff0c;简称邮箱&#xff0c;是一…

BGP路由优选

1.BGP路由优选规则 上述规则依序排列&#xff0c;BGP进行路由优选时&#xff0c;从第一条规则开始执行&#xff0c;如果根据第一条规则无法作出判断&#xff0c;例如路由的Preferred-Value属性值相同&#xff0c;则继续执行下一条规则&#xff0c;如果根据当前的规则&#xff0…

如何快速打开多个网页?

在平常的工作当中&#xff0c; 如果每天都需固定打开几个网站&#xff0c;可以通过创建一个批处理&#xff0c;一键打开需要的所有网站。 使用方法&#xff1a; 在桌面新建一个txt文本&#xff0c;按照以下格式输入代码&#xff0c;并将需要打开网站的地址输入进去。 ​ ec…

JavaScript异步编程——11-异常处理方案【万字长文,感谢支持】

异常处理方案 在JS开发中&#xff0c;处理异常包括两步&#xff1a;先抛出异常&#xff0c;然后捕获异常。 为什么要做异常处理 异常处理非常重要&#xff0c;至少有以下几个原因&#xff1a; 防止程序报错甚至停止运行&#xff1a;当代码执行过程中发生错误或异常时&#x…

虚拟化技术 在vCenter Server创建数中心、添加主机

一、实验内容 1.安装Flash 2.在vCenter Server创建数中心、添加主机 二、实验主要仪器设备及器材 1.安装有64位Windows操作系统的台式电脑或笔记本电脑&#xff0c;建议4C8G或以上配置 2.在Windows Server 2008 R2已安装vCenter Server 3.Adobe Flash Player 12.0.0.70.e…

算法-卡尔曼滤波之卡尔曼滤波的第一个方程:状态更新方程

通过一个例子来引出卡尔曼滤波的状态更新方程&#xff1b; 这里系统状态是金条的重量&#xff1b; 为了估计系统的状态&#xff0c;我们可以多次测量金条的重量&#xff0c;然后求平均值&#xff1b; 其中估计值是所有测量值的平均值&#xff1b; 由于我们使用的是静态模型&am…