本章主要讨论结构型设计模式,即介绍一些为了达到某种目的,而使用的特殊代码结构。
在运行时想要动态地修改一个对象的属性,将对象用树状结构组织起来,构建一个松耦合的系统,即使将对象的属性都塞进一个Map里,仍然能在维护它的时候做到类型安全。 ### 如何做 看下面两张图 {% asset_img abstract-document-base.png %} {% asset_img abstract-document.png %} 这两张图可能描述的让你不是很明白,我解释一下: 首先我们有一个车(car),这个车有型号(model),有价格(price),还有部件(Part),其中,部件是一个对象,有可能是轮子,也有可能是门之类的,它们也有自己的型号和价格。型号和价格都是值域,不是对象。 无论是车还是车的部件,都相当于一个Document,所以它们都实现了Document接口,Document接口规定它们拥有设置域的值或者得到域的值的能力。无论如何,这些域全部都保存在内部的一个Map里,因此,我们的车或者车的部件比较特殊,型号啊,价格啊这些域,都没有定义在类里,是运行时动态加入进去的。 然后注意了,我们这里Document还有一个方法叫children,其实只有车这个对象使用这个方法才有意义,使用它可以得到它的children,也即部件。当然,得到的是一个类型已经转换好的视图,你对这个视图的任何操作,都会反应到原始的Part部件对象上。 我们有个AbstractDocument抽象类,不知道这个设计模式是不是这么来的,主要的存取逻辑都在这个抽象类里。 另外,第二张图还演示了我们有三个接口,分别可以获得价格、型号、部件。它们的目的是在获得的时候,能够使用类型安全的方法来获得域。 ### 好处 好处在适用场景已经说了: 1. 它可以让你在运行时动态地增删修改一个类的域,而且保证类型安全 2. 它可以构建一个松耦合的系统,它可以用来作为配置类,因为它的域可以动态删减,所以做配置类很合适。
一颗星,说明你应该尽量避免使用这种设计模式,它是小众设计模式,其复杂度远大于其带来的收益。
{% link “相关链接—含代码” http://java-design-patterns.com/patterns/abstract-document/ [external] %}
适配器模式是经典设计模式之一。主要应用在面向接口编程的实践中。如果你接口用的不多,大概用不到这种模式。 这种模式主要是解决程序员想要利用现有代码,然而无奈现有代码并不支持现在要使用的某个接口,只好使用适配器模式来包装现有代码,以使其满足接口。
看下面的图 {% asset_img adapter.png %} 我们有一个船长类(Captain),该类内部维护着一个域,这个域是一个战斗船(Battleship)的对象。船长可以调用战斗船的fire方法和move方法来攻击或者移动船只。 现在,情况比较糟糕,我们的船长就跟加勒比海盗里的杰克船长一样,丢了自己的黑珍珠号,为了能够继续冒险,只好求助于好朋友造船匠JVM,但是JVM说,我这只有渔船(FishingBoat)的模型,勉强给你造一艘得了。 那么问题来了,渔船只支持航行(sail)和打鱼(fish)两个方法,移动倒是好解决,就是航行方法呗,可是没有战斗方法呀。怎么办? 本着对修改关闭,对扩展开放
的原则,当然不能直接改渔船模型啦,改了渔船,装上大炮,渔船还能叫渔船吗?况且,渔船以后还有别的船长要造,大家都为了满足自己,你改一下我改一下,咱们的渔船就要成四不像啦。 于是,杰克船长灵机一动,发明了一种全新的模型,它即支持战斗,又是渔船。它的名字叫做战斗渔船(BattleFishingBoat),这个类内部持有渔船的一个实例,并实现战斗船接口。这下即满足了船长战斗的需求,又利用起了JVM原本造渔船的本事。 战斗渔船就是一种适配器类,它很好地使渔船支持了战斗(这部分相当于系统里从未有过的新代码,要程序员自己实现),同时,在实现移动这个方法的时候,直接内部调用渔船的sail方法,这个航行的方法,就不需要程序员自己动手写啦。
需要额外解释一下,为什么不使用继承的方法来搞定。比如,这里建立一个渔船的子类,叫做战斗渔船,让它支持战斗。原因和为什么用组合而不用继承理由类似,此处若用继承,那么我们的战斗渔船就会深受渔船的影响。当渔船有了一个释放鱼饵的方法,我们的战斗渔船也会强迫有这么一个方法。如果你用组合,你就可以选择你想要的方法出现在适配器里,而不是全部!此外还有很多和覆盖,打破约束有关的问题。请谨慎地使用继承!