文章目录
前言
上文讲完了iOS的架构模式,接下来聊一聊设计模式,设计模式有许多,主要介绍一下工厂模式
设计模式的三大原则
- S 单一职责原则
告诉我们实现类要职责单一;
例如UIView负责处理事件传递响应,CALayer负责动画与视图的显示 - O 开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
bash
// 基本的绘制类
@interface Shape : NSObject
- (void)draw;
@end
// 扩展新的形状,无需修改现有代码
@interface Circle : Shape
@end
@implementation Circle
- (void)draw {
// 绘制圆形的逻辑
}
@end
- L 里氏替换原则告诉我们不要破坏继承体系;
bash
@interface Bird : NSObject
- (void)fly;
@end
@interface Duck : Bird
@end
@implementation Duck
- (void)fly {
// 实现飞行
}
@end
- D 迪米特法则。 一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
bash
// 班级类
@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 接口隔离原则: 使用多个专门的协议、而不是一个庞大臃肿的协议
bash
@protocol Printer
- (void)printDocument;
@end
@protocol Scanner
- (void)scanDocument;
@end
@interface MultiFunctionMachine : NSObject <Printer, Scanner>
@end
@implementation MultiFunctionMachine
- (void)printDocument {
// 打印文档
}
- (void)scanDocument {
// 扫描文档
}
@end
- D 依赖倒置原则。抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的
定义协议(接口)
首先,我们定义一个文件读取的协议,这个协议负责声明读取文件内容的方法。
bash
// FileReaderProtocol.h
#import <Foundation/Foundation.h>
@protocol FileReaderProtocol <NSObject>
- (NSString *)readContent;
@end
具体实现类
接下来,实现这个协议。这里我们可以有多种实现方式,例如从本地文件系统读取,或是从网络获取。
bash
// 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.filePath
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
NSLog(@"Error reading file: %@", error.localizedDescription);
return nil;
}
return content;
}
@end
简单工厂模式
简单工厂模式并不是一个正式的设计模式,更多的是一种变成习惯
其主要通过传入参数给唯一的工厂类来创建不同类型的对象
但是这样就会出现一个问题,当我们需要添加新的类型,也就是添加新的产品时,我们必须去修改工厂类,这就违反了开闭原则
bash
// 创建产品协议
@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];
// 首先创建一个工厂,根据传入的参数觉得工厂生产什么产品
工厂方法模式
工厂方法模式同样只是生产一种产品,但是一种产品可以有多个品牌
举个例子,我们的可乐工厂可以生产可乐,其即可以生产百事可乐,也可以生产可口可乐
首先我们需要定义一个创建对象的接口,但让子类决定要实例化的类是哪一个。工厂方法让类的实例化延迟到子类进行。
也就是抽象工厂是父类,具体工厂是子类,只有确定了具体工厂才能决定生产的产品是哪一种品牌的
bash
// 定义产品接口
@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
的方法,但并没有实现它。这个方法的具体实现由 FileLoggerFactory
和 NetworkLoggerFactory
这两个子类根据不同的业务逻辑来提供。这样,每个工厂子类决定了要实例化的具体 Logger
类,父类不需要知道具体的实现细节。
这种模式的优势在于,它支持易于扩展和修改的开放封闭原则,同时减少了类间的耦合。
我们再来看一个场景:
假设我们正在开发一个简单的绘图应用,该应用可以绘制不同类型的图形。我们可以使用工厂方法模式来创建不同类型的图形对象。
步骤 1: 定义图形接口和具体图形类。
bash
// 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: 定义抽象工厂类和具体工厂类。
bash
// 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: 使用工厂类。
bash
// 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)
bash
@protocol Shape <NSObject>
- (void)draw;
@end
实现具体类(如 Circle 和 Rectangle):
bash
@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
Circle
和 Rectangle
类都实现了 Shape
协议,但具体的绘制逻辑根据图形的类型不同而不同。
抽象工厂模式
抽象工厂模式与工厂模式关键区别在于抽象工厂是用来创建一系列产品的,每个产品可以属于不同的产品类别,而工厂方法一般只创建一种产品。
场景:
假设我们有一个应用程序,需要支持多种界面风格,如"暗黑模式"和"亮色模式",每种风格都有一套不同的界面组件(如按钮、文本框等)。我们可以使用抽象工厂模式来根据用户的选择动态提供相应的组件。
- 步骤 1: 定义抽象产品和具体产品
首先,我们定义两种产品接口:Button 和 TextField,以及它们的具体实现。
bash
// 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,以及为每种界面风格实现一个具体工厂。
bash
// 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: 使用抽象工厂
客户端代码现在可以根据用户的偏好选择使用合适的工厂,而不需要关心具体的产品如何创建。
bash
// 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;
}
关于三兄弟的升级与降级
- 单一类型产品比较少时,用简单工厂模式。
- 单一类型产品各种定制比较多时,用工厂模式。
- 多种类型产品时,使用抽象工厂模式。
注意
我们注意到定义产品类前我们通常会先定义一个抽象产品接口,也就是协议,例如:
bash
// 定义产品接口
@protocol Logger <NSObject>
- (void)logMessage:(NSString *)message;
@end
// 具体产品类 FileLogger
@interface FileLogger : NSObject <Logger>
- (void)logMessage:(NSString *)message;
@end
同时还有在主程序中创建对象
bash
// 使用工厂方法创建形状
id<Shape> circle = [circleFactory createShape];
id<Shape> rectangle = [rectangleFactory createShape];
这就符合我们的依赖倒置原则
主程序依赖于抽象接口(Shape 协议和 ShapeFactory 类),而不是具体的类