重学设计模式之组合模式(Kotlin)

480 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第二十四天,点击查看活动详情

重学设计模式之组合模式(Kotlin)

前言

组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦

组合模式(Composite Pattern):将对象组合成树形结构以表示‘部分-整体’的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式的角色以及职责

  1. Component(抽象构件):一般是接口或者抽象类,是叶子构件和容器构件对象声明接口,抽象出访问以及管理子构件的方法

  2. Leaf(叶子构件):在组合中表示叶节点,叶节点没有子节点,定义对象的基本行为

  3. Composite(容器构件):容器节点可以包含子节点,子节点可以是叶子节点,也可以是容器节点

composite_uml.png

实例

我们公司都会又有组织结构,技术部、运营部、市场部、总经办等结构,我们就以这个为例用代码来实现以下。

在组合模式中有两种实现方式:透明模式安全模式

两者的区别在于透明模式将组合使用的方法放到抽象类中,而安全模式则是放到具体实现类中

透明模式

  • 创建抽象构件Component
//抽象公司框架结构
abstract class Component(var name: String) {

    //创建节点:创建部门或者员工入职
    abstract fun add(component: Component)

    //删除节点:解散部门或者员工离职
    abstract fun remove(component: Component)

    //展示
    abstract fun show(depth: Int)
}
  • 创建容器构件Composite:
//这里可以表示具体公司框架(公司、部门)
class Composite(name: String) : Component(name) {
    var childs: ArrayList<Component> = ArrayList()
    override fun add(component: Component) {
        childs.add(component)
    }

    override fun remove(component: Component) {
        childs.remove(component)
    }

    override fun show(depth: Int) {
        for (i in 0 until depth) {
            print("    ")
        }
        println("$name: ")
        for (component in childs) {
            component.show(depth + 1)
        }
    }

}
  • 创建叶子构件Leaf:
//具体职员
class Leaf(
    name: String
) : Component(name) {
    override fun add(component: Component) {
    }

    override fun remove(component: Component) {
    }

    override fun show(depth: Int) {
        for (i in 0 until depth) {
            print("    ")
        }
        println(name)
    }

}
  • 最后我们来调用一下:
fun main() {
    //创建一个公司
    val gongsi = Composite("大厂公司")

    //创建部门
    val zongjingban = Composite("总经办")
    val jishub = Composite("技术部")
    val yunyingbu = Composite("运营部")
    val shichangbu = Composite("市场部")

    gongsi.add(zongjingban)
    gongsi.add(jishub)
    gongsi.add(yunyingbu)
    gongsi.add(shichangbu)

    //给部门招人
    zongjingban.add(Leaf("董事1"))
    zongjingban.add(Leaf("董事2"))
    zongjingban.add(Leaf("董事3"))

    jishub.add(Leaf("程序员1"))
    jishub.add(Leaf("程序员2"))
    jishub.add(Leaf("程序员3"))

    yunyingbu.add(Leaf("运营1"))
    yunyingbu.add(Leaf("运营2"))
    yunyingbu.add(Leaf("运营3"))

    shichangbu.add(Leaf("市场1"))
    shichangbu.add(Leaf("市场2"))
    shichangbu.add(Leaf("市场3"))

    gongsi.show(0)
}

输出:

大厂公司: 
    总经办: 
        董事1
        董事2
        董事3
    技术部: 
        程序员1
        程序员2
        程序员3
    运营部: 
        运营1
        运营2
        运营3
    市场部: 
        市场1
        市场2
        市场3

可以看到以上是一棵树的结果,不管是叶子节点,还是组合节点,都是一样的操作

  • 安全模式

就是叶子节点和组合节点的特性分开,只有组合节点才有增加和删除操作,而两者都会拥有展示操作。但是如果同时对外暴露叶子节点和组合节点的话,使用起来还需要做特殊的判断

修改后:

//公司框架结构
abstract class Component(var name: String) {
    //展示
    abstract fun show(depth: Int)
}
//这里可以表示具体公司框架(公司、部门)
class Composite(name: String) : Component(name) {
    var childs: ArrayList<Component> = ArrayList()
    fun add(component: Component) {
        childs.add(component)
    }

    fun remove(component: Component) {
        childs.remove(component)
    }

    override fun show(depth: Int) {
        for (i in 0 until depth) {
            print("    ")
        }
        println("$name: ")
        for (component in childs) {
            component.show(depth + 1)
        }
    }

}

//具体职员
class Leaf(
    name: String
) : Component(name) {

    override fun show(depth: Int) {
        for (i in 0 until depth) {
            print("    ")
        }
        println(name)
    }

}

fun main() {
    //创建一个公司
    val gongsi = Composite("大厂公司")

    //创建部门
    val zongjingban = Composite("总经办")
    val jishub = Composite("技术部")
    val yunyingbu = Composite("运营部")
    val shichangbu = Composite("市场部")

    gongsi.add(zongjingban)
    gongsi.add(jishub)
    gongsi.add(yunyingbu)
    gongsi.add(shichangbu)

    //给部门招人
    zongjingban.add(Leaf("董事1"))
    zongjingban.add(Leaf("董事2"))
    zongjingban.add(Leaf("董事3"))

    jishub.add(Leaf("程序员1"))
    jishub.add(Leaf("程序员2"))
    jishub.add(Leaf("程序员3"))

    yunyingbu.add(Leaf("运营1"))
    yunyingbu.add(Leaf("运营2"))
    yunyingbu.add(Leaf("运营3"))

    shichangbu.add(Leaf("市场1"))
    shichangbu.add(Leaf("市场2"))
    shichangbu.add(Leaf("市场3"))

    gongsi.show(0)
}

输出:

大厂公司: 
    总经办: 
        董事1
        董事2
        董事3
    技术部: 
        程序员1
        程序员2
        程序员3
    运营部: 
        运营1
        运营2
        运营3
    市场部: 
        市场1
        市场2
        市场3

组合模式优缺点

优点:

  • 你可以利用多态和递归机制更方便地使用复杂树结构

  • 满足开闭原则 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分

缺点:

  • 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解

与其他模式的关系

  • 桥接模式策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。

  • 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 你可以使用迭代器模式来遍历组合树。

  • 你可以使用访问者模式对整个组合树执行操作。

  • 你可以使用享元模式实现组合树的共享叶节点以节省内存。

  • 组合装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。