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

Objective-C中的代码重构与设计模式应用

2022-10-204.3k 阅读

代码重构的概念与意义

在Objective-C编程中,代码重构是一个至关重要的实践,它能够显著提升代码的质量、可维护性和可扩展性。简单来说,代码重构就是在不改变代码外部行为的前提下,对代码内部结构进行调整和优化。

为什么要进行代码重构呢?随着项目的不断发展和迭代,代码库会逐渐变大,复杂度也会随之增加。最初编写的代码可能在当时的场景下是可行的,但随着需求的变化,可能会出现重复代码、过长的方法、紧密耦合等问题。这些问题会使得代码难以理解、修改和扩展,甚至可能引入更多的错误。通过代码重构,可以消除这些问题,让代码更加清晰、简洁且易于维护。

例如,假设有一个简单的iOS应用,用于显示用户的个人信息。在开发初期,为了快速实现功能,可能会在视图控制器中直接编写大量获取和显示用户信息的代码:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *userEmail;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 直接在视图控制器中获取用户信息
    self.userName = @"John Doe";
    self.userEmail = @"johndoe@example.com";
    
    // 显示用户信息
    UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
    nameLabel.text = self.userName;
    [self.view addSubview:nameLabel];
    
    UILabel *emailLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 150, 200, 30)];
    emailLabel.text = self.userEmail;
    [self.view addSubview:emailLabel];
}

@end

这段代码虽然实现了基本功能,但存在一些问题。获取用户信息的逻辑直接写在视图控制器中,导致视图控制器的职责不单一,而且如果后续获取用户信息的方式发生变化(比如从网络请求获取),就需要在视图控制器中大量修改代码。这时候就需要进行代码重构,将获取用户信息的逻辑提取到一个单独的服务类中:

#import <Foundation/Foundation.h>

@interface UserService : NSObject

+ (NSString *)fetchUserName;
+ (NSString *)fetchUserEmail;

@end

@implementation UserService

+ (NSString *)fetchUserName {
    // 这里可以是从数据库、网络等获取用户名
    return @"John Doe";
}

+ (NSString *)fetchUserEmail {
    // 这里可以是从数据库、网络等获取用户邮箱
    return @"johndoe@example.com";
}

@end

然后在视图控制器中调用这个服务类:

#import "ViewController.h"
#import "UserService.h"

@interface ViewController ()

@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *userEmail;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.userName = [UserService fetchUserName];
    self.userEmail = [UserService fetchUserEmail];
    
    // 显示用户信息
    UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
    nameLabel.text = self.userName;
    [self.view addSubview:nameLabel];
    
    UILabel *emailLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 150, 200, 30)];
    emailLabel.text = self.userEmail;
    [self.view addSubview:emailLabel];
}

@end

通过这样的重构,视图控制器的职责变得更加清晰,只负责显示用户信息,而获取用户信息的逻辑封装在UserService类中。如果获取用户信息的方式发生变化,只需要在UserService类中进行修改,而不需要修改视图控制器的代码。

常见的代码重构手法

提取方法(Extract Method)

提取方法是一种非常常用的重构手法,它将一段代码从一个方法中提取出来,形成一个新的独立方法。这样做可以使原方法更加简洁,提高代码的可读性和可维护性。例如,在一个处理订单的方法中,如果有一段复杂的计算订单总价的代码:

#import <Foundation/Foundation.h>

@interface Order : NSObject

@property (nonatomic, strong) NSArray<NSNumber *> *productPrices;

- (void)processOrder {
    // 订单处理逻辑
    NSNumber *totalPrice = @0;
    for (NSNumber *price in self.productPrices) {
        totalPrice = @([totalPrice doubleValue] + [price doubleValue]);
    }
    // 其他订单处理逻辑,比如保存订单到数据库等
    NSLog(@"Total price: %@", totalPrice);
}

@end

可以将计算订单总价的代码提取出来,形成一个新的方法:

#import <Foundation/Foundation.h>

@interface Order : NSObject

@property (nonatomic, strong) NSArray<NSNumber *> *productPrices;

- (NSNumber *)calculateTotalPrice {
    NSNumber *totalPrice = @0;
    for (NSNumber *price in self.productPrices) {
        totalPrice = @([totalPrice doubleValue] + [price doubleValue]);
    }
    return totalPrice;
}

