面向对象设计模式-6大原则
什么是设计模式?
iOS内平时有很多设计模式,如:delegate,单例模式,观察者模式,工厂模式……
设计模式是为特定场景下的问题而制定的解决方案,好的编码经验和范式。
最终实现高类聚,低耦合,使代码更易维护。
面向对象设计六大原则
image.png1.单一职责原则
一个类只做一件事或者只做自己的事;
不好的设计:
//================== Employee.h ==================
@interface Employee : NSObject
//============ 初始需求 ============
@property (nonatomic, copy) NSString *name; //员工姓名
@property (nonatomic, copy) NSString *address; //员工住址
@property (nonatomic, copy) NSString *employeeID; //员工ID
//============ 新需求 ============
//计算薪水
- (double)calculateSalary;
//今年是否晋升
- (BOOL)willGetPromotionThisYear;
@end
好的设计
员工类
//================== Employee.h ==================
@interface Employee : NSObject
//初始需求
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, copy) NSString *employeeID;
会计部门类
//================== FinancialApartment.h ==================
#import "Employee.h"
//会计部门类
@interface FinancialApartment : NSObject
//计算薪水
- (double)calculateSalary:(Employee *)employee;
@end
人事部门类
//================== HRApartment.h ==================
#import "Employee.h"
//人事部门类
@interface HRApartment : NSObject
//今年是否晋升
- (BOOL)willGetPromotionThisYear:(Employee*)employee;
@end
会计类,人事类有什么功能新增在自己内部完成,不影响员工类;
单独实现的设计类和人事类可以为其他类如客户类服务
2.开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
实践开闭原则的优点在于可以在不改动原有代码的前提下给程序扩展功能。增加了程序的可扩展性,同时也降低了程序的维护成本。
设计一个课程,包含:课程名称,课程介绍,讲师姓名,文字内容;新增需求:视频课程,音频课程,直播课程
不好的设计:
//================== Course.h ==================
@interface Course : NSObject
@property (nonatomic, copy) NSString *courseTitle; //课程名称
@property (nonatomic, copy) NSString *courseIntroduction; //课程介绍
@property (nonatomic, copy) NSString *teacherName; //讲师姓名
@property (nonatomic, copy) NSString *content; //文字内容
//新需求:视频课程
@property (nonatomic, copy) NSString *videoUrl;
//新需求:音频课程
@property (nonatomic, copy) NSString *audioUrl;
//新需求:直播课程
@property (nonatomic, copy) NSString *liveUrl;
@end
好的设计:创建课程基类,通过继承拓展其他新需求
//================== Course.h ==================
@interface Course : NSObject
@property (nonatomic, copy) NSString *courseTitle; //课程名称
@property (nonatomic, copy) NSString *courseIntroduction; //课程介绍
@property (nonatomic, copy) NSString *teacherName; //讲师姓名
视频课程:
//================== VideoCourse.h ==================
@interface VideoCourse : Course
@property (nonatomic, copy) NSString *videoUrl; //视频地址
@end
3.里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类对象可以替换其父类对象,而程序执行效果不变。
在继承体系中,子类中可以增加自己特有的方法,也可以实现父类的抽象方法,但是不能重写父类的非抽象方法,否则该继承关系就不是一个正确的继承关系。
可以检验继承使用的正确性,约束继承在使用上的泛滥。
4.迪米特法则(最少知道原则)
一个对象应该对尽可能少的对象有接触,也就是只接触那些真正需要接触的对象。(降低耦合)
迪米特法则也叫做最少知道原则(Least Know Principle), 一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流)。
不好的设计:
//================== Car.h ==================
@class GasEngine;
@interface Car : NSObject
//构造方法
- (instancetype)initWithEngine:(GasEngine *)engine;
//返回私有成员变量:引擎的实例
- (GasEngine *)usingEngine;
@end
//================== Car.m ==================
#import "Car.h"
#import "GasEngine.h"
@implementation Car
{
GasEngine *_engine;
}
- (instancetype)initWithEngine:(GasEngine *)engine{
self = [super init];
if (self) {
_engine = engine;
}
return self;
}
- (GasEngine *)usingEngine{
return _engine;
}
@end
//================== GasEngine.h ==================
@interface GasEngine : NSObject
@property (nonatomic, copy) NSString *brandName;
@end
//================== Client.m ==================
#import "GasEngine.h"
#import "Car.h"
- (NSString *)findCarEngineBrandName:(Car *)car{
GasEngine *engine = [car usingEngine];
NSString *engineBrandName = engine.brandName;//获取到了引擎的品牌名称
return engineBrandName;
}
好的设计:
//================== Car.h ==================
@class GasEngine;
@interface Car : NSObject
//构造方法
- (instancetype)initWithEngine:(GasEngine *)engine;
//直接返回引擎品牌名称
- (NSString *)usingEngineBrandName;
@end
//================== Car.m ==================
#import "Car.h"
#import "GasEngine.h"
@implementation Car
{
GasEngine *_engine;
}
- (instancetype)initWithEngine:(GasEngine *)engine{
self = [super init];
if (self) {
_engine = engine;
}
return self;
}
- (NSString *)usingEngineBrandName{
return _engine.brand;
}
@end
//================== Client.m ==================
#import "Car.h"
- (NSString *)findCarEngineBrandName:(Car *)car{
NSString *engineBrandName = [car usingEngineBrandName]; //直接获取到了引擎的品牌名称
return engineBrandName;
}
5.接口分离原则
多个特定的客户端接口要好于一个通用性的总接口。
客户端不应该依赖它不需要实现的接口。
不建立庞大臃肿的接口,应尽量细化接口,接口中的方法应该尽量少。
需要注意的是:接口的粒度也不能太小。如果过小,则会造成接口数量过多,使设计复杂化。
避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想。
餐厅的例子:
在这里声明了一个接口,它包含了下单和支付的几种方式:
下单:
online下单
电话下单
店里下单(店内服务)
支付
online支付(适用于online下单和电话下单的顾客)
店里支付(店内服务)
//================== RestaurantProtocol.h ==================
@protocol RestaurantProtocol <NSObject>
- (void)placeOnlineOrder; //下订单:online
- (void)placeTelephoneOrder; //下订单:通过电话
- (void)placeWalkInCustomerOrder; //下订单:在店里
- (void)payOnline; //支付订单:online
- (void)payInPerson; //支付订单:在店里支付
@end
1.online下单,online支付的顾客
//================== OnlineClient.h ==================
#import "RestaurantProtocol.h"
@interface OnlineClient : NSObject<RestaurantProtocol>
@end
//================== OnlineClient.m ==================
@implementation OnlineClient
- (void)placeOnlineOrder{
NSLog(@"place on line order");
}
- (void)placeTelephoneOrder{
//not necessarily
}
- (void)placeWalkInCustomerOrder{
//not necessarily
}
- (void)payOnline{
NSLog(@"pay on line");
}
- (void)payInPerson{
//not necessarily
}
@end
2.电话下单,online支付的顾客
//================== TelephoneClient.h ==================
#import "RestaurantProtocol.h"
@interface TelephoneClient : NSObject<RestaurantProtocol>
@end
//================== TelephoneClient.m ==================
@implementation TelephoneClient
- (void)placeOnlineOrder{
//not necessarily
}
- (void)placeTelephoneOrder{
NSLog(@"place telephone order");
}
- (void)placeWalkInCustomerOrder{
//not necessarily
}
- (void)payOnline{
NSLog(@"pay on line");
}
- (void)payInPerson{
//not necessarily
}
@end
3.在店里下单并支付的顾客:
//================== WalkinClient.h ==================
#import "RestaurantProtocol.h"
@interface WalkinClient : NSObject<RestaurantProtocol>
@end
//================== WalkinClient.m ==================
@implementation WalkinClient
- (void)placeOnlineOrder{
//not necessarily
}
- (void)placeTelephoneOrder{
//not necessarily
}
- (void)placeWalkInCustomerOrder{
NSLog(@"place walk in customer order");
}
- (void)payOnline{
//not necessarily
}
- (void)payInPerson{
NSLog(@"pay in person");
}
@end
好的设计
//================== RestaurantPlaceOrderProtocol.h ==================
@protocol RestaurantPlaceOrderProtocol <NSObject>
- (void)placeOrder;
@end
//================== RestaurantPaymentProtocol.h ==================
@protocol RestaurantPaymentProtocol <NSObject>
- (void)payOrder;
@end
//================== Client.h ==================
#import "RestaurantPlaceOrderProtocol.h"
#import "RestaurantPaymentProtocol.h"
@interface Client : NSObject<RestaurantPlaceOrderProtocol,RestaurantPaymentProtocol>
@end
1.online下单,online支付的顾客
//================== OnlineClient.h ==================
#import "Client.h"
@interface OnlineClient : Client
@end
//================== OnlineClient.m ==================
@implementation OnlineClient
- (void)placeOrder{
NSLog(@"place on line order");
}
- (void)payOrder{
NSLog(@"pay on line");
}
@end
6.依赖倒置原则
依赖抽象,而不是依赖实现。(针对接口编程,而不是针对实现编程)
抽象不应该依赖细节;细节应该依赖抽象。(尽量不要从具体的类派生,而是以继承抽象类或实现接口来实现)
高层模块不能依赖低层模块,二者都应该依赖抽象。(关于高层模块与低层模块的划分可以按照决策能力的高低进行划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层)
优点:
通过抽象来搭建框架,建立类和类的关联,以减少类间的耦合性。而且以抽象搭建的系统要比以具体实现搭建的系统更加稳定,扩展性更高,同时也便于维护。
用代码模拟一个实际项目开发的场景:前端和后端开发人员开发同一个项目。
不好的设计
前端开发者:
//================== FrondEndDeveloper.h ==================
@interface FrondEndDeveloper : NSObject
- (void)writeJavaScriptCode;
@end
//================== FrondEndDeveloper.m ==================
@implementation FrondEndDeveloper
- (void)writeJavaScriptCode{
NSLog(@"Write JavaScript code");
}
@end
后端开发者:
//================== BackEndDeveloper.h ==================
@interface BackEndDeveloper : NSObject
- (void)writeJavaCode;
@end
//================== BackEndDeveloper.m ==================
@implementation BackEndDeveloper
- (void)writeJavaCode{
NSLog(@"Write Java code");
}
@end
开始开发:
//================== Project.h ==================
@interface Project : NSObject
//构造方法,传入开发者的数组
- (instancetype)initWithDevelopers:(NSArray *)developers;
//开始开发
- (void)startDeveloping;
@end
//================== Project.m ==================
#import "Project.h"
#import "FrondEndDeveloper.h"
#import "BackEndDeveloper.h"
@implementation Project
{
NSArray *_developers;
}
- (instancetype)initWithDevelopers:(NSArray *)developers{
if (self = [super init]) {
_developers = developers;
}
return self;
}
- (void)startDeveloping{
[_developers enumerateObjectsUsingBlock:^(id _Nonnull developer, NSUInteger idx, BOOL * _Nonnull stop) {
if ([developer isKindOfClass:[FrondEndDeveloper class]]) {
[developer writeJavaScriptCode];
}else if ([developer isKindOfClass:[BackEndDeveloper class]]){
[developer writeJavaCode];
}else{
//no such developer
}
}];
}
@end
好的设计
首先,创建一个接口,接口里面有一个写代码的方法writeCode:
//================== DeveloperProtocol.h ==================
@protocol DeveloperProtocol <NSObject>
- (void)writeCode;
@end
前端程序员类:
//================== FrondEndDeveloper.h ==================
@interface FrondEndDeveloper : NSObject<DeveloperProtocol>
@end
//================== FrondEndDeveloper.m ==================
@implementation FrondEndDeveloper
- (void)writeCode{
NSLog(@"Write JavaScript code");
}
@end
后端程序员类:
//================== BackEndDeveloper.h ==================
@interface BackEndDeveloper : NSObject<DeveloperProtocol>
@end
//================== BackEndDeveloper.m ==================
@implementation BackEndDeveloper
- (void)writeCode{
NSLog(@"Write Java code");
}
@end
Project类:
//================== Project.h ==================
#import "DeveloperProtocol.h"
@interface Project : NSObject
//只需传入遵循DeveloperProtocol的对象数组即可
- (instancetype)initWithDevelopers:(NSArray <id <DeveloperProtocol>>*)developers;
//开始开发
- (void)startDeveloping;
@end
//================== Project.m ==================
#import "FrondEndDeveloper.h"
#import "BackEndDeveloper.h"
@implementation Project
{
NSArray <id <DeveloperProtocol>>* _developers;
}
- (instancetype)initWithDevelopers:(NSArray <id <DeveloperProtocol>>*)developers{
if (self = [super init]) {
_developers = developers;
}
return self;
}
- (void)startDeveloping{
//每次循环,直接向对象发送writeCode方法即可,不需要判断
[_developers enumerateObjectsUsingBlock:^(id<DeveloperProtocol> _Nonnull developer, NSUInteger idx, BOOL * _Nonnull stop) {
[developer writeCode];
}];
}
@end