总结:本篇文字分为两个部分。第一部分:设计模式基本常识;第二部分:软件设计中的六大原则,并详细分析了单一职责原则。(本篇文章的时间轴参考:为知笔记支撑文件夹Java设计模式(时间序列图).vsdx)
部分一:初识设计模式
什么是设计模式?James拿到这个论点时,很是迷惑!
模式?是不是一个模子?模式识别——计算机领域的经典问题?
设计模拟?软件的设计模式?不懂!!!
但是在实际编码、调试过程中,James的遇到过很是难解的问题:工程代码中有过多的冗余代码——代码复用性不高;需求一旦改变,需要更改很多地方的代码逻辑——代码灵活性不强……
那先看看设计模式的概念吧!
设计模式是一套被反复使用、为多数人知晓、经过分类编目的、代码设计经验的总结。为了编写可重用性代码,让代码更容易被他人理解,并保证代码可靠性而使用的设计思想。
设计模式使代码编制真正工程化。
设计模式是软件行业智慧积累的结晶;它提出了一系列标准术语,概括了相关行业中经验丰富的从业者所应用的所有概念和方法。
常用的23种设计模式如下:
1.单例模式;
2.工厂方法模式;
3.抽象工厂模式;
4.模版方法模式;
5.建造者模式;
6.代理模式;
7.原型模式;
8.中介者模式;
9.命令模式;
10.责任链模式;
11.装饰模式;
12.策略模式;
13.适配器模式;
14.迭代器模式;
15.组合模式;
16.观察者模式;
17.门面模式;
18.备忘录模式;
19.访问者模式;
20.状态模式;
21.解释器模式;
22.享元模式;
23.桥梁模式;
设计模式的起源是面向对象程序设计思想,是面向对象设计的精髓——抽象;面向对象通过类和对象来实现抽象,实现时产生了面向对象的三种重要机制:封装、继承和多态。而这三种机制衍生了各式各样的设计模式。
在运用面向对象思想进行软件设计时,需要遵循以下原则:
1. 单一责任原则;
2. 里氏替换原则;
3. 依赖倒置原则;
4. 接口隔离原则;
5. 迪米特法原则;
6. 开闭原则;
这23种设计模式按设计意图可组织成五类:接口型模式,责任型模式、构造型模式,操作型模式以及扩展型模式。模式的设计意图指出了应用一个模式的价值所在。但上面所说的23中的某种设计模式,并不是仅仅支持一种设计意图。
在软件设计过程中,只要我们尽量遵循以上设计原则,设计出来的软件一定是优秀的,且足够健壮、稳定,并有足够的灵活性来迎接需求的变更。
那James还是不知道为什么,比如:这些原则的含义是什么?它们为什么而出现,解决了什么工程问题?这些原则和之前讲述的23种设计模式有什么联系?如何在我们的工程代码中使用……
这些疑问都需要James一一解决,不过James相信自己肯定能够战胜这些困难。(纵使是住着握手楼、吃着方便面、挤着公交车,只要能够活下来,就有希望!别人能做出来的东西,自己为什么不行?要有必胜的信心!)
部分二:从设计原则开始——单一职责原则
单一职责原则:应该有且仅有一个原因引起类的变更。
为什么在这里出现的是类?是不是仅仅只应用于类的设计?
(为什么会出现这种设计原则?)当一个类承担了过多的职责,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者一直这个类完成其他职责的能力。问题就出在耦合性上!
软件设计真正要做的工作是:发现职责并把那些职责互相分离。单一职责可使类的复杂性降低,其实现的工作有明确且清晰的定义。
那问题来了,James不明白什么是“类的变更”?
(如何实践单一职责原则?)考虑到如何使用代码实现单一责任原则(代码即是思路嘛),好在有百度和谷歌。下面就使用网站上的例子讲解,James认为:用这个例子比较容易说明问题。
需求分析:烹饪,做一道鱼香茄子。
public interface Cooking { // 获取菜名 String getCookingName(); void setCookingName(String cookingName); // 烹饪之前的准备工作:摘菜、洗菜等等,准备相应的食材 void doPrepare(String vegetableName); // 烹饪模式:蒸、煮、炒、炸等,并提供菜名 void doFire(String cookingName, String mode); }
如果James想要烹饪一道鱼香茄子,可以覆写以上四个方法,从获知菜名、准备食材到具体的烹饪操作。从信息和行为的角度来说,前面两个方法是鱼香茄子相关的信息,后两个则是相关的行为(烹饪具体的行为)。
如果考虑如下方式是否更完善?
public interface ICookingInfo { // 获取菜名 String getCookingName(); void setCookingName(String cookingName); }
public interface ICookingFire { // 烹饪之前的准备工作:摘菜、洗菜等等,准备相应的食材 void doPrepare(ICookingInfo info); // 烹饪模式:蒸、煮、炒、炸等,并提供菜名 void doFire(ICookingInfo info, String mode); }
Cooking类包含两个部分,一个部分的职责是获取菜名及相关信息(口味、特点…),另一个部分的职责是具体烹饪(当然需要菜名相关信息啦)。
如果ICookingInfo发生改变,势必会造成ICookingFire的改变。对于职责不同的两个部分,需要拆分。那在Cooking类中,只需要实现上述两个不同接口,即可实现烹饪一道菜的功能。
public class Cooking implements ICookingInfo, ICookingFire { @Override public String getCookingName() { return null; } @Override public void setCookingName(String cookingName) { } @Override public void doPrepare(ICookingInfo info) { } @Override public void doFire(ICookingInfo info, String mode) { } }
产生了Cooking对象后,可将其视为ICookingInfo或者ICookingFire接口使用;如果需要设置菜名或其他信息,可以当做是ICookingInfo的实现类;要是进行烹饪操作,就当做是ICookingFire的实现类。(突然引出了一个问题:James貌似不太理解类和其实现的接口之间的关系……)
总之,在接口设计中,如果接口可细分为不同的“职责”,就可将该接口进一步细分。
上述仅仅只是从接口角度解读了:单一职责原则,此外还可以用在类的设计、方法的定义上。归结为一点:自己的事情自己做,并承担相应的责任。
可参考一下实例分析:
1. http://blog.csdn.net/zhengzhb/article/details/7278174 从类的角度解读;
2. 待续……
单一职责原则的优点:
1. 类的复杂性降低,实现什么职责都有清晰明确的定义;
2. 可读性提高;
3. 可维护性提高;
4. 变更引起的风险降低;如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,对系统的扩展性、维护性都有非常大的帮助
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
单一职责原则可以使用到哪些方面?单一职责原则适用于接口、类,同样适用于方法(一个方法尽可能只做一件事情)。接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
但凡事都有需要权衡利弊的地方,不能因为生搬硬套单一职责原则,而让系统变得繁杂而庞大。