- (void)processOrder {
    NSNumber *totalPrice = [self calculateTotalPrice];
    // 其他订单处理逻辑,比如保存订单到数据库等
    NSLog(@"Total price: %@", totalPrice);
}

@end

这样processOrder方法变得更加简洁,而且calculateTotalPrice方法可以在其他地方复用。

内联方法(Inline Method)

与提取方法相反,内联方法是将一个方法的调用替换为方法的实际代码。当一个方法的逻辑非常简单,只有一行代码,并且方法调用的开销大于方法本身的执行开销时,可以考虑使用内联方法。例如:

#import <Foundation/Foundation.h>

@interface MathUtils : NSObject

+ (NSInteger)add:(NSInteger)a and:(NSInteger)b;

@end

@implementation MathUtils

+ (NSInteger)add:(NSInteger)a and:(NSInteger)b {
    return a + b;
}

@end

在另一个类中调用这个方法:

#import "MathUtils.h"

@interface Calculator : NSObject

- (NSInteger)performCalculation {
    NSInteger result = [MathUtils add:2 and:3];
    return result;
}

@end

由于add方法非常简单,可以将其内联:

#import <Foundation/Foundation.h>

@interface Calculator : NSObject

- (NSInteger)performCalculation {
    NSInteger result = 2 + 3;
    return result;
}

@end

这样可以减少方法调用的开销,提高代码的执行效率。

提炼类(Extract Class)

当一个类承担了过多的职责,变得过于庞大和复杂时,可以使用提炼类的重构手法。将相关的属性和方法从原类中提取出来,形成一个新的类。例如,有一个Employee类,既包含员工的基本信息,又包含员工的考勤记录和薪资计算逻辑:

#import <Foundation/Foundation.h>

@interface Employee : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSDate *hireDate;
@property (nonatomic, strong) NSMutableArray<NSDate *> *attendanceDates;

- (NSInteger)calculateSalary;

@end

@implementation Employee

- (NSInteger)calculateSalary {
    // 复杂的薪资计算逻辑
    return 5000;
}

@end

可以将考勤记录相关的逻辑提取到一个新的Attendance类中,将薪资计算逻辑提取到一个SalaryCalculator类中:

#import <Foundation/Foundation.h>

@interface Attendance : NSObject

@property (nonatomic, strong) NSMutableArray<NSDate *> *attendanceDates;

@end

@implementation Attendance

@end

@interface SalaryCalculator : NSObject

+ (NSInteger)calculateSalaryForEmployee:(Employee *)employee;

@end

@implementation SalaryCalculator

+ (NSInteger)calculateSalaryForEmployee:(Employee *)employee {
    // 复杂的薪资计算逻辑
    return 5000;
}

@end

@interface Employee : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSDate *hireDate;
@property (nonatomic, strong) Attendance *attendance;

- (NSInteger)calculateSalary {
    return [SalaryCalculator calculateSalaryForEmployee:self];
}

@end

@implementation Employee

@end

这样Employee类的职责更加单一,代码的结构也更加清晰。

设计模式在Objective-C中的应用

单例模式(Singleton Pattern)

单例模式确保一个类只有一个实例,并提供一个全局访问点。在Objective-C中,实现单例模式有多种方式。传统的方式是使用static变量和dispatch_once

#import <Foundation/Foundation.h>

@interface AppSettings : NSObject

@property (nonatomic, strong) NSString *theme;
@property (nonatomic, assign) BOOL isDarkMode;

+ (instancetype)sharedSettings;

@end

@implementation AppSettings

static AppSettings *sharedInstance = nil;

+ (instancetype)sharedSettings {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

@end

在应用中,可以通过[AppSettings sharedSettings]来获取唯一的实例:

#import "AppSettings.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    AppSettings *settings = [AppSettings sharedSettings];
    settings.theme = @"Default Theme";
    settings.isDarkMode = NO;
}

@end

单例模式在很多场景下都非常有用,比如应用的配置管理、数据库连接管理等。它可以避免创建多个不必要的实例,节省资源。

工厂模式(Factory Pattern)

工厂模式提供了一种创建对象的方式,将对象的创建和使用分离。在Objective-C中,可以通过类方法来实现简单的工厂模式。例如,有一个图形绘制的应用,需要创建不同类型的图形:

