24种设计模式代码实例学习(一)七大设计原则

6,623 阅读17分钟

项目Demo

本文代码语言为Objective-C

设计模式是一种被广泛应用于软件工程的解决问题的方法。

它们可以帮助开发人员提高代码的可复用性、可维护性和可扩展性。设计模式的使用可以让开发人员更加专注于解决实际的问题而不是去考虑如何实现它们。

image.png

在1994年出版的《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书中阐述了这些模式。 这本书中共涵盖了23种设计模式,后来又有一种新的设计模式被加入,使得总数达到24种,这些设计模式可以分为三种类型,分别是:

1. 创建型模式(Creational Patterns)

2. 结构型模式(Structural Patterns)

3. 行为型模式(Behavioral Patterns)

在学习这些设计模式前,本文将会介绍设计模式的七大原则

1 总原则:开闭原则(Open/Closed Principle,OCP)

image.png 这个原则非常易于理解,同样,越是简单的原则,越是会成为设计模式中“基石”一样的存在。

该原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭

开闭原则的思想是:当新的需求出现时,应该尽可能地通过增加新的代码来满足这些需求,而不是直接修改现有代码。

通过增加新的代码,可以保证现有代码的稳定性,同时也可以提高代码的可维护性和可扩展性

例子

当面对一个需求变化时,如果没有采用开闭原则,我们通常会直接修改原有代码来适应新的需求,这可能会破坏原有代码的结构和稳定性。下面是一个不符合开闭原则的Objective-C 代码示例:

// 原有代码:汽车类
@interface Car : NSObject
@property (nonatomic, copy) NSString *brand;
@property (nonatomic, assign) NSInteger price;
- (void)startEngine;
@end

@implementation Car
- (void)startEngine {
    NSLog(@"Start Engine!");
}
@end

// 需求变化:添加自动驾驶功能
@implementation Car
- (void)startEngine {
    NSLog(@"Start Engine!");
    // 新增代码:自动驾驶功能
    NSLog(@"Auto Driving!");
}
@end

在上述代码中,原有的Car类有一个startEngine方法用于启动发动机,但是当需要添加自动驾驶功能时,我们直接在startEngine方法中添加了新的代码。这虽然可以满足新的需求,但是也破坏了原有代码的结构,违反了开闭原则的要求。

相反,如果我们采用开闭原则,就可以通过扩展现有代码来满足新的需求,而不需要修改现有的代码。下面是一个符合开闭原则的Objective-C代码示例:

// 原有代码:汽车类
@interface Car : NSObject
@property (nonatomic, copy) NSString *brand;
@property (nonatomic, assign) NSInteger price;
- (void)startEngine;
@end

@implementation Car
- (void)startEngine {
    NSLog(@"Start Engine!");
}
@end

// 新需求:添加自动驾驶功能
// 抽象类:汽车装饰器
@interface CarDecorator : Car
@property (nonatomic, strong) Car *car;
- (instancetype)initWithCar:(Car *)car;
@end

@implementation CarDecorator
- (instancetype)initWithCar:(Car *)car {
    if (self = [super init]) {
        self.car = car;
    }
    return self;
}
- (void)startEngine {
    [self.car startEngine];
}
@end

// 具体装饰器:自动驾驶装饰器
@interface AutoDriveDecorator : CarDecorator
- (void)startAutoDriving;
@end

@implementation AutoDriveDecorator
- (void)startAutoDriving {
    NSLog(@"Auto Driving!");
}
- (void)startEngine {
    [super startEngine];
    [self startAutoDriving];
}
@end

在上述代码中,我们采用了装饰器模式来实现开闭原则。首先,我们定义了一个抽象类CarDecorator,用于表示所有的汽车装饰器。然后,我们定义了一个具体装饰器AutoDriveDecorator,用于添加自动驾驶功能。在AutoDriveDecorator中,我们重写了startEngine方法,并在其中添加了自动驾驶功能。最后,我们可以通过创建一个AutoDriveDecorator对象来给汽车添加自动驾驶功能,而不需要修改原有的Car类。具体地,我们可以通过以下代码来实现:

