【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;在梳理知…

MySQL:某字段追加随机数

在MySQL中&#xff0c;你可以使用UPDATE语句结合随机函数RAND()来为某个字段追加随机数。以下是一个示例&#xff0c;假设我们有一个表my_table&#xff0c;其中有一个字段my_field&#xff0c;我们想要为这个字段追加一个介于1到100之间的随机数&#xff1a; UPDATE my_table…

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…

Java Spring的远程调用方式(如RMI、Hessian、Burlap等)

Spring框架提供了对多种远程调用技术的集成&#xff0c;使得开发者可以方便地在分布式系统中实现远程服务调用。以下是Spring支持的一些常见远程调用方式&#xff1a; 1. RMI (Remote Method Invocation) RMI是一种Java特有的远程调用技术&#xff0c;允许一个Java虚拟机上的…

【一站式学会Kotlin】第六节 lamda表达式、拖尾lamda表达式

一、 lamda 表达式的完整语法形式: val sum: (Int, Int) -> Int = {x: Int, y: Int -> x + y }(1)lamda表达式总是在花括号中 (2)完成的语法形式参数声明放在花括号内,(可选) (3)函数体跟在->后 (4)如果能推断出该lamda表达式的返回值类型不是Unit,那么该…

nginx文件夹内文件解释<一>

目录 linux中/etc/nginx/查询目录整体解释分目录解释Conf.d目录 linux中/etc/nginx/查询目录 [rootrelease nginx]# ls -l total 76 drwxr-xr-x 2 root root 4096 Mar 12 22:49 conf.d drwxr-xr-x 2 root root 4096 Nov 11 2022 default.d -rw-r--r-- 1 root root 1077 Nov 1…

vue中图片引入与使用几种方式

方法一&#xff1a;img 标签静态引入 朴实无华 <img src"../assets/img/logo.png" > 方法二&#xff1a;background-image 朴实无华2.0版 background-image: url("../assets/img/bg.jpg");方法三&#xff1a;require()方法 将编写代码时定义的路径…

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;以及其中一些常用的高级参数使…

支付平台怎么应对DDOS攻击

支付平台怎么应对DDOS攻击?在数字化时代&#xff0c;支付平台已成为人们日常生活中不可或缺的一部分。然而&#xff0c;随着支付平台的广泛应用&#xff0c;其面临的安全威胁也日益严峻。其中&#xff0c;DDOS&#xff08;分布式拒绝服务攻击&#xff09;以其高隐蔽性、强破坏…

智慧武装三维实景电子沙盘系统

智慧武警实景三维电子沙盘系统是一个集成了先进三维建模技术、大数据分析、实时动态更新、交互操作以及高度安全可靠的综合性平台。它专为武警部队设计&#xff0c;旨在提供一个实战指挥和战术训练的基础平台支撑&#xff0c;以满足指战员在作战环境研究、标图作业、战法研究、…

Java 包装类型的缓存机制了解吗?

在 Java 中&#xff0c;基本数据类型有各自的包装类型。这些包装类型在某些情况下会使用缓存机制来提高性能。本文将详细探讨这些缓存机制的实现原理及其实际应用。 什么是包装类型的缓存机制&#xff1f; Java 的包装类型缓存机制是指在某些特定范围内&#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…

蓝桥杯第2357题——限高杆(多层图+dijkstra)

问题描述 某市有 &#x1d45b; 个路口, 有 &#x1d45a; 段道路连接这些路口, 组成了该市的公路系统。其 中一段道路两端一定连接两个不同的路口。道路中间不会穿过路口。 由于各种原因, 在一部分道路的中间设置了一些限高杆, 有限高杆的路段 货车无法通过。 在该市有两个…

【微命令】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的主页」…

【科普】ChatGPT-4o 是什么?和之前的ChatGPT4.0有什么区别,各有什么优劣势

文章目录 前言一、ChatGPT-4o 是什么&#xff1f;**主要特点和改进**&#xff1a; 二、ChatGPT-4o 和之前的ChatGPT4.0有什么区别&#xff0c;各有什么优劣势区别优势和劣势ChatGPT-4.0ChatGPT-4o 前言 5月13日&#xff0c;ChatGPT-4o发布&#xff0c;是人工智能的进一步发展&…