设计原则
- 接口(or 抽象类)负责定义public属性和方法,并且声明与其他对象的依赖关系(模型构建)
- 抽象类负责公共构造部分的实现(抽象是所有子类的共性封装)
- 实现类实现业务逻辑,同时在适当的时候对父类进行细化。
开闭原则(Open-Closed Principle)
一个软件实体如类、模块和函数,应该对扩展开发,对修改关闭。
- 抽象约束:①通过抽象类约束扩展,②外部调用尽量使用接口或抽象类,③抽象层尽量保持稳定,不允许轻易修改
- 元数据控制模块行为:使用配置参数去控制模块的行为,案例-控制反转Inversion of Control
- 约定优于配置:使用共有的章程约定替换繁琐的配置,可提高开发效率和可维护性,保存代码的简洁。
- 封装变化:只对外暴露稳定的接口,使可能存在的修改不影响调用方使用
依赖倒置原则(Dependence Inversion Principle) - 面向接口编程(Object-Oriented Design)
面向接口编程,依赖于抽象而不依赖于具体。 - 抽象构建框架,实现扩展细节
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象。
- 抽象不应该依赖细节,细节应该依赖抽象。
接口/抽象类:本质是一种规范和约束,是对外暴露的一组通信规约,使其实现能被内部修改而不影响外界其他实体与其交互的方式。
程序设计:就是对现实进行抽象,并将抽象与抽象相关联,从而形成一套合乎情理,描述了程序世界的规则。(系统模型构建)
程序实现:根据需要对抽象进行具体的实现,在不同的场景,可以有不同的实现,但所有的实现都应该符合程序设计(符合模型规则)。
即:当你构建一个世界的时候,你应该表达:这个世界的规则和联系应该是怎么样的,而不是具体细节:它,他,她,祂。
// 依赖的三种写法
// 1. 由实现类的构造函数中传递依赖对象:
public Driver(ICar _car)
{
this.car = _car;
}
// 2. setter方法声明依赖关系:
public void setCar(ICar car);
// 3. 接口声明依赖关系:
public void drive(ICar car);
// 额外,依赖注入
单一职责原则(Single Responsibility Principle)
一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分 - 降低变更引起的风险,修改只影响对应的功能,不会影响其他功能
接口隔离原则(Interface Segregation Principle)
因为:类间的依赖关系应该建立在最小的接口上
所以:接口需要尽量细化,而非建立一个统一的臃肿的接口
目的:对外暴露功能时,不会提供额外多余的功能
- public类型的接口方法,是对外界提供的契约,契约越少,越有利于日后的开发和扩展
设计模式之禅中案例,为避免赋予普通用户额外的权限,对接口进行拆分:
迪米特法则(Law of Demeter)/最少知道原则(Least Knowledge Principle)
①. 只与朋友类(成员对象,方法入参,方法返回值)交流
-- 类与类之间的关系是建立在类间的,而非方法间的(应避免在方法实现中新增类的依赖关系)。即:在程序模型建立时就将依赖关系确认,而非实现时再意外引入
②. 对于依赖的类知道的最少,尽量少的暴露public接口
-- 当调用方需依次调用多个接口完成某个功能时,应该把该套逻辑抽取出来,调用方只调用一个接口即可完成整个功能(低耦合,高复用)
③. 如果两个软件实体无需直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发(中介) - 由中介维护交互细节,案例:中介者模式、访问者模式
总结:高内聚,低耦合,尽量减少类间依赖。
对于①:调用方无需知晓被调用方将对哪个对象进行操作(只依赖必须依赖的类)
对于②:实现逻辑应该维护在被调用方,而不是调用方每一步去操作,降低耦合度(被依赖类尽可能少暴露接口)
对于③:当调用方和被调用方不需要直接通信时,可将通信部分单独封装成中介类,从而使调用方和被调用方的改动只影响其本身和中介类(案例:中介者模式和访问者模式)
里氏替换原则(Liskov Substitution Principle)
子类可以作为父类使用,且调用方无需分辨其是否是子类(在子类被当作其父类使用时,其应有和父类相同的行为逻辑)
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 子类重载父类方法时,要有不小于父类的(入参)前置条件
- 子类实现父类方法时,要有不大于父类的(出参)后置条件(IDE会检查不通过)
// (前置条件)例:
Father{
public void do(Map map);
}
Son{
public void do{HashMap hmap};
}
// 对于f.do(new HashMap()):
Father f = new Son(); // 将调用father.do(Map)方法
Son f = new Son(); // 将调用son.do(HashMap)方法
// 导致前后不一
设计模式之禅中案例,特殊子类无法实现父类方法时,采用关联委托关系实现解耦。
因此采用关联委托关系(组合),声音、形状等委托给AbstractGun处理,其他玩具枪相关的特性由AbstractToyGun进行拓展
合成复用原则(Composite Reuse Principle)
为避免继承泛滥,应尽量先使用组合或者聚合等关联关系(黑箱复用)来实现复用,其次才考虑使用继承关系(白箱复用)来实现。
继承的缺点 - 强耦合的:
侵入性的:子类具有父类的所有属性和方法
降低了代码的灵活性:子类必须拥有父类的方法
欢迎疑问、期待评论、感谢指点 -- kiqi,愿同您为友
-- 星河有灿灿,愿与之辉