// 创建一辆汽车
Car *car = [[Car alloc] init];
car.brand = @"Ford";
car.price = 100000;

// 使用装饰器来添加自动驾驶功能
AutoDriveDecorator *decorator = [[AutoDriveDecorator alloc] initWithCar:car];
[decorator startEngine];

通过扩展现有代码来满足新的需求,而不需要修改原有的代码。这种方式可以保证原有代码的结构和稳定性,同时也使得代码更加易于扩展和维护。

2 单一职责原则(Single responsibility principle, SRP)

image.png

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分.

单一职责原则可以帮助我们更好地组织代码,使得代码易于维护和扩展。通过遵循单一职责原则,我们可以使代码更加模块化,每个模块只负责一个职责,易于测试和重用。同时,当需求变化时,我们也可以更容易地对代码进行修改,而不会影响到其他模块。

例子

不遵循单一职责原则的代码示例:

// 不遵循单一职责原则的代码示例

@interface Employee : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *department;

- (void)calculateSalary;
- (void)performDailyTasks;

@end

@implementation Employee

- (void)calculateSalary {
    // 计算员工薪水的代码
}

- (void)performDailyTasks {
    // 执行员工日常任务的代码
}

@end

上面这个示例中,Employee 类实现了两个职责:计算员工薪水和执行员工日常任务。这违反了单一职责原则,因为一个类不应该有多个导致其变更的原因。

下面是一个遵循单一职责原则的示例代码,将 Employee 类的两个职责拆分成两个类:

// 遵循单一职责原则的代码示例

@interface Employee : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *department;

@end

@implementation Employee

// Employee 类的代码只包含员工的基本信息,不再包含计算薪水和执行日常任务的代码

@end

@interface SalaryCalculator : NSObject

- (void)calculateSalaryForEmployee:(Employee *)employee;

@end

@implementation SalaryCalculator

- (void)calculateSalaryForEmployee:(Employee *)employee {
    // 计算员工薪水的代码
    NSLog(@"calculateSalaryForEmployee: name: %@, age: %ld, department: %@", employee.name, employee.age, employee.department);
}

@end

@interface TaskPerformer : NSObject

- (void)performDailyTasksForEmployee:(Employee *)employee;

@end

@implementation TaskPerformer

- (void)performDailyTasksForEmployee:(Employee *)employee {
    // 执行员工日常任务的代码
    NSLog(@"performDailyTasksForEmployee: name: %@, age: %ld, department: %@", employee.name, employee.age, employee.department);
}

@end

上面这个示例中,我们将 Employee 类的两个职责分别拆分为 SalaryCalculator 类和 TaskPerformer 类。这样每个类只负责单一职责,易于维护和扩展,符合单一职责原则。

使用:

Employee *employee = [[Employee alloc] init];
employee.name = @"Layton Pike";
employee.age = 20;
employee.department = @"iOS development department";
SalaryCalculator *salaryCalculator = [[SalaryCalculator alloc] init];
[salaryCalculator calculateSalaryForEmployee:employee];
TaskPerformer *taskPerformer = [[TaskPerformer alloc] init];
[taskPerformer performDailyTasksForEmployee:employee];

在实际开发中,我们可以通过以下几个方面来遵循单一职责原则:

  • 分离职责:将一个类中的不同职责拆分成独立的类或模块,使每个类或模块只负责单一职责。
  • 抽象接口:使用抽象接口或协议来定义类或模块之间的交互方式,避免直接依赖具体的实现。
  • 限制类的大小:尽可能限制类的大小,避免过于复杂的类,可以通过拆分、继承、组合等方式来实现。
  • 定期重构:定期地对代码进行重构,去除重复代码,将代码按照职责进行组织,使得代码更易于维护和扩展。

总之,遵循单一职责原则是一个重要的设计原则,可以帮助我们写出更加模块化、可维护和可扩展的代码。

3 里氏替换原则(Liskov Substitution Principle, LSP)

image.png

核心:子类对象可以替换父类对象出现在程序中,而不影响程序的正确性。

