六大设计原则
- 总原则 开放-封闭原则
- 单一职责原则
- 依赖倒置原则
- 里氏转换原则
- 最少知道原则
- 隔离接口原则
- 合成复用原则
开放封闭原则
对扩展开放,对修改关闭
一个类写好了就不应该去修改它,要想添加新功能可以进行扩展。
我们说对扩展开放,对修改关闭,是只是我们开发过程中的一个行动思想,绝对的开放和封闭是不存在的。
在实际开发中设计的模块无论多么的封闭,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。先猜测有可能发生的变化种类,然后构造抽象来隔离那些变化。
事实上我们在开发之前很难预测准确,难免在开发中还是会遇变化,那在变化发生时就立即采取行动。也就是说,我们在最初编写代码时,考虑肯定会发生的变化,其余的不再纠结, 当变化发生时,我们就创建抽象来隔离以后发生的同类变化。
最理想的状态就是在开发工作开展不久就知道可能发生的变化。反之查明可能发生的变化等待时间越长,要创建抽象的难度就越大。
开放封闭是面向对象设计的核心所在,带的就是可维护,可扩展,可复用,灵活性好。
我们应该对频繁发生变化的部分作出抽象,其余的不要刻意。
例子 :我国的交际政治,对国与国之间合作,探讨,交流是开放的,但对内政是封闭的。
思考:开放封闭不是绝对的,没有人能够打一开始就想好了所有的结构和抽象,我们不必为开发时会修改而苦恼,事前准备,事中控制,事后总结,也是这个道理,要学会动态的看问题。
单一职责原则
就一个类而言,应该仅有一个引起它变化的原因
如果一个类承担的职责过多,就相当于把这些指责耦合在一起,牵一发而动全身,就是这个理。
我们开发真正要做的许多内容,就是发现职责并发那些职责相互分离。
例子: 各种家电,扫地机器人只扫地,洗衣机只洗衣服,你不会让扫地机器人去做饭,让洗衣机去洗碗。
思考:我想说的,过分单一职责化,虽然极大程度的降低耦合度,但这也会出现大量的功能类,难以管理,怎样去把握这个度,这便是我们真正的功课。
依赖倒置原则
抽象不应该依赖于细节,细节依赖于抽象
编程思想就是,针对接口编程,不要对现实编程。我们常常会做一些高层模块依赖底层模块的事情,这样会导致耦合过高。高层模块不应该依赖于底层模块,两个模块都因该依赖于抽象。
依赖倒置可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,程序中所有的依赖关系都是终止于抽象类或者接口。
例子:人会开车,开小汽车,开大卡车,设计时如果人依赖于开小汽车,开大卡车,这就不符合依赖倒置原则, 我们依赖的是开车这个抽象,至于开什么车可以交给IOC容器.而各种车继承开车这个抽象.
思考:可以关联或者依赖一个抽象的时候,就不要去找一个具体对象。《生存之战》项目的枪械思想 V层和C层就是这个理, C层抽象 只关联V层抽象, C层子类 也只关联C层抽象,通过C层抽象中的V层抽象转换成V层子类,而不是直接关联。
里氏转换原则
子类型必须能替换其父类型
子类型的可替换性使得使用父类型的模块在无需修改的情况下就可以扩展。子类也不要去重写父类中的方法,否则会有潜在问题。
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序行为没有变化。
例子: 比如我们常见的鸟类划分,所有鸟都会飞,那企鹅应不应该继承鸟类,大雁应不应该继承鸟类。 虽然抽象类鸟类里面可能就一个飞的方法, 子类继承也可以重写,比如企鹅继承后重写飞的方法,飞的速度为0,那不就表示不能飞吗? 这不一样,不能这么干。万一后面我有需求,让所有的鸟比赛飞翔的成绩,赛道100米, 大雁飞行速度10米/秒,时间就是10秒, 企鹅呢? 速度0, 100/0,这是什么? 会出错的。 我们就不应该让企鹅继承会飞的鸟,因为企鹅不能替换鸟,不会飞。
思考:其实继承也是一种耦合关系,子类与父类的耦合, 父类一旦有改动,子类就都会发生变化。如果可以的话应该少用继承,而多用组合的方式, unity这款引擎其实就很好的体现了这一点,一个对象 一个空物体就能是一个对象,它只有 transform一个组件,即便这样它也是一个对象。其它的组件 比如 boxcollider,rigidiboy等组件,都是以组合的形式加入这个对象,对这个对象进行了功能扩展,但不影响它最原始的功能。即便把这些组件剔除,也不影响它作为一个对象存在。这其实就是组合的思想。
最少知道原则
不必要的联络就不要联络
也叫迪米特原则,一个对象应该对其它对象有最少的了解,一个类应该对需要耦合或调用的类知道的最少.迪米特的核心在于解耦合,只有弱耦合关系的类才有较高的复用性.
例子: 我们常用的三层划分,不能数据 逻辑 显示之间想要什么就自己直接获取,这样就耦合太高, 应当将需要调用的内容 提供一个接口,统一在一处管理, 比如MVC的C就是充当了这个作用.我们去公司面试,需要和HR 专业负责人 公司老板 交谈,但我们是不必自己去找每一个人的,我们只需要找到公司HR,公司HR会处理后续的一些列请求.HR来联络这些人员.
思考:迪米特法则的核心是解耦合,这样类与类之间的确减少了不必要的依赖,但他们之间的直接通信就变成了间接通信,之间的交互需要一个中介来完成,这样从单纯的A↔B之间交互 就多出了 一个M层, A↔M↔B,一旦这种情况变多,势必会产生大量的中介类,就会导致系统复杂而庞大,而且一旦需要改变A或B之间传递的内容, 就得去修改M层,这违背了封闭原则.所以迪米特法则的运用要权衡,不是越多越好,既要结构清晰,又要高内聚低耦合.
隔离接口原则
只暴露必要的接口
不要在一个类里面放多个方法,这样一个接口就会很臃肿,将与外界交互的接口尽可能细致和少,方法做到轻便灵活.接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
例子: 比如我有一个工具类,是一个画笔工具,可以调颜色,可以调粗细,还可以调质地,还能 调工作时间。客户需要调用这个工具类去画画,可客户只需要其中的颜色 粗细 质地, 不需要时间, 这个时间方法就不符合接口隔离。
思考:让接口隔离,势必会增加类的细粒度,这样又不可避免的走向另一个极端,类的数量过多,系统太过复杂。其实我们开发设计不是一个完美设计模式的过程,设计原则只是一个指导思想,告诉我们可以按这几条思路去设计,但并不是说一定要符合,没有一个软件是完完全全符合所有设计原则的,它一定有所取舍,而我们设计框架就是一个取舍的问题,是一个匹配度问题。设计软件应当,首要需需求相适合,可做演化扩展,然后尽可能简单而不是上来就搬设计原则照猫画虎。
合成复用原则
让我们灵活拆装而不继承
合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。聚合用来表示“拥有”关系或者整体与部分的关系。代表部分的对象有可能会被多个代表整体的对象所共享,而且不一定会随着某个代表整体的对象被销毁或破坏而被销毁或破坏,部分的生命周期可以超越整体。例如,班级和学生,当班级删除后,学生还能存在,学生可以被培训机构引用。
例子:例如汽车,假如使用继承的思想, 有一个汽车父类,下面有电动车和汽油车两个方向, 颜色有 黑色 白色 黄色 蓝色四个方向, 如果是继承,那就不得不设计两个子类,一个 电动车 一个汽油车, 其下面还分别被继承了 黑色 白色 黄色 蓝色的子类, 这样的设计就显得特别臃肿,不够灵活。 假如是合成复用, 只需要两个子类 电动车和汽油车, 让后 颜色作为关联项,在汽车里面持有即可。
思考:有时候继承并不是最佳的方法,它让子类的灵活性受到限制,而且暴露了父类的封装性。子类和父类高度耦合,父类发生什么变化,子类也会跟着改变。采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。
它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
23种设计模式
创建型
- 单例模式
- 工厂模式
- 抽象工厂模式
- 建造者模式
- 原型模式
结构型
- 适配器模式
- 装饰模式
- 桥接模式
- 组合模式
- 享元模式
- 代理模式
- 外观模式
行为型
- 观察者模式
- 模板方法模式
- 命令模式
- 状态模式
- 职责链模式
- 解释器模式
- 中介者模式
- 访问者模式
- 策略模式
- 备忘录模式
- 迭代器模式