MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 中的工厂模式解析与实践

2024-01-076.0k 阅读

一、工厂模式基础概念

(一)什么是设计模式

在软件开发过程中,我们常常会遇到一些反复出现的问题。为了解决这些问题,前辈们总结出了一系列行之有效的解决方案,这些解决方案就被称为设计模式。设计模式就像是建筑行业中的建筑蓝图,它为我们提供了一种通用的、可复用的设计思路,帮助我们构建更加健壮、可维护和可扩展的软件系统。

(二)工厂模式概述

工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,将对象的创建和使用分离。通过使用工厂模式,我们可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象。这样做的好处是,当对象的创建逻辑发生变化时,只需要修改工厂类的代码,而不需要修改所有使用该对象的客户端代码,从而提高了代码的可维护性和可扩展性。

二、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 类有两个属性 nameage,以及一个方法 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(自动引用计数)出现之前,开发者需要手动管理对象的内存,即通过 allocretainreleasedealloc 等方法来控制对象的生命周期。ARC 出现后,编译器会自动插入适当的内存管理代码,大大减轻了开发者的负担。例如,在 ARC 环境下,我们不需要手动调用 release 方法来释放对象:

// ARC 环境
Person *person = [[Person alloc] init];
// 不需要手动调用 [person release];

但在某些情况下,如使用 Core Foundation 框架的对象时,仍然需要手动管理内存,这就需要开发者对内存管理机制有深入的理解。

三、Objective - C 中的简单工厂模式

(一)简单工厂模式定义与结构

简单工厂模式是工厂模式的一种基础形式,它定义了一个工厂类,用于创建产品对象。简单工厂模式包含三个角色:

  1. 工厂类(Creator):负责创建产品对象,它包含一个创建产品对象的方法。
  2. 抽象产品类(Product):定义了产品对象的公共接口。
  3. 具体产品类(ConcreteProduct):实现了抽象产品类的接口。

(二)Objective - C 代码示例

假设我们正在开发一个图形绘制程序,需要绘制不同类型的图形,如圆形和矩形。我们可以使用简单工厂模式来创建图形对象。 首先,定义抽象图形类 Shape

#import <Foundation/Foundation.h>

@interface Shape : NSObject

- (void)draw;

@end

然后,定义具体的图形类 CircleRectangle

#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: 方法根据传入的类型参数创建相应的图形对象。客户端代码只需要调用工厂方法,而不需要关心图形对象的具体创建过程。

(三)简单工厂模式优缺点

  1. 优点
    • 实现了对象创建和使用的分离,提高了代码的可维护性和可扩展性。
    • 客户端代码不需要了解具体产品类的创建细节,只需要调用工厂方法即可。
  2. 缺点
    • 工厂类的职责过重,当需要创建更多类型的产品对象时,工厂类的 createShapeWithType: 方法会变得非常庞大,难以维护。
    • 不符合开闭原则,当需要添加新的产品类时,需要修改工厂类的代码。

四、Objective - C 中的工厂方法模式

(一)工厂方法模式定义与结构

工厂方法模式是对简单工厂模式的进一步抽象和扩展。在工厂方法模式中,工厂类不再负责创建具体的产品对象,而是将创建对象的任务交给子类去完成。工厂方法模式包含四个角色:

  1. 抽象工厂类(Creator):定义了一个创建产品对象的抽象方法,具体的创建逻辑由子类实现。
  2. 具体工厂类(ConcreteCreator):继承自抽象工厂类,实现了创建产品对象的具体逻辑。
  3. 抽象产品类(Product):定义了产品对象的公共接口。
  4. 具体产品类(ConcreteProduct):实现了抽象产品类的接口。

(二)Objective - C 代码示例

继续以图形绘制程序为例,我们使用工厂方法模式来改进代码。 首先,修改抽象图形类 Shape 保持不变。 然后,修改具体的图形类 CircleRectangle 也保持不变。 接下来,定义抽象图形工厂类 ShapeFactory

#import "Shape.h"

@interface ShapeFactory : NSObject

- (Shape *)createShape;

@end

再定义具体的图形工厂类 CircleFactoryRectangleFactory

#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;
}

在上述代码中,CircleFactoryRectangleFactory 分别负责创建 CircleRectangle 对象。客户端代码通过创建具体的工厂类对象来获取相应的图形对象,这样当需要添加新的图形类型时,只需要创建新的具体工厂类和具体图形类,而不需要修改现有的工厂类代码,符合开闭原则。

(三)工厂方法模式优缺点

  1. 优点
    • 符合开闭原则,当需要添加新的产品类时,只需要创建新的具体工厂类和具体产品类,不需要修改现有的代码。
    • 工厂类的职责得到了合理的分配,每个具体工厂类只负责创建一种产品对象,代码更加清晰和易于维护。
  2. 缺点
    • 当产品类型过多时,会导致具体工厂类的数量过多,增加了系统的复杂度。
    • 客户端代码需要了解具体工厂类的存在,与简单工厂模式相比,客户端代码的耦合度有所增加。

五、Objective - C 中的抽象工厂模式

(一)抽象工厂模式定义与结构

抽象工厂模式提供了一种创建一系列相关或相互依赖对象的方式,而无需指定它们具体的类。抽象工厂模式包含五个角色:

  1. 抽象工厂类(AbstractFactory):定义了创建一系列产品对象的抽象方法。
  2. 具体工厂类(ConcreteFactory):继承自抽象工厂类,实现了创建一系列产品对象的具体逻辑。
  3. 抽象产品类(AbstractProduct):定义了产品对象的公共接口。
  4. 具体产品类(ConcreteProduct):实现了抽象产品类的接口。
  5. 客户端(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

再定义具体工厂类 iOSUIFactoryMacOSUIFactory

#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;
}

在上述代码中,iOSUIFactoryMacOSUIFactory 分别负责创建 iOS 和 macOS 平台的按钮和文本框。客户端代码通过选择不同的工厂类来创建适合特定平台的 UI 组件,实现了产品对象的系列化创建。

(三)抽象工厂模式优缺点

  1. 优点
    • 隔离了具体产品类的创建,客户端代码只需要与抽象工厂类交互,降低了客户端与具体产品类的耦合度。
    • 方便切换产品族,例如从 iOS 平台切换到 macOS 平台,只需要更换具体工厂类即可。
    • 符合开闭原则,当需要添加新的产品族时,只需要创建新的具体工厂类和具体产品类,不需要修改现有的代码。
  2. 缺点
    • 抽象工厂模式的实现较为复杂,当产品族中的产品种类过多时,会导致代码量增加,维护难度加大。
    • 当需要添加新的产品类型时,不仅需要修改抽象工厂类的接口,还需要修改所有具体工厂类的代码,违反了开闭原则。

六、工厂模式在 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 项目中应用工厂模式,提高代码的质量和开发效率。