这个原则可以提高程序的灵活性和可维护性,使程序更容易扩展和修改。

例子

假设有一个Animal 类和一个Dog类,Dog 类是Animal 类的子类。Animal 类有一个run 方法,它可以让动物奔跑;Dog 类继承了Animal 类,并重写了run 方法。

@interface Animal : NSObject
- (void)run;
@end

@implementation Animal
- (void)run {
    NSLog(@"Animal is running");
}
@end

@interface Dog : Animal
@end

@implementation Dog
- (void)run {
    NSLog(@"Dog is running");
}
@end

根据里氏替换原则,我们可以将Dog 对象赋值给Animal 对象,而程序仍然可以正常运行。例如:

Animal *animal = [[Dog alloc] init];
[animal run];

4 依赖倒转原则(Dependency Inversion Principle)

image.png

官方解释是:高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。

其实核心就是面向接口编程,而不是面向实现编程

这样的好处就是通过抽象接口,可以减少模块间的依赖关系,提高系统的灵活性和可维护性。

我们可以通过代码的例子更好的看出他们之间的不同:

例子

现在假设我们有一个Logger类,它负责将日志信息输出到控制台,还有一个MyClass类,它需要使用Logger类来输出日志信息。

没有遵守依赖倒转原则的代码:

Logger类:

@interface Logger : NSObject

- (void)log:(NSString *)message;

@end

@implementation Logger

- (void)log:(NSString *)message {
    NSLog(@"%@", message);
}
@end

MyClass类,它需要使用Logger类来输出日志信息:

@interface MyClass : NSObject

@property (nonatomic, strong) Logger *logger;

- (void)log;

@end

@implementation MyClass

- (void)doSomething {
    [self.logger log:@"Log something"];
}
@end

这里MyClass类依赖于Logger类,直接创建Logger对象,并在doSomething方法中调用Logger对象的log方法输出日志信息。这样做会造成MyClass类和Logger类之间的紧耦合,MyClass类无法独立于Logger类进行测试或扩展。

面向接口编程

为了遵守依赖倒转原则,我们需要对MyClass类进行改造。首先,我们需要定义一个Logger接口,包含一个log方法:

@protocol LoggerProtocol <NSObject>

- (void)log:(NSString *)message;

@end

然后,我们修改Logger类,使其遵守接口方法:

@interface Logger : NSObject <LoggerProtocol>

@end

@implementation Logger

- (void)log:(NSString *)message {
    NSLog(@"%@", message);
}

@end

然后,我们修改MyClass类,使其依赖于Logger接口而非具体的Logger类:

@interface MyClass : NSObject

@property (nonatomic, strong) Logger *logger;

- (void)log;
@end

@implementation MyClass

- (void)doSomething {
    [self.logger log:@"Log something"];
}

@end

现在MyClass类不再依赖于具体的Logger类,而是依赖于一个抽象的Logger接口。这样做可以将Logger类的实现细节与MyClass类解耦,使得MyClass类更加灵活和可维护。

实现:

MyClass *logClass = [[MyClass alloc] init];
Logger *logger = [[Logger alloc] init];
logClass.logger = logger;
[logClass log];

5 接口隔离原则(Interface Segregation Principle)

image.png

一个类不应该强迫其它类依赖它们不需要使用的方法,也就是说,一个类对另一个类的依赖应该建立在最小的接口上。

核心思想是:一个类应该只提供其它类需要使用的方法,而不应该强迫其它类依赖于它们不需要使用的方法。

通过遵守接口隔离原则,可以提高系统的灵活性、可维护性和可扩展性

例子

假设我们有一个Database类,它提供了数据库的一些基本操作,包括连接数据库、执行SQL语句等:

没有遵守接口隔离原则的代码:

@interface Database : NSObject

- (void)connect;
- (void)executeSQL:(NSString *)sql;
- (void)disconnect;

@end

@implementation Database

- (void)connect {
    NSLog(@"Connect to database");
}

- (void)executeSQL:(NSString *)sql {
    NSLog(@"Execute SQL: %@", sql);
}

- (void)disconnect {
    NSLog(@"Disconnect from database");
}

