文章目录
Single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Law of Demeter:迪米特法则
Interface Segregation Principle:接口隔离原则
Dependence Inversion Principle:依赖倒置原则
把这六个原则的首字母联合起来就是 SOLID (稳定的),
代表的含义是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。
一、Open Closed Principle:开闭原则(OCP)
软件实体,如类、模块和函数应该遵守扩展开放和修改关闭的原则。
1、开: 对扩展进行开放
用抽象构建稳定的基本架构,用抽象的派生类来扩展实现易变的细节。
2、闭: 对修改进行关闭
这是因为对原有代码修改,往往可能引入新的错误;
这可能会使我们不得不重构。
相关案例:
设计原则 之 开闭原则
二、Single Responsibility Principle:单一职责原则(SRP)
一个类应该只有一个发生变化的原因,即: 一个类只负责一个职责。
这样的好处是:
1、类的复杂度低
每个类只负责单一的事情
2、代码容易维护,可读性高
代码职责划分清晰,不臃肿
SRP 也可以扩展到: 接口的单一职责 / 方法的单一职责。
三、Liskov Substitution Principle:里氏替换原则(LSP)
所有引用基类的地方,必须能透明地使用其子类的对象;
即: 所有基类在的地方,都可以换成子类,程序还可以正常运行。
1、为什么会有这个原则? 因为继承存在缺陷
这个原则是与面向对象语言的继承特性密切相关的,
继承优点:
代码可重用,减少开发类的工作量;
代码可扩展,子类不但拥有了父类的所有功能,还可以添加自己的功能。
继承缺陷:
继承是侵入性的:只要继承,就必须拥有父类的所有属性和方法;
降低代码灵活度:父类对子类存在约束;
增强耦合: 当需要修改父类代码时,必须考虑对子类的影响。
2、里氏替换原则,对继承进行规则约束,从而降低继承缺陷的影响
1) 子类必须重写父类的抽象方法,但不得重写父类的非抽象(已实现)方法;
若子类不对父类的抽象方法进行重写和实例化,那么这个接口或抽象类就毫无存在的意义了。
2)当子类重写父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格(返回值类型范围更小);
3)子类虽不能重写父类的已实现方法,但可以重载;子类重载父类方法需要有更宽松的形参;
当子类重载父类方法时,方法的前置条件(方法的形参)需要比父类方法的输入参数更宽松,
即输入允许访问更大,比如:子类:Map, 父类:HashMap。
4)子类中可以增加自己特有的方法,扩展功能。
四、Law of Demeter:迪米特法则(LOD)
只与你的直接朋友交谈,不要跟 “陌生人” 说话 :
如果两个软件实体无须直接通信,
那么就不应当发生直接的相互调用,应该通过第三方转发该调用;
1、LOD 优点
1)降低了类之间的耦合度,提高了模块的相对独立性。
2)由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
2、LOD 缺点
过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低;
所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合
的同时,保证系统的结构清晰。
从依赖者的角度来说,只依赖应该依赖的对象。
从被依赖者的角度说,只暴露应该暴露的方法。
客户找中介只需要在意符合自己条件的楼盘 ,而不必跟每个楼盘都发生联系。
五、Interface Segregation Principle:接口隔离原则
客户端不应该依赖它不需要的接口;
类间的依赖关系应该建立在最小的接口上。
即:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
1、接口隔离原则 VS 单一职责原则
接口隔离原则和单一职责都是为了实现 “高内聚,低耦合“,体现了封装的思想,但两者也有点区别:
单一职责原则,主要注重对类的职责的约束,它针对的是程序中的实现和细节;
接口隔离原则,主要注重对接口依赖的隔离,它针对的是抽象和程序整体框架的构建。
2、衡量方法
1)根据接口隔离原则拆分接口时,首先必须满足单一职责原则;
2)接口拆分要有限度,比如一个接口只服务于一个子模块或业务逻辑。
3)为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
六、Dependence Inversion Principle:依赖倒置原则(DIP)
上层模块不应该依赖底层模块,它们都应该依赖于抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。
倒置,是指和一般 OO 设计的思考方式完全相反。
参考 实现一个比萨店的案例 来理解这个 “倒置” 。
举个例子
现在你需要实现一个比萨店,你第一件想到的事情是什么?
我想到的是一个比萨店,里面有很多具体的比萨,如:芝士比萨、素食比萨、海鲜比萨……
比萨店是上层模块,比萨是下层模块,
如果把比萨店和它依赖的对象画成一张图,看起来是这样:
先从顶端开始,然后往下到具体类;
但是,实际上并不想让比萨店理会这些具体类,要不然比萨店将全都依赖这些具体类。
现在 “倒置” 你的想法……
别从上层模块比萨店开始思考,而是从下层模块比萨开始,然后想想看能抽象化些什么。
你可能会想到,芝士比萨、素食比萨、海鲜比萨都是比萨,
所以它们应该共享一个 Pizza 接口,即抽象化一个Pizza。
然后,回头重新思考如何设计比萨店:
图一的依赖箭头都是从上往下的,
图二的箭头出现了从下往上,
依赖关系确实 “倒置” 了!
另外,此例子也很好的解释了 “上层模块不应该依赖底层模块,它们都应该依赖于抽象。”,
在最开始的设计中,高层模块 PizzaStroe 直接依赖低层模块(各种具体的Pizaa),
调整设计后, 高层模块和低层模块都依赖于抽象(Pizza) 。