设计模式学习:概述
首先,别把程序设计不当设计!
程序的框架就是建筑物的骨架,内部实现不过是装修改造。只要骨架设计没有问题,装修起来也是得心应手。一个糟糕的程序框架,对于接下来的Debug和程序的更新换代来讲,是灾难性的(我在本学期的数字电路实验课和上学期的程序设计实验课中都深有体会…)。
一个优秀的程序,不仅体现在实际功能上和性能上的优秀,更要体现在代码复用性、可扩展性等方面。实现同样的功能,能用两百行实现为什么要写2000行?修改2份文件就能完成更新,为什么要修改十几份?这些,都是在程序框架设计的过程中要考虑的。
GOF将设计模式的宝贵经验总结成了一本经典之作:《设计模式》,为广大程序设计者提供了指导。然而,这本书实在是太太太太太太难读了,因此,在艰难地啃完整本《设计模式》之后,我打算用自己的语言,结合自己的一些理解,重新对书中的23中设计模式用简单易懂的语言进行解读。如有不当之处,烦请指正。
首先,在开始了解设计模式本身之前,我们需要了解:我们为什么需要设计模式?
设计模式的价值
设计模式并不在乎程序本身要实现的功能和它的性能如何,它试图解决的是:当用户需求发生变化时,能做最少的修改来达成这一目标。这一过程复用性和扩展性两大方面。
复用性
下面这段代码可不叫复用:
void sortInt(int a[], int len)
{
for(int i=0; i<len-1; ++i)
for(int j=i+1; j<len; ++j)
{
if(a[i] < a[j])
swap(a[i], a[j]);
}
}
void sortDouble(double a[], int len)
{
for(int i=0; i<len-1; ++i)
for(int j=i+1; j<len; ++j)
{
if(a[i] < a[j])
swap(a[i], a[j]);
}
}
这是用冒泡排序分别对INT类型和DOUBLE类型进行排序,可见,这两段函数的函数体内容是完全一致的,有人或许认为:写好其中一个函数,直接复制粘贴到另一个函数中,不也没写多少代码吗?
这叫代码复用吗?显然不叫。我们所说的代码复用,简单来讲,是体现在二进制层面的。比如,我们调用库函数printf,printf函数作为共享库函数,在使用时可以直接链接现成的DLL文件。而如果你试图把printf的源代码直接拷贝到自己的文件中,内存中就会多出一部分内容用于存放你拷贝的这段代码,即使printf本身已经存在与内存中,这样,就造成了二进制层面上的代码浪费。
扩展性
在软件开发的过程中,必须要遵循一条规则:尽量使用扩展,而非修改原本代码的方式,对现有软件进行更新。
比如,一款游戏要进行更新,你不需要整个把游戏下载一遍,只需要下载一个大小远小于游戏本身的更新包。因为可扩展性良好,开发者只需要修改或添加若干文件就可以完成游戏内容的更新,而大部分文件在更新中不需要发生变化。
这也是设计模式最想要解决的问题,比起代码复用性,这也是一个更加复杂的问题,你将在对设计模式的学习中,进一步加深对它的理解。
这里先列一些涉及到良好可扩展性的关键词:
晚绑定,运行时(runtime)多态,编译时(compile)多态,松耦合,封装…
面向对象的设计理念将会成为解决这些问题的强大武器。
八大设计原则
23个设计模式说到底,只是一种经验的理论化表述。这些设计模式,往往有许多相似之处,但万变不离其宗,所有的解决方案,都围绕下面这八大设计原则展开:
这里只提一嘴,之后对设计模式的详细学习将会加深你对它们的理解。
-
依赖倒置原则(DIP)
高层模块(稳定)不应当依赖于低层模块,而二者都应当依赖于抽象(稳定)。
抽象(稳定)不应当依赖于实现细节(变化),实现应当依赖于抽象(稳定)。
-
开放封闭原则(OCP)
对扩展开放,对更改封闭。
类模块应当是可扩展的,但是不可修改。
-
单一职责原则(SRP)
一个类应该仅有一个引起它变化的原因。
变化的方向隐含着类的责任。
-
Liskov替换原则(LSP)
子类必须能够替换它们的基类(IS-A“是一个”原则)
继承应当表达的是类型抽象
-
接口隔离原则(ISP)
不应该强迫客户程序依赖它们不用的方法。
接口应当小而完备。
-
对象组合优于类继承
-
封装变化点
设计者在变化来临时,可以在变化点一侧进行修改,而不会对另一侧产生不良影响。
-
面向接口编程
统一化的接口实现了更好的封装性,极大地提高了代码可扩展性和可复用性。
类比:秦统一度量衡,从而使得国家管理更加简便;为什么各国要争着抢着研发新技术?等人家搞好了嫖过来不好吗?答:新技术的开发者拥有指定行业标准(即统一接口)的权力,这一权力的重要性是难以言述的。
如何应用设计模式?
设计模式的应用不是说,拿到一个项目,一拍脑袋就能决定用什么设计模式。应当在对用户需求的分析后,通过对已有代码的重构来迭代地进行。
同时,各个设计模式有时大同小异,相辅相成,甚至相互渗入。在解决一个问题时,我们应当以上面提到的八大设计原则为基准来判断设计的好坏,而非死板地套用模式。事实上,无脑套模式也只会增加自己编写的负担。
一个应用的稳定点和变化点将会是重点分析的内容,所谓稳定,如果程序的某些部分1个月就要发生一次变化,而有些部分1年才发生一次变化,那么后者就是稳定点。也就是说,稳定时相对的。固定一个稳定点,针对可能来临的变化搭建程序,使得程序能够做出良好的反应,这正是设计模式要做到的工作。
23个设计模式持续更新中…
1. 组件协作类
设计模式:Template Method(模板方法)
设计模式:Strategy(策略模式)
设计模式:Observer(观察者模式)
2. 单一职责类
设计模式:Decorator(装饰模式)
设计模式:Bridge(桥接模式)
3. 对象创建类
设计模式:Factory Method(工厂方法)
设计模式:Abstract Factory(抽象工厂)
设计模式:Prototype(原型机)
4. 对象性能类
设计模式:Singleton(单例类)
5. 数据结构类
设计模式:Composite(组合模式)
设计模式:Iterator(迭代器)
2021.1.19