@end

现在我们有一个UserService类,它依赖于Database类来实现用户信息的存储:

@interface UserService : NSObject

@property (nonatomic, strong) Database *database;

- (void)saveUser:(NSString *)name withEmail:(NSString *)email;

@end

@implementation UserService

- (void)saveUser:(NSString *)name withEmail:(NSString *)email {
    [self.database connect];
    NSString *sql = [NSString stringWithFormat:@"INSERT INTO users(name, email) VALUES('%@', '%@')", name, email];
    [self.database executeSQL:sql];
    [self.database disconnect];
}
@end

这里UserService类依赖于Database类,并使用它的connectexecuteSQLdisconnect方法。但实际上,UserService类只需要使用Database类中的executeSQL方法来完成用户信息的存储和查询,而不需要使用connectdisconnect方法。因此,UserService类应该只依赖于一个更小的接口,即只需要一个executeSQL方法。

为了遵守接口隔离原则,我们需要对Database类进行改造。首先,我们定义一个DatabaseProtocol接口,包含一个executeSQL方法:

@protocol DatabaseProtocol <NSObject>

- (void)executeSQL:(NSString *)sql;

@end

然后,我们修改Database类,使其实现DatabaseProtocol接口:

@interface Database : NSObject <DatabaseProtocol>

- (void)connect;
- (void)disconnect;

@end

@implementation Database

#pragma mark - Method

- (void)connect {
    NSLog(@"Connect to database");
}

- (void)disConnect {
    NSLog(@"Disconnect from database");
}

#pragma mark - DatabaseProtocol

- (void)executeSQL:(NSString *)sql {
    NSLog(@"Execute SQL: %@", sql);
}

修改UserService类,使其依赖于DatabaseProtocol接口而不是具体的Database类:

@interface UserService : NSObject

@property (nonatomic, strong) id<DatabaseProtocol> database;

- (instancetype)initWithDatabase:(id<DatabaseProtocol>)database;

- (void)saveUser:(NSString *)name withEmail:(NSString *)email;

@end

@implementation UserService

- (instancetype)initWithDatabase:(id<DatabaseProtocol>)database {
    self = [super init];
    if (self) {
        _database = database;
    }
    return self;
}

- (void)saveUser:(NSString *)name withEmail:(NSString *)email {
    [self.database executeSQL:[NSString stringWithFormat:@"INSERT INTO users(name, email) VALUES('%@', '%@')", name, email]];
}

@end

实现:

Database *database = [[Database alloc] init];
UserService *userService = [[UserService alloc] initWithDatabase:database];
[database connect];
[userService saveUser:@"SungKaikai" withEmail:@"aaaa.com"];
[database disConnect];

接口隔离原则的核心是为了让接口具备高内聚性低耦合性

如果一个接口过于臃肿,就需要将其拆分成多个小的接口,使得每个接口中只包含必要的方法。这样的好处是:

  1. 接口更加具有内聚性:每个接口只需要关注自己的功能,而不需要关注其他接口中的方法,因此能够使接口更加专注和具有内聚性。
  2. 接口之间的耦合度更低:每个接口只依赖于必要的方法,而不依赖于其他不必要的方法,因此能够使接口之间的耦合度更低。
  3. 代码的复用性更高:每个接口只包含必要的方法,因此能够使得代码的复用性更高,也能够提高代码的可读性和可维护性。

6 迪米特法则(最少知道原则)(The Law of Demeter, LoD)

image.png

一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。

最少知道原则的另一个表达方式是:只与直接的朋友通信。

类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。

例子

假设有一个购物车类ShoppingCart,它有一个方法 addGoodsInfo,用于添加商品到购物车中。这个方法需要从产品类Goods中获取商品信息。最初的ShoppingCart类可能会直接和这个类进行耦合,代码如下:

// 商品类
@interface Goods : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGFloat price;
@end

@implementation Goods
@end

// 购物车类
@interface ShoppingCart : NSObject

@property (nonatomic, strong) NSMutableArray<Goods *> *goodsArray;

- (void)addGoodsInfo;

