软件构造课程已经进行到了第5章,慢慢地进入到了深水区,涉及到了各种各样的面向不同质量目标的设计模式和OO的基本设计原则。对于没写过太多代码的我来说,想要将它们熟练的运用起来,不仅需要理解课程的讲义,还要阅读一些前人已经总结好的书籍与文章,更重要的是编写更多的代码。因而本文将我在学习SOLID设计原则过程中阅读的一些文章和代码总结起来,在对学过的内容的不断总结的过程中不断地提高自己。
SOLID设计原则是由罗伯特·C·马丁在21世纪早期引入面向对象编程和面向对象设计中的五个基本原则的首字母缩写,它们分别是:
首先,单一职责原则中的职责一词的解释为引起变化的原因,这一原则描述如下:
There should never be more than one reason for a class to change.
引起类变化的因素永远不要多于一个。
这和模块化设计中的“高内聚,低耦合”中的高内聚是相似的,也就是说让一个类只做它应该做的事情,某种程度上说是在分离关注点,将一个类能做的多个关联度不大的两件事情(两个职责)分离开来。
public interface IPhone{
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//挂断电话
public void hangup();
}
分析上面这个接口,却发现它包含了两个职责:一个是连接管理,一个是数据传送。IPhone接口包含了两个职责,而且这两个职责的变化不互相影响,这就可以考虑分成两个接口
public interface Connection{
//拨通电话
public void dial(String phoneNumber);
//挂断电话
public void hangup();
}
public interface DataChannel{
//通话
public void chat(Object o);
}
单一职责原则是最简单的原则,却也是最难做好的原则。什么时候要拆分,什么时候要合并?这需要你不断“品尝”你的代码,当“味道”不够好时,对代码持续重构,直到“味道”刚刚好。
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
软件实体(类、模块等)应当对扩展开放,对修改闭合。
实现开闭原则的关键是抽象。我们要将系统中可能变化的地方封装起来,即对修改封闭,可以通过接口、抽象类、策略模式等方法来实现开闭原则。
设计模式中的strategy模式是这个原则的一个很好的体现。
通过构造一个抽象的 Server 类:AbstractServer,该抽象类中包含针对所有类型的 Server 都通用的代码,从而实现了对修改的封闭。
当出现新的 Server 类型时,只需从该抽象类中派生出具体的子类 ConcreteServer 即可,从而支持了对扩展的开放。
之前已经对Liskov替换原则进行过详细的总结,在此不再赘述。
no client should be forced to depend on methods it does not use.
客户端不应该被迫依赖于它不使用的方法。
也就是说更简洁和更具体的瘦接口比庞大臃肿、职责过多的胖接口好。这是因为胖的接口的职责过多,不够聚合,因而很容易违反单一职责原则。
而怎样将胖接口变成瘦接口呢?这就要依赖与客户端了!接口如果变得过瘦,就不能够想客户端提供它所需要的服务。因而应该面向不同的客户端,对接口进行适当的减肥,从而使得客户端只访问自己所需要的接口和对于的服务。
接口隔离原则本质上也是单一职责原则的体现,同时它也服务于里氏替换原则。
A. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
B. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
高层模块不应该依赖底层模块,两者都应该依赖其抽象。
抽象不应该依赖于实现细节,而实现细节应该依赖于抽象。
这个原则其实是在指导如何实现接口隔离原则。
上图的关系中,当Button直接调用灯的开和关时,Button就依赖于灯了。也就是说上层的clent直接调用了下层具体实现的类
public class Button {
private Lamp lamp;
public void Poll() {
if (/*some condition*/)
lamp.TurnOn();
}
}
而如果Button还想控制电视机,微波炉怎么办?
因而上层的client的代码应该面向抽象的接口进行编程,从而隔离上层稳定的部分和下层变化的部分,避免对下层具体实现的类的直接调用。
不管是电灯,还是电视机,只要实现了ButtonServer,Button都可以控制。这是面向对象的编程方式。
只有当这些原则被一起应用时,它们才能使一个程序员开发一个容易进行维护和扩展的系统变得更加可能。因而必须理解它们之间的关系,并综合应用这五大原则。只有把SOLID作为一个整体,才可能构建出坚实(Solid)的软件。
课程学了这么多的设计模式和设计原则,总结起来大概就是:
将变化的部分和不变的部分隔离开来,不断地进行解耦合,从而便于以后可能的扩展与复用。