#import <Foundation/Foundation.h>

@interface Shape : NSObject

- (void)draw;

@end

@implementation Shape

- (void)draw {
    NSLog(@"Drawing a shape");
}

@end

@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

@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 [[Shape alloc] init];
}

@end

在使用时,可以通过ShapeFactory来创建不同类型的图形:

#import "ShapeFactory.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Shape *circle = [ShapeFactory createShapeWithType:@"circle"];
    [circle draw];
    
    Shape *rectangle = [ShapeFactory createShapeWithType:@"rectangle"];
    [rectangle draw];
}

@end

工厂模式使得代码更加灵活,当需要添加新的图形类型时,只需要在ShapeFactory中添加相应的创建逻辑,而不需要修改使用图形的代码。

观察者模式(Observer Pattern)

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。在Objective-C中,可以使用NSNotificationCenter来实现类似观察者模式的功能。例如,有一个新闻应用,当有新的新闻发布时,需要通知所有关注该类型新闻的用户:

#import <Foundation/Foundation.h>

@interface NewsPublisher : NSObject

+ (instancetype)sharedPublisher;
- (void)publishNews:(NSString *)news;

@end

@implementation NewsPublisher

static NewsPublisher *sharedInstance = nil;

+ (instancetype)sharedPublisher {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void)publishNews:(NSString *)news {
    NSDictionary *userInfo = @{@"news": news};
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NewNewsPublished" object:self userInfo:userInfo];
}

@end

@interface NewsSubscriber : NSObject

- (void)handleNewsNotification:(NSNotification *)notification;

@end

@implementation NewsSubscriber

- (void)handleNewsNotification:(NSNotification *)notification {
    NSString *news = notification.userInfo[@"news"];
    NSLog(@"Received news: %@", news);
}

@end

在应用中,订阅者可以注册监听通知:

#import "NewsPublisher.h"
#import "NewsSubscriber.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NewsSubscriber *subscriber = [[NewsSubscriber alloc] init];
    [[NSNotificationCenter defaultCenter] addObserver:subscriber selector:@selector(handleNewsNotification:) name:@"NewNewsPublished" object:nil];
    
    NewsPublisher *publisher = [NewsPublisher sharedPublisher];
    [publisher publishNews:@"New technology breakthrough!"];
}

@end

这样,当有新的新闻发布时,所有注册的订阅者都会收到通知并进行相应的处理。

结合代码重构与设计模式提升代码质量

在实际的Objective-C项目开发中,代码重构和设计模式往往是相辅相成的。通过代码重构,可以为设计模式的应用创造更好的条件,而设计模式的应用又可以进一步指导代码重构的方向。

例如,在一个电商应用中,最初的购物车功能可能是在一个视图控制器中直接实现的,代码比较混乱,存在重复计算购物车总价等问题。通过代码重构,将购物车相关的逻辑提取到一个单独的CartManager类中:

#import <Foundation/Foundation.h>

@interface Product : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) double price;

@end

@implementation Product

@end

@interface CartManager : NSObject

@property (nonatomic, strong) NSMutableArray<Product *> *cartItems;

- (void)addProductToCart:(Product *)product;
- (void)removeProductFromCart:(Product *)product;
- (double)calculateCartTotal;

@end

@implementation CartManager

- (void)addProductToCart:(Product *)product {
    if (!self.cartItems) {
        self.cartItems = [NSMutableArray array];
    }
    [self.cartItems addObject:product];
}

- (void)removeProductFromCart:(Product *)product {
    [self.cartItems removeObject:product];
}

- (double)calculateCartTotal {
    double total = 0;
    for (Product *product in self.cartItems) {
        total += product.price;
    }
    return total;
}

@end

此时,购物车的逻辑已经相对清晰。但如果需要在购物车总价发生变化时通知其他模块(比如更新显示总价的视图),可以应用观察者模式。首先定义一个通知名称和相关的通知发送逻辑:

#import <Foundation/Foundation.h>

FOUNDATION_EXPORT NSString *const CartTotalDidChangeNotification;

@interface CartManager : NSObject

@property (nonatomic, strong) NSMutableArray<Product *> *cartItems;

- (void)addProductToCart:(Product *)product;
- (void)removeProductFromCart:(Product *)product;
- (double)calculateCartTotal;

@end