- (void)printShoppingList;

@end

@implementation ShoppingCart

- (instancetype)init {
    self = [super init];
    if (self) {
        _goodsArray = [NSMutableArray array];
    }
    return self;
}

- (void)addGoodsInfo {
    // apple
    Goods *apple = [[Goods alloc] init];
    apple.name = @"apple";
    apple.price = 5.0;
    // orange
    Goods *orange = [[Goods alloc] init];
    orange.name = @"orange";
    orange.price = 5.0;
    [self.goodsArray addObject:apple];
    NSLog(@"添加商品 %@ 到购物车", apple.name);
    [self.goodsArray addObject:orange];
    NSLog(@"添加商品 %@ 到购物车", orange.name);
}

- (void)printShoppingList {
    NSLog(@"========== 购物车清单 ==========");
    for (Goods *info in self.goodsArray) {
        NSLog(@"%@ , %f", info.name, info.price);
    }
    NSLog(@"================================");
}
@end

在上面的代码中,ShoppingCart类直接依赖了Goods类,这样会导致ShoppingCart类的修改会影响到这两个类,而且如果Goods类的实现发生了变化,ShoppingCart类也必须相应地进行修改。

为了遵守最少知道原则,我们可以修改ShoppingCart类的实现,将获取商品信息逻辑放到方法外部,这样 ShoppingCart类只需要调用这个类的方法即可。修改后的代码如下:

// 商品类
@interface Goods : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGFloat price;
@end

@implementation Goods
@end

// 购物车类
@interface ShoppingCart : NSObject

@property (nonatomic, strong) NSMutableArray<Goods *> *goodsArray;

- (void)addGoodsInfo:(Goods *)goods;

- (void)printShoppingList;

@end

@implementation ShoppingCart
- (instancetype)init {
    self = [super init];
    if (self) {
        _goodsArray = [NSMutableArray array];
    }
    return self;
}

- (void)addGoodsInfo:(Goods *)goods {
    [self.goodsArray addObject:goods];
    NSLog(@"添加商品 %@ 到购物车", goods.name);
}

- (void)printShoppingList {
    NSLog(@"========== 购物车清单 ==========");
    for (Goods *info in self.goodsArray) {
        NSLog(@"%@ , %f", info.name, info.price);
    }
    NSLog(@"================================");
}

@end

@interface User : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) ShoppingCart *shoppingCart;

- (void)addGoodsToCart:(Goods *)goods;

- (void)checkOut;

@end

@implementation User

- (instancetype)init {
    self = [super init];
    if (self) {
        _shoppingCart = [[ShoppingCart alloc] init];
    }
    return self;
}

- (void)addGoodsToCart:(Goods *)goods {
    [self.shoppingCart addGoodsInfo:goods];
}

- (void)checkOut {
    NSLog(@"用户 %@ 结账,购物清单如下:", self.name);
    [self.shoppingCart printShoppingList];
}

@end

在上面的代码示例中,每个类都只和自己直接的朋友进行通信,遵循了最少知道原则。User 类只依赖于 ShoppingCart 类,而不直接依赖于 Goods 类。ShoppingCart 类只依赖于Goods 类。这样,当 Goods 类发生变化时,只会对 ShoppingCart 类产生影响,而不会对 User 类产生影响。 以下为实现代码:

User *user = [[User alloc] init];
user.name = @"Kaikai Pike";
// apple
Goods *apple = [[Goods alloc] init];
apple.name = @"apple";
apple.price = 5.0;
// orange
Goods *orange = [[Goods alloc] init];
orange.name = @"orange";
orange.price = 5.0;
// 用户添加商品到购物车
[user addGoodsToCart:apple];
[user addGoodsToCart:orange];
// 结账
[user checkOut];

7 合成复用原则(Composite Reuse Principle, CRP)

image.png 当我们需要在一个类中使用另一个类的功能时,有两种方式:继承和合成/聚合。

继承是指子类继承父类的属性和方法,可以重写父类的方法,但也存在耦合性较高的问题,当父类修改时,子类也需要相应地修改。

