七大设计原则

1.单一职责原则

单一职责原则定义是:不要存在多于一个导致类变更的原因。通俗地说,即一个类只负责一项职责
单一职责原则(Single Responsibility Principle,简称SRP),

单一职责原则让每个类都只做一件事,减低了类的复杂性。但是如果严格遵守单一职责原则,又会导致类的数目大增,反而增加了整体的复杂性。这就是单一职责原则的争议性。所以在实践中,很少会看到严格遵守单一职责原则的代码。

2.接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

接口隔离原则的要点,就是要细化我们的接口。那么这样做主要有四个好处,分别是:1.避免接口污染;2.提高灵活性;3.提供定制服务;4.实现高内聚。下面就来详细说一下接口隔离原则的好处。

2.1 避免接口污染

一个类如果要实现一个接口,那么就要实现这个接口要求的所有方法,如果这个接口里面包含这个类不需要的方法,那么就会造成接口污染,这是不好的设计,会对系统留下隐患。

比如说我们有一个枪的接口,枪有两个属性:扳机和子弹。枪有一个功能:射杀。其接口如下:

@protocol IGun <NSObject>
@property (strong,nonatomic) id trigger;//扳机
@property (strong,nonatomic) id bullet;//子弹
- (void)shot;//射杀
@end

然后我们现在需要一个玩具枪的类。玩具枪有扳机也有子弹,只是不能射杀。为了图方便,我们直接用IGun这个接口来实现我们的玩具枪:

#import "IGun.h"
@interface ToyGun : NSObject<IGun>
@end

@implementation ToyGun
@synthesize trigger = _trigger,bullet = _bullet;
- (void)shot{
    //空实现,什么也不做
}
@end

玩具枪是不能射杀的,但是由于它实现了IGun这个接口,所以只能空实现它并不需要的shot方法,于是玩具枪这个类就被污染了。这好像也没有什么不妥,但是这是有隐患的,因为玩具枪一旦实现了IGun接口,那么在程序里它就代表一把能射杀的枪。假设在后面突然遇到了一个老虎,唯一保命的方法就是拿枪射杀这个老虎,结果你拿到的是你之前为了图方便做的ToyGun,那么你面临的就是灭顶之灾。

2.2 提高灵活性

一个类是可以同时实现多个接口的,所以将一个臃肿的接口分割为若干个小接口,通过小接口的不同组合可以满足更多的需求。

举个例子。我们现在需要一个代表美女的接口,美女的标准也很明确:面貌、身材和气质,那么我们的美女接口就出来了:

@protocol IPrettyGirl <NSObject>
- (void)goodLooking;//好面貌
- (void)niceFigure;//好身材
- (void)greatTemperament;//好气质
@end

这并没有什么问题。但是在现实中,一定是要美貌和气质兼备的才算美女吗?非也,其实也有长得不好看,但是气质很好的气质美女的,当然也有没有气质但是长得好看的美女。这样上面的接口就不适用了,因为按照上面的的接口,只有长得好看而且气质好的才算美女。

可以通过细化这个接口解决这个问题。上述的接口可以一分为二:

只有外貌好的美女:

@protocol IGoodBodyGirl <NSObject>
- (void)goodLooking;//好面貌
- (void)niceFigure;//好身材
@end

只有气质好的美女:

@protocol IGreatTemperamentGirl <NSObject>
- (void)greatTemperament;//好气质
@end

然后通过这两个接口的不同组合,就能满足外貌美女、气质美女和外貌气质俱佳的美女的不同需求了。所以,细化接口可以让我们的接口更加灵活,满足更多需求

2.3 提供定制服务

比如我们开发了一个图书管理系统,其中有一个查询图书的接口:

@protocol IBookSearcher <NSObject>
- (void)searchByAuthor;//根据作者搜索
- (void)searchByTitle;//根据书名搜索
- (void)searchByCatagory;//根据分类搜索
- (void)complexSearch;//复杂的搜索
@end

我们的图书馆管理系统的访问者有管理人员和公网,其中complexSearch方法非常损耗服务器的性能,它只提供给管理人员使用。其他方法管理人员和公网都可以使用。公网这部分是另一个项目组在开发的,所以当时我们口头上跟公网项目组说明不能在公网上调用complexSearch这个方法。图书馆管理系统上线后,有一天发现系统速度非常慢,在熬了一个通宵排查后,发现是由于公网项目组某个程序员的疏忽,把complexSearch方法公布到了公网中…

显然通过口头的方式说哪一个方法不能调用是不管用的,要想彻底解决这个问题,还是得通过细化接口,为访问者定制专有的接口才行。那么上述的接口可以一分为二:

简单的搜索:

@protocol ISimpleBookSearcher <NSObject>
- (void)searchByAuthor;//根据作者搜索
- (void)searchByTitle;//根据书名搜索
- (void)searchByCatagory;//根据分类搜索
@end

复杂的搜索:

@protocol IComplexBookSearcher <NSObject>
- (void)complexSearch;//复杂的搜索
@end

这样我们就可以分别给管理人员和公网定制接口了:

  • 给管理人员提供ISimpleBookSearcher和IComplexBookSearcher两个接口;
  • 给外网提供ISimpleBookSearcher这个接口。

2.4 实现高内聚