@implementation CartManager

NSString *const CartTotalDidChangeNotification = @"CartTotalDidChangeNotification";

- (void)addProductToCart:(Product *)product {
    if (!self.cartItems) {
        self.cartItems = [NSMutableArray array];
    }
    [self.cartItems addObject:product];
    double total = [self calculateCartTotal];
    NSDictionary *userInfo = @{@"total": @(total)};
    [[NSNotificationCenter defaultCenter] postNotificationName:CartTotalDidChangeNotification object:self userInfo:userInfo];
}

- (void)removeProductFromCart:(Product *)product {
    [self.cartItems removeObject:product];
    double total = [self calculateCartTotal];
    NSDictionary *userInfo = @{@"total": @(total)};
    [[NSNotificationCenter defaultCenter] postNotificationName:CartTotalDidChangeNotification object:self userInfo:userInfo];
}

- (double)calculateCartTotal {
    double total = 0;
    for (Product *product in self.cartItems) {
        total += product.price;
    }
    return total;
}

@end

然后在需要监听购物车总价变化的地方注册通知:

#import "CartManager.h"

@interface TotalPriceViewController : UIViewController

@end

@implementation TotalPriceViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateTotalPrice:) name:CartTotalDidChangeNotification object:nil];
}

- (void)updateTotalPrice:(NSNotification *)notification {
    NSNumber *total = notification.userInfo[@"total"];
    // 更新显示总价的视图
    NSLog(@"New cart total: %@", total);
}

@end

通过这样的方式,结合代码重构和设计模式,使得购物车功能的代码更加清晰、可维护,并且具备良好的扩展性。

在处理复杂业务逻辑时,比如订单处理流程,可能会涉及多个类之间的交互和状态变化。通过代码重构,可以将不同的职责分配到合适的类中,然后应用设计模式来管理这些类之间的关系。例如,可以使用状态模式来处理订单在不同状态下的行为。假设订单有“未支付”、“已支付”、“已发货”等状态:

#import <Foundation/Foundation.h>

@interface Order;

@interface OrderState : NSObject

- (void)handleOrder:(Order *)order;

@end

@implementation OrderState

- (void)handleOrder:(Order *)order {
    NSLog(@"Handling order in default state");
}

@end

@interface UnpaidOrderState : OrderState

- (void)handleOrder:(Order *)order;

@end

@implementation UnpaidOrderState

- (void)handleOrder:(Order *)order {
    NSLog(@"Order is unpaid. Please make a payment.");
}

@end

@interface PaidOrderState : OrderState

- (void)handleOrder:(Order *)order;

@end

@implementation PaidOrderState

- (void)handleOrder:(Order *)order {
    NSLog(@"Order is paid. Preparing to ship.");
}

@end

@interface ShippedOrderState : OrderState

- (void)handleOrder:(Order *)order;

@end

@implementation ShippedOrderState

- (void)handleOrder:(Order *)order {
    NSLog(@"Order is shipped. Tracking number: 123456789");
}

@end

@interface Order : NSObject

@property (nonatomic, strong) OrderState *currentState;

- (void)setState:(OrderState *)state;
- (void)handleOrder;

@end

@implementation Order

- (void)setState:(OrderState *)state {
    self.currentState = state;
}

- (void)handleOrder {
    [self.currentState handleOrder:self];
}

@end

在使用时,可以根据订单的实际状态设置相应的状态对象:

#import "Order.h"

@interface OrderProcessor : NSObject

@end

@implementation OrderProcessor

- (void)processOrder {
    Order *order = [[Order alloc] init];
    order.currentState = [[UnpaidOrderState alloc] init];
    [order handleOrder];
    
    order.currentState = [[PaidOrderState alloc] init];
    [order handleOrder];
    
    order.currentState = [[ShippedOrderState alloc] init];
    [order handleOrder];
}

@end

这样,通过代码重构将订单状态相关的逻辑分离出来,再应用状态模式,使得订单处理流程的代码更加清晰、易于理解和维护。在面对业务需求变化,比如新增订单状态或者修改某个状态下的行为时,只需要在相应的状态类中进行修改,而不会影响其他部分的代码。

再比如,在一个图片加载和缓存的功能中,最初可能是在视图控制器中直接实现图片的加载和缓存逻辑,代码耦合度较高。通过代码重构,将图片加载和缓存的逻辑提取到一个ImageLoader类中:

