【iOS】设计模式的六大原则
【iOS】设计模式的六大原则
文章目录
- 【iOS】设计模式的六大原则
- 前言
- 开闭原则——OCP
- 单一职能原则——SRP
- 里氏替换原则——LSP
- 依赖倒置原则——DLP
- 接口隔离原则——ISP
- 迪米特法则——LoD
- 小结
前言
笔者这段时间看了一下有关于设计模式的七大原则,下面代码示例均为OC。
开闭原则——OCP
这原则要求很简单:
- 该原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
开闭原则的思想是:当新的需求出现时,应该尽可能地通过增加新的代码来满足这些需求,而不是直接修改现有代码。
正如iOS开发中的分类的思想一样。
下面给出一个例子:
//下面是一个有关于Car的例子
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@end
NS_ASSUME_NONNULL_END
#import "Car.h"
@implementation Car
- (void) startEngine { // 一个汽车启动的业务
NSLog(@"the car go to ran");
}
@end
根据我们这个原则的思想来说的话,我们如果想添加一个新的功能,比方说自动泊车,我们应该是添加一个方法,而不是在原先的方法上进行一个修改:
错误案例:
//下面是一个有关于Car的例子
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@end
NS_ASSUME_NONNULL_END
#import "Car.h"
@implementation Car
- (void) startEngine { // 一个汽车启动的业务
NSLog(@"the car go to ran");
//添加自动泊车
NSLog(@"the car begin to park automatically");
}
@end
这样很显然不符合我们的开闭原则,下面是一个正确的案例:
#import "Car.h"
NS_ASSUME_NONNULL_BEGIN
//这里创建一个抽象类来实现,这个抽象类表示所有的一个汽车装饰器
@interface CarDecorator : Car
@property (nonatomic, strong) Car* car;
-(instancetype)initWithCar:(Car*)car;
@end
NS_ASSUME_NONNULL_END
#import "CarDecorator.h"
//这里是这个抽象类的一个具体实现
@implementation CarDecorator
- (instancetype)initWithCar:(Car *)car {
if ([super init]) {
self.car = car;
}
return self;
}
- (void)startEngine {
[self.car startEngine];
}
@end
//下面是一个具体某一类别的实现:
#import "CarDecorator.h"
NS_ASSUME_NONNULL_BEGIN
@interface ElectricCarDecorator : CarDecorator
-(void)autoParking;
@end
NS_ASSUME_NONNULL_END
#import "ElectricCarDecorator.h"
@implementation ElectricCarDecorator
-(void)autoParking { // 创建一个新的方法用于实现我们的一个自动泊车
NSLog(@"Auto Parking");
}
- (void)startEngine { //在这里重写父类的方法,让这个方法在实现我们想要的自动泊车功
[super startEngine];
[self autoParking];
}
@end
这上面的我们创建了一个新的抽象类来执行他新的一个方法,然后在子类中重写有关于父类的一个方法,既保证了代码不会被修改的同时,实现了一个新的功能,这就体现出了我们的一个开闭原则。
单一职能原则——SRP
该原则要求:
- 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分.
要点:
- 一个类只负责一个职能,类的设计应该避免包含过多的一个功能
- 高内聚,低耦合
- 避免滥用接口
下面是一个错误的示例:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Employee : NSObject // 这是一个工人的基本信息的内容
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
-(void)calculateSalary:(Employee*)employee; // 计算了工人的薪资
@end
NS_ASSUME_NONNULL_END
#import "Employee.h"
@implementation Employee
-(void)calculateSalary:(Employee *)employee {
NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end
这个案例我们可以看出我们这里出现了这个类包含了多个职能,这很显然不符合我们的单一职能原则,我们这个工人的类别就应该仅仅包含一个工人的一个基本信息,而不应该涉及计算薪资的一个内容。
这里我们给出一个正确的例子:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Employee : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
@class Employee;
NS_ASSUME_NONNULL_BEGIN
@interface SalaryCaculator : NSObject
-(void)calculateSalary:(Employee*)employee;
@end
NS_ASSUME_NONNULL_END
#import "SalaryCaculator.h"
#import "Employee.h"
@implementation SalaryCaculator
-(void)calculateSalary:(Employee *)employee {
NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end
在这个案例中我们把两个内容分开了,创建了一个新的类来负责他对应的一个工作,从而保证了一个类只负责一个职能,符合我们的一个单一职能原则。
遵循单一职责原则是一个重要的设计原则,可以帮助我们写出更加模块化、可维护和可扩展的代码。
里氏替换原则——LSP
该原则要求:
子类对象可以替换父类对象出现在程序中,而不影响程序的正确性。
这里可能比较难以理解这项原则的一个作用,这里笔者借用学长的一段话来看一下:
- 里氏替换原则是实现开放封闭原则的重要方式之一。
- 它克服了继承中重写父类造成的可复用性变差的缺点。
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。【设计模式】六大原则详解,每个原则提供代码示例
这里我们可以看出这项原则一个核心是为来保证类的一个扩展是不会给已经存在的系统引入新的错误,防止我们采用多态的时候出现一个代码上的问题。
下面给出一个经典的长方形不是正方形的一个例子:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol Shape <NSObject>
- (NSInteger)calculateArea; //定义一个协议方法,也就是一个公共的接口
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "Shape.h"
NS_ASSUME_NONNULL_BEGIN
@interface Squre : NSObject<Shape> //实现一个正方形类来实现对应的接口的内容
@property (nonatomic, assign) NSInteger length;
@end
NS_ASSUME_NONNULL_END
#import "Squre.h"
@implementation Squre
- (NSInteger)calculateArea {
return self.length * self.length;
}
@end
#import "Shape.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Rectangle : NSObject<Shape> //实现长方形类来实现对应接口的内容
@property (nonatomic, assign) NSInteger width;
@property (nonatomic, assign) NSInteger height;
@end
NS_ASSUME_NONNULL_END
#import "Rectangle.h"
@implementation Rectangle
- (NSInteger)calculateArea {
return self.width * self.height;
}
@end
这里我们把长方形和正方形分成两个不同的类别遵循不同的协议,这样才不会出现里氏替换原则中将正方形替换成长方形,然后设置长宽出现与预期的结果实际不符的一个情况。
依赖倒置原则——DLP
该原则要求:
- 高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象
在OC中我认为抽象类是协议,细节是一个类的实现方法,高层模块就是调用端,底层模块就是实现端。
也就是说,模块间依赖是通过抽象发生;实现类之间没有依赖关系,所有的依赖关系通过接口/抽象类产生。【设计模式】六大原则详解,每个原则提供代码示例
下面给出我认为的依赖倒置原则的实现:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol LoggerProtocol <NSObject> // 一个抽象的接口
-(void) log:(NSString*)message;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "LoggerProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface Logger : NSObject<LoggerProtocol> //这里是我们的一个具体的底层实现端
@end
NS_ASSUME_NONNULL_END
#import "Logger.h"
@implementation Logger
- (void)log:(NSString*)message { // 底层实现一个抽象
NSLog(@"1234 %@", message);
}
@end
#import <Foundation/Foundation.h>
#import "Logger.h"
NS_ASSUME_NONNULL_BEGIN
@interface MyClass : NSObject // 这里是我们的高层调用端
@property (nonatomic, strong) id<LoggerProtocol> logger; //高层调用这个接口
@end
NS_ASSUME_NONNULL_END
//使用的时候我们通过下面的方法来实现:
// MyClass.logger = [[Logger alloc] init];在这里进行一个依赖注入
// [MyClass.logger log:@"123"];
接口隔离原则——ISP
该原则要求:
- 一个类不应该强迫其它类依赖它们不需要使用的方法,也就是说,一个类对另一个类的依赖应该建立在最小的接口上
也就说:一个类应该只提供其它类需要使用的方法,而不应该强迫其它类依赖于它们不需要使用的方法
这里我们举一个例子:假设我们设计一个多功能设备,里面可能包含了打印机和扫描仪,但是一般的设备可能不具备对应的一个扫描的一个功能,但是如果这个类遵循这个协议就会导致对应的一个问题,实现了一个他不具备的一个功能。
下面只介绍正确的案例的样式,设置小而专的接口
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol Printable <NSObject> //设置打印的接口
- (void) printDoucment;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol Scanabel <NSObject> //设置扫描的接口
-(void) scanDoucment;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "Printable.h"
NS_ASSUME_NONNULL_BEGIN
@interface Printer : NSObject <Printable> //普通打印机的一个实现,如果是多功能则多遵循一个协议就可以了,
@end
NS_ASSUME_NONNULL_END
#import "Printer.h"
@implementation Printer
-(void)printDoucment {
NSLog(!"common Printer");
}
@end
其核心思想在以下的内容:
如果一个接口过于臃肿,就需要将其拆分成多个小的接口,使得每个接口中只包含必要的方法。这样的好处是:
- 接口更加具有内聚性:每个接口只需要关注自己的功能,而不需要关注其他接口中的方法,因此能够使接口更加专注和具有内聚性。
- 接口之间的耦合度更低:每个接口只依赖于必要的方法,而不依赖于其他不必要的方法,因此能够使接口之间的耦合度更低。
- 代码的复用性更高:每个接口只包含必要的方法,因此能够使得代码的复用性更高,也能够提高代码的可读性和可维护性。24种设计模式代码实例学习(一)七大设计原则
迪米特法则——LoD
该原则要求:
- 一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过
public
方法提供给外部。
最少知道原则的另一个表达方式是:只与直接的朋友通信。
类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。
这里我们以购物车为例子:一个人去超市买东西主要分成三个部分,一个是我们的用户,一个是购物车,一个是商品,这里面我们如果要符合这个设计原则的话,这里的人应该和购物车进行交互,然后购物车和商品进行一个交互,这样符合我们的迪米特法则。
下面给出一段代码示例:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Product : NSObject // 这个是商品
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, strong) NSString* name;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
@class Product;
NS_ASSUME_NONNULL_BEGIN
@interface ShopCart : NSObject // 这个是购物车
@property (nonatomic, strong) NSMutableArray<Product*>* products;
-(void) addProduct:(Product*)product;
@end
NS_ASSUME_NONNULL_END
#import "ShopCart.h"
@implementation ShopCart //与商品发生直接关系
- (void)addProduct:(Product *)product {
[self.products addObject:product];
}
@end
#import <Foundation/Foundation.h>
@class ShopCart;
@class Product;
NS_ASSUME_NONNULL_BEGIN
@interface User : NSObject //用户
@property (nonatomic, strong) ShopCart* shopCart;
-(void)addToCart:(Product*)product;
@end
NS_ASSUME_NONNULL_END
#import "User.h"
#import "ShopCart.h"
@implementation User
- (void)addToCart:(Product *)product {
[self.shopCart addProduct:product]; // 与购物车发生直接关系
}
@end
在上面的代码示例中,每个类都只和自己直接的朋友进行通信,遵循了最少知道原则。
小结
这里笔者简单介绍了一下六大设计模式的一个内容,笔者对这部分内容也是初次接触,如有纰漏或者还请不吝赐教,笔者会及时改正。
参考博客:
【设计模式】六大原则详解,每个原则提供代码示例
24种设计模式代码实例学习(一)七大设计原则