Objective-C 中的工厂模式解析与实践
一、工厂模式基础概念
(一)什么是设计模式
在软件开发过程中,我们常常会遇到一些反复出现的问题。为了解决这些问题,前辈们总结出了一系列行之有效的解决方案,这些解决方案就被称为设计模式。设计模式就像是建筑行业中的建筑蓝图,它为我们提供了一种通用的、可复用的设计思路,帮助我们构建更加健壮、可维护和可扩展的软件系统。
(二)工厂模式概述
工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,将对象的创建和使用分离。通过使用工厂模式,我们可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象。这样做的好处是,当对象的创建逻辑发生变化时,只需要修改工厂类的代码,而不需要修改所有使用该对象的客户端代码,从而提高了代码的可维护性和可扩展性。
二、Objective - C 语言基础回顾
(一)Objective - C 类与对象
在 Objective - C 中,类是对象的模板,它定义了对象的属性和方法。对象是类的实例,通过向对象发送消息来调用其方法。例如,我们定义一个简单的 Person
类:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)sayHello;
@end
@implementation Person
- (void)sayHello {
NSLog(@"Hello, my name is %@ and I'm %ld years old.", self.name, (long)self.age);
}
@end
在上述代码中,Person
类有两个属性 name
和 age
,以及一个方法 sayHello
。我们可以通过以下方式创建 Person
类的对象并调用其方法:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.name = @"John";
person.age = 30;
[person sayHello];
}
return 0;
}
(二)内存管理
在 Objective - C 中,内存管理是一个重要的方面。在 ARC(自动引用计数)出现之前,开发者需要手动管理对象的内存,即通过 alloc
、retain
、release
和 dealloc
等方法来控制对象的生命周期。ARC 出现后,编译器会自动插入适当的内存管理代码,大大减轻了开发者的负担。例如,在 ARC 环境下,我们不需要手动调用 release
方法来释放对象:
// ARC 环境
Person *person = [[Person alloc] init];
// 不需要手动调用 [person release];
但在某些情况下,如使用 Core Foundation 框架的对象时,仍然需要手动管理内存,这就需要开发者对内存管理机制有深入的理解。
三、Objective - C 中的简单工厂模式
(一)简单工厂模式定义与结构
简单工厂模式是工厂模式的一种基础形式,它定义了一个工厂类,用于创建产品对象。简单工厂模式包含三个角色:
- 工厂类(Creator):负责创建产品对象,它包含一个创建产品对象的方法。
- 抽象产品类(Product):定义了产品对象的公共接口。
- 具体产品类(ConcreteProduct):实现了抽象产品类的接口。
(二)Objective - C 代码示例
假设我们正在开发一个图形绘制程序,需要绘制不同类型的图形,如圆形和矩形。我们可以使用简单工厂模式来创建图形对象。
首先,定义抽象图形类 Shape
:
#import <Foundation/Foundation.h>
@interface Shape : NSObject
- (void)draw;
@end
然后,定义具体的图形类 Circle
和 Rectangle
:
#import "Shape.h"
@interface Circle : Shape
@end
@implementation Circle
- (void)draw {
NSLog(@"Drawing a circle.");
}
@end
@interface Rectangle : Shape
@end
@implementation Rectangle
- (void)draw {
NSLog(@"Drawing a rectangle.");
}
@end
接下来,定义图形工厂类 ShapeFactory
:
#import "Shape.h"
#import "Circle.h"
#import "Rectangle.h"
@interface ShapeFactory : NSObject
+ (Shape *)createShapeWithType:(NSString *)type;
@end
@implementation ShapeFactory
+ (Shape *)createShapeWithType:(NSString *)type {
if ([type isEqualToString:@"circle"]) {
return [[Circle alloc] init];
} else if ([type isEqualToString:@"rectangle"]) {
return [[Rectangle alloc] init];
}
return nil;
}
@end
在客户端代码中,我们可以使用图形工厂类来创建图形对象:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Shape *circle = [ShapeFactory createShapeWithType:@"circle"];
[circle draw];
Shape *rectangle = [ShapeFactory createShapeWithType:@"rectangle"];
[rectangle draw];
}
return 0;
}
在上述代码中,ShapeFactory
类的 createShapeWithType:
方法根据传入的类型参数创建相应的图形对象。客户端代码只需要调用工厂方法,而不需要关心图形对象的具体创建过程。
(三)简单工厂模式优缺点
- 优点:
- 实现了对象创建和使用的分离,提高了代码的可维护性和可扩展性。
- 客户端代码不需要了解具体产品类的创建细节,只需要调用工厂方法即可。
- 缺点:
- 工厂类的职责过重,当需要创建更多类型的产品对象时,工厂类的
createShapeWithType:
方法会变得非常庞大,难以维护。 - 不符合开闭原则,当需要添加新的产品类时,需要修改工厂类的代码。
- 工厂类的职责过重,当需要创建更多类型的产品对象时,工厂类的
四、Objective - C 中的工厂方法模式
(一)工厂方法模式定义与结构
工厂方法模式是对简单工厂模式的进一步抽象和扩展。在工厂方法模式中,工厂类不再负责创建具体的产品对象,而是将创建对象的任务交给子类去完成。工厂方法模式包含四个角色:
- 抽象工厂类(Creator):定义了一个创建产品对象的抽象方法,具体的创建逻辑由子类实现。
- 具体工厂类(ConcreteCreator):继承自抽象工厂类,实现了创建产品对象的具体逻辑。
- 抽象产品类(Product):定义了产品对象的公共接口。
- 具体产品类(ConcreteProduct):实现了抽象产品类的接口。
(二)Objective - C 代码示例
继续以图形绘制程序为例,我们使用工厂方法模式来改进代码。
首先,修改抽象图形类 Shape
保持不变。
然后,修改具体的图形类 Circle
和 Rectangle
也保持不变。
接下来,定义抽象图形工厂类 ShapeFactory
:
#import "Shape.h"
@interface ShapeFactory : NSObject
- (Shape *)createShape;
@end
再定义具体的图形工厂类 CircleFactory
和 RectangleFactory
:
#import "ShapeFactory.h"
#import "Circle.h"
@interface CircleFactory : ShapeFactory
@end
@implementation CircleFactory
- (Shape *)createShape {
return [[Circle alloc] init];
}
@end
#import "ShapeFactory.h"
#import "Rectangle.h"
@interface RectangleFactory : ShapeFactory
@end
@implementation RectangleFactory
- (Shape *)createShape {
return [[Rectangle alloc] init];
}
@end
在客户端代码中,我们可以使用具体的图形工厂类来创建图形对象:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ShapeFactory *circleFactory = [[CircleFactory alloc] init];
Shape *circle = [circleFactory createShape];
[circle draw];
ShapeFactory *rectangleFactory = [[RectangleFactory alloc] init];
Shape *rectangle = [rectangleFactory createShape];
[rectangle draw];
}
return 0;
}
在上述代码中,CircleFactory
和 RectangleFactory
分别负责创建 Circle
和 Rectangle
对象。客户端代码通过创建具体的工厂类对象来获取相应的图形对象,这样当需要添加新的图形类型时,只需要创建新的具体工厂类和具体图形类,而不需要修改现有的工厂类代码,符合开闭原则。
(三)工厂方法模式优缺点
- 优点:
- 符合开闭原则,当需要添加新的产品类时,只需要创建新的具体工厂类和具体产品类,不需要修改现有的代码。
- 工厂类的职责得到了合理的分配,每个具体工厂类只负责创建一种产品对象,代码更加清晰和易于维护。
- 缺点:
- 当产品类型过多时,会导致具体工厂类的数量过多,增加了系统的复杂度。
- 客户端代码需要了解具体工厂类的存在,与简单工厂模式相比,客户端代码的耦合度有所增加。
五、Objective - C 中的抽象工厂模式
(一)抽象工厂模式定义与结构
抽象工厂模式提供了一种创建一系列相关或相互依赖对象的方式,而无需指定它们具体的类。抽象工厂模式包含五个角色:
- 抽象工厂类(AbstractFactory):定义了创建一系列产品对象的抽象方法。
- 具体工厂类(ConcreteFactory):继承自抽象工厂类,实现了创建一系列产品对象的具体逻辑。
- 抽象产品类(AbstractProduct):定义了产品对象的公共接口。
- 具体产品类(ConcreteProduct):实现了抽象产品类的接口。
- 客户端(Client):使用抽象工厂类创建产品对象。
(二)Objective - C 代码示例
假设我们正在开发一个跨平台的 UI 组件库,需要创建不同平台(如 iOS 和 macOS)的按钮和文本框。我们可以使用抽象工厂模式来实现。
首先,定义抽象按钮类 Button
和抽象文本框类 TextBox
:
#import <Foundation/Foundation.h>
@interface Button : NSObject
- (void)render;
@end
@interface TextBox : NSObject
- (void)render;
@end
然后,定义 iOS 平台的按钮类 iOSButton
和文本框类 iOSTextBox
:
#import "Button.h"
@interface iOSButton : Button
@end
@implementation iOSButton
- (void)render {
NSLog(@"Rendering an iOS button.");
}
@end
#import "TextBox.h"
@interface iOSTextBox : TextBox
@end
@implementation iOSTextBox
- (void)render {
NSLog(@"Rendering an iOS text box.");
}
@end
接着,定义 macOS 平台的按钮类 MacOSButton
和文本框类 MacOSTextBox
:
#import "Button.h"
@interface MacOSButton : Button
@end
@implementation MacOSButton
- (void)render {
NSLog(@"Rendering a macOS button.");
}
@end
#import "TextBox.h"
@interface MacOSTextBox : TextBox
@end
@implementation MacOSTextBox
- (void)render {
NSLog(@"Rendering a macOS text box.");
}
@end
接下来,定义抽象工厂类 UIFactory
:
#import "Button.h"
#import "TextBox.h"
@interface UIFactory : NSObject
- (Button *)createButton;
- (TextBox *)createTextBox;
@end
再定义具体工厂类 iOSUIFactory
和 MacOSUIFactory
:
#import "UIFactory.h"
#import "iOSButton.h"
#import "iOSTextBox.h"
@interface iOSUIFactory : UIFactory
@end
@implementation iOSUIFactory
- (Button *)createButton {
return [[iOSButton alloc] init];
}
- (TextBox *)createTextBox {
return [[iOSTextBox alloc] init];
}
@end
#import "UIFactory.h"
#import "MacOSButton.h"
#import "MacOSTextBox.h"
@interface MacOSUIFactory : UIFactory
@end
@implementation MacOSUIFactory
- (Button *)createButton {
return [[MacOSButton alloc] init];
}
- (TextBox *)createTextBox {
return [[MacOSTextBox alloc] init];
}
@end
在客户端代码中,我们可以根据不同的平台使用相应的工厂类来创建 UI 组件:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 使用 iOS 工厂创建 UI 组件
UIFactory *iOSFactory = [[iOSUIFactory alloc] init];
Button *iOSButton = [iOSFactory createButton];
[iOSButton render];
TextBox *iOSTextBox = [iOSFactory createTextBox];
[iOSTextBox render];
// 使用 macOS 工厂创建 UI 组件
UIFactory *macOSFactory = [[MacOSUIFactory alloc] init];
Button *macOSButton = [macOSFactory createButton];
[macOSButton render];
TextBox *macOSTextBox = [macOSFactory createTextBox];
[macOSTextBox render];
}
return 0;
}
在上述代码中,iOSUIFactory
和 MacOSUIFactory
分别负责创建 iOS 和 macOS 平台的按钮和文本框。客户端代码通过选择不同的工厂类来创建适合特定平台的 UI 组件,实现了产品对象的系列化创建。
(三)抽象工厂模式优缺点
- 优点:
- 隔离了具体产品类的创建,客户端代码只需要与抽象工厂类交互,降低了客户端与具体产品类的耦合度。
- 方便切换产品族,例如从 iOS 平台切换到 macOS 平台,只需要更换具体工厂类即可。
- 符合开闭原则,当需要添加新的产品族时,只需要创建新的具体工厂类和具体产品类,不需要修改现有的代码。
- 缺点:
- 抽象工厂模式的实现较为复杂,当产品族中的产品种类过多时,会导致代码量增加,维护难度加大。
- 当需要添加新的产品类型时,不仅需要修改抽象工厂类的接口,还需要修改所有具体工厂类的代码,违反了开闭原则。
六、工厂模式在 iOS 开发中的应用场景
(一)视图控制器创建
在 iOS 开发中,视图控制器的创建常常可以使用工厂模式。例如,我们有一个应用程序,包含不同类型的视图控制器,如登录视图控制器、主视图控制器等。我们可以创建一个视图控制器工厂类,负责创建这些视图控制器。
#import <UIKit/UIKit.h>
@interface ViewControllerFactory : NSObject
+ (UIViewController *)createViewControllerWithType:(NSString *)type;
@end
@implementation ViewControllerFactory
+ (UIViewController *)createViewControllerWithType:(NSString *)type {
if ([type isEqualToString:@"login"]) {
// 假设 LoginViewController 是自定义的登录视图控制器
return [[LoginViewController alloc] init];
} else if ([type isEqualToString:@"main"]) {
// 假设 MainViewController 是自定义的主视图控制器
return [[MainViewController alloc] init];
}
return nil;
}
@end
在应用程序的入口处或者其他需要创建视图控制器的地方,我们可以使用这个工厂类:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIViewController *loginVC = [ViewControllerFactory createViewControllerWithType:@"login"];
self.window.rootViewController = loginVC;
[self.window makeKeyAndVisible];
return YES;
}
这样做的好处是,当视图控制器的创建逻辑发生变化时,只需要修改工厂类的代码,而不需要修改所有使用视图控制器的地方。
(二)网络请求构建
在网络请求开发中,不同的请求可能有不同的参数、请求头和请求方法。我们可以使用工厂模式来构建网络请求。例如,我们定义一个抽象的网络请求类 NetworkRequest
:
#import <Foundation/Foundation.h>
@interface NetworkRequest : NSObject
@property (nonatomic, strong) NSString *url;
@property (nonatomic, strong) NSDictionary *parameters;
@property (nonatomic, strong) NSDictionary *headers;
@property (nonatomic, copy) NSString *requestMethod;
- (NSURLRequest *)buildRequest;
@end
@implementation NetworkRequest
- (NSURLRequest *)buildRequest {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
request.HTTPMethod = self.requestMethod;
if (self.parameters) {
NSData *data = [NSJSONSerialization dataWithJSONObject:self.parameters options:0 error:nil];
request.HTTPBody = data;
}
if (self.headers) {
for (NSString *key in self.headers) {
[request setValue:self.headers[key] forHTTPHeaderField:key];
}
}
return request;
}
@end
然后定义具体的请求类,如 LoginRequest
:
#import "NetworkRequest.h"
@interface LoginRequest : NetworkRequest
@end
@implementation LoginRequest
- (instancetype)init {
self = [super init];
if (self) {
self.url = @"https://example.com/api/login";
self.requestMethod = @"POST";
}
return self;
}
@end
再定义一个网络请求工厂类 NetworkRequestFactory
:
#import "NetworkRequest.h"
#import "LoginRequest.h"
@interface NetworkRequestFactory : NSObject
+ (NetworkRequest *)createRequestWithType:(NSString *)type;
@end
@implementation NetworkRequestFactory
+ (NetworkRequest *)createRequestWithType:(NSString *)type {
if ([type isEqualToString:@"login"]) {
return [[LoginRequest alloc] init];
}
return nil;
}
@end
在需要发起网络请求的地方,我们可以使用工厂类来获取请求对象:
NetworkRequest *loginRequest = [NetworkRequestFactory createRequestWithType:@"login"];
NSURLRequest *request = [loginRequest buildRequest];
// 使用 NSURLSession 发起请求
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理响应
}];
[task resume];
通过这种方式,我们可以将不同类型网络请求的构建逻辑封装在工厂类中,提高代码的可维护性和可扩展性。
(三)数据模型创建
在处理数据模型时,工厂模式也有应用场景。例如,我们从服务器获取到 JSON 数据,需要将其解析为相应的数据模型对象。我们可以创建一个数据模型工厂类,根据不同的 JSON 结构创建不同的数据模型。假设我们有一个用户数据模型 User
:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
+ (instancetype)userWithJSON:(NSDictionary *)json;
@end
@implementation User
+ (instancetype)userWithJSON:(NSDictionary *)json {
User *user = [[User alloc] init];
user.name = json[@"name"];
user.age = [json[@"age"] integerValue];
return user;
}
@end
再假设我们有一个订单数据模型 Order
:
#import <Foundation/Foundation.h>
@interface Order : NSObject
@property (nonatomic, copy) NSString *orderId;
@property (nonatomic, strong) NSArray *products;
+ (instancetype)orderWithJSON:(NSDictionary *)json;
@end
@implementation Order
+ (instancetype)orderWithJSON:(NSDictionary *)json {
Order *order = [[Order alloc] init];
order.orderId = json[@"orderId"];
order.products = json[@"products"];
return order;
}
@end
然后定义一个数据模型工厂类 DataModelFactory
:
#import "User.h"
#import "Order.h"
@interface DataModelFactory : NSObject
+ (id)createModelWithType:(NSString *)type json:(NSDictionary *)json;
@end
@implementation DataModelFactory
+ (id)createModelWithType:(NSString *)type json:(NSDictionary *)json {
if ([type isEqualToString:@"user"]) {
return [User userWithJSON:json];
} else if ([type isEqualToString:@"order"]) {
return [Order orderWithJSON:json];
}
return nil;
}
@end
在解析 JSON 数据的地方,我们可以使用工厂类来创建相应的数据模型对象:
NSDictionary *userJSON = @{@"name": @"John", @"age": @30};
id userModel = [DataModelFactory createModelWithType:@"user" json:userJSON];
if ([userModel isKindOfClass:[User class]]) {
User *user = (User *)userModel;
NSLog(@"User name: %@, age: %ld", user.name, (long)user.age);
}
NSDictionary *orderJSON = @{@"orderId": @"12345", @"products": @[@"Product 1", @"Product 2"]};
id orderModel = [DataModelFactory createModelWithType:@"order" json:orderJSON];
if ([orderModel isKindOfClass:[Order class]]) {
Order *order = (Order *)orderModel;
NSLog(@"Order ID: %@, Products: %@", order.orderId, order.products);
}
这样可以将数据模型的创建逻辑集中管理,便于维护和扩展。
七、工厂模式在 Objective - C 中的最佳实践
(一)合理选择工厂模式类型
在实际开发中,要根据具体的需求和场景选择合适的工厂模式类型。如果创建对象的逻辑比较简单,且产品类型较少,简单工厂模式可能是一个不错的选择。例如,在一个小型项目中,只需要创建几种固定类型的对象,使用简单工厂模式可以快速实现对象的创建和管理。
如果系统需要频繁添加新的产品类型,且希望符合开闭原则,工厂方法模式更为合适。比如在一个不断迭代更新的应用程序中,经常会添加新的功能模块,每个功能模块可能对应一个新的视图控制器,使用工厂方法模式可以方便地添加新的视图控制器创建逻辑。
当需要创建一系列相关或相互依赖的对象,并且要支持产品族的切换时,抽象工厂模式是最佳选择。例如,在开发一个跨平台的应用程序时,需要创建不同平台的 UI 组件,使用抽象工厂模式可以很好地满足需求。
(二)结合依赖注入
依赖注入是一种设计模式,它允许将对象的依赖关系传递给对象,而不是在对象内部创建依赖对象。在使用工厂模式时,可以结合依赖注入来提高代码的可测试性和可维护性。例如,在网络请求工厂类中,可以通过依赖注入的方式传递网络配置信息,而不是在工厂类内部硬编码这些信息。
@interface NetworkRequestFactory : NSObject
- (instancetype)initWithBaseURL:(NSString *)baseURL;
+ (NetworkRequest *)createRequestWithType:(NSString *)type;
@end
@implementation NetworkRequestFactory
- (instancetype)initWithBaseURL:(NSString *)baseURL {
self = [super init];
if (self) {
self.baseURL = baseURL;
}
return self;
}
+ (NetworkRequest *)createRequestWithType:(NSString *)type {
// 根据 type 创建请求,使用 self.baseURL 构建请求 URL
}
@end
在测试网络请求工厂类时,可以通过传入不同的 baseURL
来模拟不同的网络环境,提高测试的灵活性。
(三)注意内存管理
在 Objective - C 中使用工厂模式时,要特别注意内存管理。尤其是在手动管理内存的情况下,确保工厂创建的对象在不再使用时能够正确释放内存。在 ARC 环境下,虽然编译器会自动处理大部分内存管理问题,但在使用 Core Foundation 等非 ARC 管理的对象时,仍然需要手动处理内存。例如,在工厂方法中创建 Core Foundation 对象时,要记得在适当的时候调用 CFRelease
来释放对象。
#import <CoreFoundation/CoreFoundation.h>
@interface CFObjectFactory : NSObject
+ (CFStringRef)createCFStringWithValue:(NSString *)value;
@end
@implementation CFObjectFactory
+ (CFStringRef)createCFStringWithValue:(NSString *)value {
CFStringRef cfString = CFStringCreateWithNSString(kCFAllocatorDefault, (__bridge CFStringRef)value);
return cfString;
}
@end
在使用这个工厂方法创建的 CFStringRef
对象后,要记得调用 CFRelease
:
CFStringRef cfString = [CFObjectFactory createCFStringWithValue:@"Hello"];
// 使用 cfString
CFRelease(cfString);
这样可以避免内存泄漏问题,确保程序的稳定性和性能。
(四)文档化工厂方法
为了提高代码的可维护性和可读性,要对工厂类的工厂方法进行详细的文档化。在文档中说明工厂方法的功能、参数含义、返回值类型以及可能抛出的异常等信息。例如,在图形工厂类的 createShapeWithType:
方法上添加文档注释:
/**
创建指定类型的图形对象。
@param type 图形类型,支持 @"circle" 和 @"rectangle"。
@return 对应的图形对象,如果类型不支持则返回 nil。
*/
+ (Shape *)createShapeWithType:(NSString *)type;
这样其他开发人员在使用这个工厂方法时,可以清楚地了解其功能和使用方法,减少出错的可能性。同时,良好的文档也有助于代码的维护和后续的扩展。
通过遵循这些最佳实践,可以更好地在 Objective - C 项目中应用工厂模式,提高代码的质量和开发效率。