#import <UIKit/UIKit.h>

@interface ImageLoader : NSObject

@property (nonatomic, strong) NSCache<NSString *, UIImage *> *imageCache;

- (UIImage *)loadImageFromURL:(NSURL *)url;

@end

@implementation ImageLoader

- (instancetype)init {
    self = [super init];
    if (self) {
        self.imageCache = [[NSCache alloc] init];
    }
    return self;
}

- (UIImage *)loadImageFromURL:(NSURL *)url {
    UIImage *cachedImage = [self.imageCache objectForKey:url.absoluteString];
    if (cachedImage) {
        return cachedImage;
    }
    
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    if (image) {
        [self.imageCache setObject:image forKey:url.absoluteString];
    }
    return image;
}

@end

此时,图片加载和缓存的逻辑已经相对独立。为了提高代码的灵活性和可扩展性,可以应用策略模式。假设后续需要支持不同的图片加载策略,比如优先从本地缓存加载,如果本地缓存没有再从网络加载,或者直接从网络加载不使用缓存等。可以定义一个图片加载策略的协议和不同的策略实现类:

#import <UIKit/UIKit.h>

@protocol ImageLoadingStrategy <NSObject>

- (UIImage *)loadImageFromURL:(NSURL *)url;

@end

@interface DefaultImageLoadingStrategy : NSObject <ImageLoadingStrategy>

@property (nonatomic, strong) NSCache<NSString *, UIImage *> *imageCache;

- (instancetype)init;
- (UIImage *)loadImageFromURL:(NSURL *)url;

@end

@implementation DefaultImageLoadingStrategy

- (instancetype)init {
    self = [super init];
    if (self) {
        self.imageCache = [[NSCache alloc] init];
    }
    return self;
}

- (UIImage *)loadImageFromURL:(NSURL *)url {
    UIImage *cachedImage = [self.imageCache objectForKey:url.absoluteString];
    if (cachedImage) {
        return cachedImage;
    }
    
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    if (image) {
        [self.imageCache setObject:image forKey:url.absoluteString];
    }
    return image;
}

@end

@interface NetworkOnlyImageLoadingStrategy : NSObject <ImageLoadingStrategy>

- (UIImage *)loadImageFromURL:(NSURL *)url;

@end

@implementation NetworkOnlyImageLoadingStrategy

- (UIImage *)loadImageFromURL:(NSURL *)url {
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    return [UIImage imageWithData:imageData];
}

@end

@interface ImageLoader : NSObject

@property (nonatomic, strong) id<ImageLoadingStrategy> loadingStrategy;

- (UIImage *)loadImageFromURL:(NSURL *)url;

@end

@implementation ImageLoader

- (UIImage *)loadImageFromURL:(NSURL *)url {
    return [self.loadingStrategy loadImageFromURL:url];
}

@end

在使用时,可以根据需要设置不同的图片加载策略:

#import "ImageLoader.h"
#import "DefaultImageLoadingStrategy.h"
#import "NetworkOnlyImageLoadingStrategy.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    ImageLoader *imageLoader = [[ImageLoader alloc] init];
    imageLoader.loadingStrategy = [[DefaultImageLoadingStrategy alloc] init];
    NSURL *imageURL = [NSURL URLWithString:@"https://example.com/image.jpg"];
    UIImage *image = [imageLoader loadImageFromURL:imageURL];
    
    // 切换到网络-only策略
    imageLoader.loadingStrategy = [[NetworkOnlyImageLoadingStrategy alloc] init];
    image = [imageLoader loadImageFromURL:imageURL];
}

@end

通过这样的方式,先进行代码重构,将相关逻辑分离出来,然后应用策略模式,使得图片加载和缓存的功能更加灵活,能够适应不同的需求变化。无论是增加新的图片加载策略,还是修改现有策略的实现,都可以在不影响其他部分代码的情况下进行。

综上所述,在Objective-C开发中,代码重构和设计模式的应用是提升代码质量、可维护性和可扩展性的关键手段。通过合理地运用各种代码重构手法和设计模式,可以打造出更加健壮、灵活且易于理解的软件系统。开发者应该不断地在实践中积累经验,熟练掌握这些技术,以应对日益复杂的项目需求。