设计模式-合成模式
合成模式属于对象的结构模式,有时又叫做“部分-整体”模式。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与复合元素同等看待。 (学习)
合成模式
合成模式把部分和整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由它们复合面成的合成对象同等看待。
比如,一个文件系统就是一个典型的合成模式系统。下图是常见的计算机xp文件系统的一部分。
从上图可以看出,文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构,在图中涂有颜色;另一种是文件,即树叶节点,没有内部树结构。
显然,可以把目录和文件当做同一种对象同等对待和处理,这也是就是合成模式的应用。
合成模式可以不提供父对象的管理方法,但是合成模式必顺在合适的地方提供子对象的管理方法,如:add()、remove()、以及getChild()等。
合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式和透明式。
安全式合成模式的结构
安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中。
这种形式涉及到三个角色
- 抽象构件(Component)角色:
这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当做类型为Component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出。
- 树叶构件(Leaf)角色:
树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
- 树枝构件(Composite)角色:
代表参加组合的下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如add()、removed()以及getChild()。
源代码
package Composite /** * 抽象构件角色 * */ interface Component { /** * 输出组件自身的名称 * */ fun printStruct(preStr: String) }
package Composite /** * 树枝构件角色 * */ class Composite constructor(name: String) : Component { /** * 用于存储组合对象中包含的子组件对象 * */ private val childComponents = ArrayList<Component>() private var name = "" init { this.name = name } fun addChild(child: Component) { childComponents.add(child) } fun removeChild(index: Int) { childComponents.removeAt(index) } fun getChildren(): List<Component> { return childComponents } /** * 输出对象的自身结构 * @param preStr 前缀,主要是按照层级控制空格,实现向后缩进 * */ override fun printStruct(preStr_: String) { var preStr = preStr_ //选输出自已 println("$preStr_ + $name") if (childComponents != null) { preStr += " " for (child in childComponents) { //递归输出每个子对象 child.printStruct(preStr) } } } }
package Composite /** * 树叶构建角色类 * */ class Leaf constructor(name:String) : Component { private var name = "" init { this.name = name } /** * 输出叶子对象的结构,叶子没有子对象,也是就输出叶子对象的名字 * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进 * */ override fun printStruct(preStr: String) { println("$preStr - $name") } }
测试运行:
val root = Composite("Clothing") val c1 = Composite("Mens") val c2 = Composite("Womens") val leaf1 = Leaf("Shirt") val leaf2 = Leaf("Jacket") val leaf3 = Leaf("Skirt") val leaf4 = Leaf("Suit") root.addChild(c1) root.addChild(c2) c1.addChild(leaf1) c1.addChild(leaf2) c2.addChild(leaf3) c2.addChild(leaf4) root.printStruct("")
结果:
+ Clothing + Mens - Shirt - Jacket + Womens - Skirt - Suit
可以看出,树枝构件类(Composite)给出了addChild()、removeChild()以及getChild()等方法的声明和实现,而树叶构件类则没有给出这些方法的声明或实现。这样的做法是安全的做法,由于这个特点,客户端应用程序不可能错误地调用树叶构件的聚集方法,因为树叶构件没有这些方法,调用会导致编译错误。
安全式合成模式的缺点是不够透明,因为树叶类和树枝类将具有不同的接口。
透明式合成模式的结构
与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口。
源代码
package Composite.Transparent /** * 抽象构建角色类 * */ abstract class Component { /** * 输出组件自身的名称 * */ abstract fun printStruct(preStr: String) /** * 增加一个子构件对象 * @param child 子构件对象 * */ open fun addChild(child: Component) { //缺省实现,抛出异常,因为叶子对象没有此功能 //或者子组件没有实现这个功能 throw UnsupportedOperationException("对象不支持此功能") } open fun removeChild(index: Int) { //缺省实现,抛出异常,因为叶子对象没有此功能 //或者子组件没有实现这个功能 throw UnsupportedOperationException("对象不支持此功能") } open fun getChild(): List<Component> { //缺省实现,抛出异常,因为叶子对象没有此功能 //或者子组件没有实现这个功能 throw UnsupportedOperationException("对象不支持此功能") } }
package Composite.Transparent /** * 树枝构建角色类 * */ class Composite constructor(name: String) : Component() { private val childComponents = ArrayList<Component>() private var name = "" init { this.name = name } override fun addChild(child: Component) { childComponents.add(child) } override fun removeChild(index: Int) { childComponents.removeAt(index) } override fun getChild(): List<Component> { return childComponents } /** * 输出对象自身构建 * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进 * */ override fun printStruct(preStr_: String) { var preStr = preStr_ println("$preStr + $name") if (childComponents != null) { //添加两个空格,表示向后缩进两个空格 preStr += " " //输出当前对象的子对象 for (child in childComponents) { //递归输出每个子对象 child.printStruct(preStr) } } } }
package Composite.Transparent class Leaf constructor(name: String) : Component() { private var name = "" init { this.name = name } /** * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */ override fun printStruct(preStr: String) { println("$preStr - $name") } }
运行测试,客户类的主要变化是不再区分Composite对象和Leaf对象。
val root = Composite("Clothing") val c1 = Composite("Mens") val c2 = Composite("Womens") val leaf1 = Leaf("Shirt") val leaf2 = Leaf("Jacket") val leaf3 = Leaf("Skirt") val leaf4 = Leaf("Suit") root.addChild(c1) root.addChild(c2) c1.addChild(leaf1) c1.addChild(leaf2) c2.addChild(leaf3) c2.addChild(leaf4) root.printStruct("")
运行结果
+ Clothing + Mens - Shirt - Jacket + Womens - Skirt - Suit
可以看出,如果在java中客户端无需再区分操作的是树枝对象(Composite)还是树叶对象(Leaf)了;对于客户端而言,操作的都是Component对象。
两种实现方法的选择
这里所说的安全性合成模式工是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。
这里所说的透明性合成模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Component对象,具体的类型对于客 户端而言是透明的,是无顺关心的。
对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。
而且对于安生性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。
因此在使用合成模式的时候,建议多采用透明性的实现方式。