有大量的文章解释什么是设计模式,如何实现设计模式,网络上不需要再写一篇这样的文章。相反,在本文中我们更多的讨论什么时候用和为什么要用,而不是用哪一个和如何使用。
我将会为这些设计模式描绘不同的场景和案例,和提供一个简短的定义帮助你们中对这些指定的模式不熟悉的人。让我们开始吧。
这篇文章覆盖了Robert C. Martin书中提到的一些敏捷设计模式。这些设计模式都是最初由四人组在1994年定义和发表的设计模式的现代改写版。Martin的模式更多是对四人组模式的承接,能更好的与现在的编程技术协作。
工厂模式用来帮助程序员管理要创建的对象的相关信息。有时对象构造方法的参数有很多,有时这些参数必须用默认的信息填充。这些对象应该在工厂内创建,把它们的生成和初始化信息放在同一个地方。
这里有两个常用的模式用于从持久层或外部数据源检索信息。
这个模式在持久化解决方案和业务逻辑间定义了一个通信频道。对于简单的应用程序,它可以通过自己检索或重建所有对象,但在复杂的应用程序中对象的创建是工厂的责任。网关只是简单的检索和保存行数据。
事实上,网关模式是另一种设计模式的一个特别的实现,我们将在稍后讨论:适配器模式。
有时你不能(不想)把持久层的信息暴露给你的业务类。代理模式(proxy pattern)是一个很好的方式:欺骗你的业务类,让它们认为是在使用已经存在的对象。
一个代理(proxy)有效实现了一个接口像真正的对象那样,有同样的功能。业务逻辑简单的把它当作真正的对象使用,但实际上,如果对象不存在代理将创建一个。
好吧,好吧。那样是很棒,但是我们如何找到需要创建的对象呢?
仓储模式对实现search方法和迷你查询语言是很有用的。它执行查询,使用一个网关封装数据,然后提供给工厂方法创建你需要的对象。
仓储模式同其它的设计模式有点不一样,它是属于领域驱动设计的一部分,也不是Robert C. Martin的书中包含的。
如果仓库找不到对象呢?一个选择是返回NULL值,但是这样会有两个副作用:
一个比较好的方法是返回一个空对象。
一个空对象实现了其它对象同样的接口,但是空对象的成员方法返回原始值。比如:一个返回字符串的方法将返回空字符串;另一个返回数字的方法将返回0。这要求你让方法不要返回有意义的值,但是你使用这些对象就不用担心请求被拒绝和去除null的检测代码。
It’s not unusual to call many methods on an object before it can do its job. There are situations when you must prepare an object after its creation before you can truly use it. This leads to code duplication when creating those objects in different places.
听起来不错,不是吗?事实上,它在很多情况下是很有用的。命令模式被广泛的用来执行事务。如果你给控制对象添加一个简单的undo()方法, 它可以跟踪所有的要取消的它执行过的事务,如果有必要还可以颠倒顺序。
现在你又有10个(或更多)的命令对象,你希望它们能同时工作。你可以把它们都聚集到一个主动对象。
简单而有趣的主动对象只有一个职责:存储一系列的命令对象,和调用它们。
一个主动对象在执行完一个命令后会把它从列表中移除,也就是说,一个命令你只能执行一次。几个主动对象的真实案例:
主动对象模式在早期的多任务系统中也发挥了作用。active对象内的每个对象都要保存一个该active对象的引用。这样它们可以执行完自己的工作后,把自己压入队列。即时在今天的系统里面,你也可以调用一个对象工作,然后等待另一个系统的响应。
我相信你听说过面向对象编程的重大承诺:代码重用。早期的OOP采纳者设想在数百万不同的项目中使用通用的库和类,但是这从未发生过。
这个模式可以重用部分代码,它适用于相互差别很小的运算。
灵活性很棒,但是我真的需要吗?
比如你创建了一个通用的Calculator,然后使用不同的ComputationStrategy来执行这个计算。这是一个被适度使用的模式,当你不得不定义很多条件行为时,它非常有用。
随着项目的发展,外部用户访问我们的应用变得越来越困难。这是一个理由:需要为讨论中的应用和模块提供一个明确定义的入口点。另外一些理由可能有想要隐藏模块的内部运作和结构。
外观(Facade)本质上是一个api——一个面向客户端的友好接口。当客户端调用其中的一个方法时,facade就委托它隐藏的其它类来提供客户端需要的信息或结果。
控制是很棒的,有时事情发生了变化,你需要执行一些任务。用户必须被通知,红灯还是闪烁,警报开始拉响…你可以用这个方法。
流行的Laravel框架把外观模式用得很好
观察者模式提供了一个简单的方法来监视对象,在条件发生变化时采取行动。这里有两种类型的观察者实现:
背景:
这个模式的案例:邮件通知、后台程序日志或信息系统。当然在现实中,有无数的途径来使用它。
中介者模式是观察者模式的一个扩展。这个模式把两个对象当做参数,中介者把它自己订阅到第一个参数(把自己当做观察者),当被观察对象变化发生时,中介者决定第二个做什么?
有时,你需要一些特别的对象:在系统中只有一个,你希望确保所有客户端都能看见这些对象的变化。因为总总原因,你想要防止创建多个实例,比如:对一些第三方库的初始化和并发操作的问题。
一个单例对象拥有一个私有的构造函数,和一个公有的getInstance()方法。这样就确保了这个对象只有一个实例存在。
另一个实现单一性的途径是Monostate模式。这种方式使用了面向对象编程语言提供的一个技巧,他有一个动态的共有方法获取或者设置静态私有变量的值。这样就确保了所有的实例共享相同的值。
要特别注意单一性。它可能会破坏全局命名空间,大多数情况下可以被其它更符合具体情况的方案代替。
假设你有一个开关和一个灯,开关可以控制开灯和关灯。但是,现在你购买了一个电风扇,想用旧的开关去控制它。使用开关、连接线,这在物理世界中是很容易实现的。
不幸的是,在编程世界里却并不容易。你有一个Switch 类和一个Light 类.如果你的开关在操作电灯,如何让他操作风扇。
很简单!复制、粘贴Switch类,修改它来操作风扇。但是这样代码重复了,这样相当于是为风扇买了一个新的开关。你可以用FanSwitch继承Switch,用一个对象代替。但是如果你需要一个按钮或远程控制呢?
这是有史以来最简单的一个模式,它仅仅是提供一个接口。但是这里有不同的实现。
PHP是动态类型的。这意味着你可以省略接口,在相同的上下文中使用不同的对象——冒着拒绝的馈赠(refused bequest)的风险。同样,php允许你定义接口,我建议你使用这个功能,让你的源码提供更清晰的意图。
但是,你想说你已经有了很多类。没错。这里有很多库、第三方的api,还有其他的模块,但是这并不意味着我们的业务逻辑知道这些细节。
适配器模式在业务逻辑和其它事物间建立了一个通信。我们已经见过这类模式了:网关模式。
如果上面的模式都不适用与你的情况,你可以使用…
这是一个非常复杂的模式。我个人不喜欢它,因为通常可以选择其它简单的方法。但是在某些特殊情况下,其它的模式都失败时,你可以考虑桥接模式。
考虑下有个脚本里面有很多相似的命令,你想通过一次调用来运行它们。等等,我们不是之前已经见过类似的情况?主动对象模式?
是的,我们见过了。但是,这次有点不一样。这次是组合模式,它保持一个对象列表。调用组合对象的一个方法,会调用它的所有对象的相同的方法,并不把它们从列表中删除。调用方法的客户端以为他们是在请求一个特定类型的对象,事实上他们的行为被应用到很多相同的类型的对象上。
这里有个示例:你有一个应用可以创建和存储订单。假设你有3个订单:$order1, $order2 和 $order3。你可以依次调用他们的place()方法,或者你可以把这些订单放到一个$compositeOrder对象里面,然后调用它的place()方法。这样一来,在包含所有订单的地方调用place().
有限状态机(FSM)是一个含有有限数字状态的模型。最简单的实现方式是用可靠的Switch…case模式。每个case语句表示一个当前状态机的状态,它知道如何激活下一个状态。
我们都知道switch…case不是很令人满意,因为它在我们的对象上输出了太多我们不想要的东西。所以忘记switch…case语句吧,我们应该考虑状态模式(state pattern)。状态模式是几个对象的组合:一个对象负责管理调度,一个表示抽象状态的接口,然后做几种不同的实现——每种实现针对一个状态。每个状态知道下一个状态是什么,这个状态可以通知负责调度的对象设置到队列里的下一个状态。
一个食物自动售货机可能有一个main类来引用state类,state类看起来可能像:WaitingForCoin, InsertedCoin, SelectedProduct, WaitingForConfirmation, DeliveringProduct, ReturningChange.每个状态完成它自己的工作,然后创建下一个对象状态并发送到负责调度的对象。
有时你在一个应用中部署了一个类或模块,你不能修改它,因为会从跟不上影响到系统。但是,同时你需要添加用户需要的新功能。
装饰模式就是针对这种情况的。它很简单:保留现有功能,另外加一个新的。这是通过继承原始类,在运行时提供新功能。旧的用户继续使用旧的对象,新用户同时使用旧的和新的功能。
一个简单的例子是打印数据。你为你的用户打印文本文件,但是你又想要有能够打印html的能力。装饰模式就是这样一个解决访问,让你同时保持两种功能。
如果说你扩展功能时遇到的问题不同,比如:你有一个复杂的像树状结构的对象,你想在一次性的在每个节点上面添加功能。但是一个访问者是一个可行的解决方案。但是不足之处是,访问者模式要求修改旧的类,如果旧的类不能接受访问者的话。
设计模式可以帮助我们解决问题。作为一个执行的建议,不要用模式命名你的类。相反,找到一个正确抽象出来的正确的名称。
有些人会说如果在你的命名里面不包含模式名称,其他的开发人员会很难理解你的代码。如果很难识别一个模式,那问题是出在这个模式的实现上。
使用设计模式可以解决你的问题,但是只是在它们适用的情况下,千万不要滥用。对于一些小问题你会找到好的解决方案。反之,只有在你用了其它的一些方案后,你会发现你需要一个设计模式。
如果你是新接触设计模式,我希望这篇文章能够给你带来一些认识:设计模式在应用程序中是很有帮助的。感谢阅读!