什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。比如说,你告诉你的下属“一个小时之内去月球搬一块石头回来”,然后你就躺在海滩上晒着太阳喝着果汁,一个小时之后你的下属就搬着一块月亮上的石头回来给你了。这种不讲任何条件,不需要你关心任何细节,立即完成任务的行为就是高内聚的表现。

具体到接口中,还是尽量细化你的接口。接口是对外界的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。

3.依赖倒置原则

High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.(高层模块不应该依赖低层模块,它们都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。)

依赖倒置原则也可以理解为“依赖抽象原则”

为什么说依赖抽象就是依赖倒置呢?因为在日常生活中,人们习惯于依赖于具体事务(细节),而不是抽象。比如说我们说开车就是具体的车,用电脑就是用具体的电脑。那么如果要倒过来去依赖抽象,就是依赖倒置

很简单,我们的任务是声明一个司机类和一个奔驰车类,然后让司机开车。我们按照我们现实生活的直觉来,两个类都是具体类,司机就是司机,奔驰车就是奔驰车,没有抽象类的存在。

宝马车类:

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

@implementation BMWCar
- (void)run{
    NSLog(@"宝马车开动了");
}
@end

司机类:

#import "BMWCar.h"

@interface Driver : NSObject
- (void)driveCar:(BMWCar *)car;
@end

@implementation Driver
- (void)driveCar:(BMWCar *)car{
    [car run];
}
@end

这样就可以让我们的老司机老王开车了:

Driver *wang = [Driver new];
BMWCar *bmw = [BMWCar new];
[wang driveCar:bmw];

上面的代码好像没有什么问题。但是如果现在新增一辆奔驰车,我们的老司机老王却没办法开,因为他目前只有开宝马车的方法!如果要开奔驰车,可能需要为他新增开奔驰车的方法。那么如果现在又要新增特斯拉、奥迪、罗斯莱斯等车呢?难道要为每一辆新增的车去修改司机类?这显然是荒唐的。依赖于具体类,会导致类之间的耦合性太强,这就是在代码中依赖具体类的问题。

虽然说代码是现实世界的反映,但是代码和现实世界还是有所区别的,你需要“倒置”一下。解决上述问题的方法自然是依赖倒置,让司机依赖于抽象的车,而不是具体的车。

抽象车类:

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

@implementation Car
- (void)run{
}
@end

司机类(记得司机类现在依赖的是抽象的车类,而不是具体的车类):

#import "Car.h"

@interface Driver : NSObject
- (void)driveCar:(Car *)car;
@end

@implementation Driver
- (void)driveCar:(Car *)car{
    [car run];
}
@end

然后,就是我们的具体车类了。只要具体的车类继承自抽象车类,那么无论它是奔驰车,还是宝马车…我们的司机都可以开。

奔驰车:

@interface BenzCar : Car
@end

@implementation BenzCar
- (void)run{
    NSLog(@"奔驰车开动了");
}
@end

让我们的老王开开奔驰:

Driver *wang = [Driver new];
Car *benz = [BenzCar new];
[wang driveCar:benz];

宝马车:

@interface BMWCar : Car
@end

@implementation BenzCar
- (void)run{
    NSLog(@"宝马车开动了");
}
@end

让我们的老王也开开宝马:

Driver *wang = [Driver new];
Car *bmw = [BMWCar new];
[wang driveCar:bmw];

可见通过依赖倒置,现在无论新增多少种车,都不需要去修改司机类了。

通过上面的例子,相信大家已经领略到在代码中使用依赖倒置原则的重要性了。总结一下依赖倒置原则的优点:

  • 减少类之间的耦合;
  • 降低并行开发引起的风险;
  • 提高代码的可读性和可维护性。

面向接口编程是依赖倒置原则的最佳实践

4. 里氏替代原则

如果我们有A和B两个条件:

A条件:在用类T的对象o1定义的程序P中,将o1全部替换为类S的对象o2,而程序P的行为没有发生变化;

B条件:类S是类T的子类。

根据里氏替换原则的第一个定义,A条件可以推导出B条件;根据里氏替换原则的第二个定义,B条件可以推导出A条件。所以,里氏替换原则其实就是:A条件和B条件互为充要条件。

在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法,继承实际上让两个类耦合性增强了,如果必须重写,可以通过聚合,组合,依赖,重新写一个父类让两个成兄弟 来解决问题。.

意义:继承,是面向对象语言非常优秀的语言机制。里氏替换原则的意义,就是规范继承的用法,让我们最大限度地发挥继承的优点。

5. 开闭原则

对扩展开放(提供方),对修改关闭(使用方)

6. 迪米特法则

迪米特法则(Law of Demeter, LoD)是1987年秋天由lan holland在美国东北大学一个叫做迪米特的项目设计提出的,它要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则(Least Knowledge Principle, LKP)。

迪米特法则还有个更简单的定义:只与直接的朋友通信

直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

例如:手机,app和书籍,手机里面可以有app,打开app才有书籍,所以书籍不能出现在手机中,只能在app中,要看书籍必须要有app对象然后调用相应方法读书

7. 合成复用原则

原则是尽量使用合成/聚合复用,而不是使用继承复用

继承的缺点

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  • 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
  • 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

组合或聚合复用优点

  • 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  • 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

8. 参考资料

简书上面的 匆执羊 链接

b站尚硅谷 链接

9. 公众号

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值