合成/聚合是指一个类作为另一个类的成员变量,通过调用该成员变量的方法来实现该类的功能

例子

假设我们有一个社交媒体应用程序,其中有多个视图控制器用于显示不同的内容。每个视图控制器都需要显示一个头像图像,并且需要处理用户点击头像时的事件。我们可以使用继承来实现这个功能,如下所示:

@interface AvatarViewController : UIViewController
@property (nonatomic, strong) UIImageView *imageView;
- (void)handleAvatarTap;
@end

@implementation AvatarViewController
- (instancetype)init {
    self = [super init];
    if (self) {
        _imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"default_avatar"]];
        _imageView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAvatarTap)];
        [_imageView addGestureRecognizer:tapGesture];
        [self.view addSubview:_imageView];
    }
    return self;
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.imageView.frame = CGRectMake(0, 0, 100, 100);
}

- (void)handleAvatarTap {
    NSLog(@"Avatar tapped in %@", NSStringFromClass([self class]));
}
@end

@interface ProfileViewController : AvatarViewController
@end

@implementation ProfileViewController
@end

@interface FeedViewController : AvatarViewController
@end

@implementation FeedViewController
@end

在这个例子中,我们创建了一个AvatarViewController基类,它包含了一个imageView属性和handleAvatarTap方法。然后,我们创建了两个子类ProfileViewControllerFeedViewController,它们都继承自AvatarViewController,重写了handleAvatarTap方法以处理自己的逻辑。在每个子类中,我们都可以使用imageView属性来显示头像,并处理用户点击头像时的事件。

虽然这种方法能够复用一部分代码,但它存在一些问题。例如,如果我们需要添加新的视图控制器来显示头像,我们需要创建一个新的子类,并重复实现大部分相同的代码

现在,让我们使用合成复用原则来改进这个例子。我们将创建一个单独的头像视图组件,它负责显示头像和处理头像点击事件。每个视图控制器都将包含一个头像视图组件的实例,并将其添加到自己的视图中。这样,我们可以轻松地创建新的视图控制器来显示头像,而无需创建新的子类。

下面是使用组合实现的例子:

@interface AvatarView : UIView
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, copy) void (^avatarTappedBlock)(void);
@end

@implementation AvatarView
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"0"]];
        _imageView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAvatarTap)];
        [_imageView addGestureRecognizer:tapGesture];
        [self addSubview:_imageView];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.imageView.frame = CGRectMake(0, 0, 100, 100);
}

- (void)handleAvatarTap {
    if (self.avatarTappedBlock) {
        self.avatarTappedBlock();
    }
}

@interface ProfileViewController : UIViewController

@property (nonatomic, strong) AvatarView *avatarView;

@end

@implementation ProfileViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.avatarView = [[AvatarView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    self.avatarView.avatarTappedBlock = ^{
        NSLog(@"Avatar tapped in ProfileViewController");
    };
    [self.view addSubview:self.avatarView];
}
@end

@interface FeedViewController : UIViewController

@property (nonatomic, strong) AvatarView *avatarView;

@end

@implementation FeedViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.avatarView = [[AvatarView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    self.avatarView.avatarTappedBlock = ^{
        NSLog(@"Avatar tapped in FeedViewController");
    };
    [self.view addSubview:self.avatarView];
}
@end

在这个例子中,我们创建了两个视图控制器ProfileViewControllerFeedViewController。这两个视图控制器都包含了一个AvatarView的实例,并且为avatarTappedBlock属性赋值了一个块,用于处理头像的点击事件。这两个视图控制器都使用了AvatarView来显示头像,而不是继承 AvatarViewController类。这种方法使得我们可以轻松地创建新的视图控制器来显示头像,而无需创建新的子类。

总之,组合和继承都可以用来实现代码的复用,但是它们之间有着不同的优缺点。在使用继承时,我们需要注意代码的耦合度,避免代码的重复和臃肿。在使用组合时,我们需要将代码分解为更小的组件,并注意组件之间的接口设计。

项目Demo

image.png

24-Design-Patterns

下一篇

下一篇:创建型模式