趣谈设计模式——组合模式

组合模式的定义

Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对某个对象和组合对象的使用具有一致性。

案例

这句话看起来似乎不太好理解,我们可以暂时先有一个概念。接下来我们会通过具体的案例,来直观的感受什么是组合模式。
假如你是一下小型IT公司的老板,整个公司处于初创阶段,只有4个部门,每个部门各司其职,负责对外承接研发工作。
--金融
--文娱
--旅游
--海淘
你的任务,就是将具体的需求分发给不同的部门,所以你很容易写出了这样一段代码。
if(type == 金融){
    //..
}else if(type == 文娱){
    //..
}
else if(type == 旅游){
    //..
}else{
    //..
}
经过你的不断努力,一段时间过后,公司已经初具规模,这个时候公司的体系架构也变得复杂起来:
公司分为不同的事业群、每个事业群下有不同的战区、每个战区下又有一级部门、一级部门下有二级部门等等等等...,这个时候,你会发现,如果你还按照原来的方法去编写代码,你的代码可能会变成这样:
(这种代码又叫做箭头形代码,有很多优化方法,比如提取方法,提前return..等等,日后会出一个代码规范化、优化的博客)
回过头来,我们再来分析这个问题,我们希望的是,不同部门,各司其职,这个时候我们就可以考虑采用组合模式
每个部门其实都是由一个个子部门组成,子部门和部门的关系,其实就是我们定义中所说的“整体-部分”的关系,如果我们把公司当作一颗树的根节点,那么各个部门就组成了这颗树的枝和叶,这样部门之间就有了树的层级结构。
而定义中描述的:使得用户对某个对象和组合对象的使用具有一致性,其实就是我们可以通过协议(编程语言中的抽象类或接口)来定义部门的属性和行为,这样无论是对于哪个部门,他对外的操作都是一致的,整体和部分就具有了一致性。

这样做有什么好处?

首先整体是由部分组成的, 每个部分其实可以作为其部分的整体,递归下去,所以我们代码中调用部分的地方,都可以用整体来替换。
(比如CTO体系/企业应用研发部/企业信息化部,CTO体系是一个整体,企业应用研发部是部分,但是对于企业信息化部来说,企业应用研发部其实也是一个整体)
其次如果整体和部分具有一致性,那么对于我们来说,将不用去书写代码去区分不同的部分了,而是可以以一种统一的方式去处理。

实现模型:

组合模式在代码具体实现上,有两种不同的方式:
1.透明组合模式:把组合(树节点)使用的方法放到统一行为(Component)中,让不同层次(树节点,叶子节点)的结构都具备一致行为;
2.安全组合模式:统一行为(Component)只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中;

为什么会有两种实现模型呢?

首先就是透明模式,会引入不必要的依赖,比如叶子结点其实是不需备add和remove功能的。所以才有了安全模式,将节点本身的方法放到自身维护,这样做的缺点就是各个节点间是有差异的,没有办法用完全统一的方式去处理。虽然实现不同,但是它们都遵循组合模式的规则。

对于两种模式的思考

其实组合模式是一种反范式的设计模式,比如透明模式会引入不必要依赖的行为,违反了接口隔离原则, 安全模式区分了叶子结点和树枝节点,导致客户端没有办法依赖抽象,违反了依赖倒置原则,基类也不能用子类去替换,违反了里式替换原则。

代码实现

接下来我们来实现透明模式组合模式的代码:
/**
 * 组合模式协议.
 *
 * @author jialin.li
 * @date 2019-12-20 15:22
 */
public abstract class AbstractComponent {
    String name;

    public AbstractComponent(String name) {
        this.name = name;
    }

    public abstract void add(AbstractComponent c);
    public abstract void remove(AbstractComponent c);
    public abstract void display(int depth);
}
import java.util.ArrayList;
import java.util.List;

/**
 * 组合模式中的枝叶,用来存储子部件.
 *
 * @author jialin.li
 * @date 2019-12-20 15:28
 */
public class Composite extends AbstractComponent {
    private List<AbstractComponent> children = new ArrayList<>();

    public Composite(String name) {
        super(name);
    }

    @Override
    public void add(AbstractComponent c) {
        children.add(c);
    }

    @Override
    public void remove(AbstractComponent c) {
        children.remove(c);
    }

    @Override
    public void display(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
        for (AbstractComponent component : children) {
            component.display(depth + 1);
        }
    }
}
/**
 * 组合模式中的叶子节点.
 *
 * @author jialin.li
 * @date 2019-12-20 15:26
 */
public class Leaf extends AbstractComponent {

    public Leaf(String name) {
        super(name);
    }

    @Override
    public void add(AbstractComponent c) {
        System.out.println("Cannot add a leaf!");
    }

    @Override
    public void remove(AbstractComponent c) {
        System.out.println("Cannot remove a leaf!");
    }

    @Override
    public void display(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }
}
/**
 * 客户端.
 *
 * @author jialin.li
 * @date 2019-12-20 15:33
 */
public class Main {
    public static void main(String[] args) {
        Composite root = new Composite("root");
        root.add(new Leaf("Leaf A"));
        root.add(new Leaf("Leaf B"));

        Composite comp = new Composite("Composite X");
        comp.add(new Leaf("Leaf XA"));
        comp.add(new Leaf("Leaf XB"));

        root.add(comp);

        Composite comp2 = new Composite("Composite XY");
        comp2.add(new Leaf("Leaf XYA"));
        comp2.add(new Leaf("Leaf XYB"));
        comp.add(comp2);

        root.add(new Leaf("Leaf C"));
        Leaf d = new Leaf("D");
        root.add(d);
        root.remove(d);

        root.display(1);
    }
}

执行结果

-root
--Leaf A
--Leaf B
--Composite X
---Leaf XA
---Leaf XB
---Composite XY
----Leaf XYA
----Leaf XYB
--Leaf C

Tomcat中的组合模式

tomcat中的容器设计,也用到了组合模式:
tomcat中设计了4中容器:Engine、Host、Context、Wrapper。这四种容器的关系如下:
它们之间是父子关系,除了Engine容器,其他都可以具有多个。容器是一个树状的层级结构
engine表示引擎,一个Service最多只能有一个引擎,Host表示虚拟主机、Context表示一个Web应用、Wrapper表示一个Servlet(后三种容器可以有多个)
Tomcat 的server.xml配置文件中的标签,其实就代表了tomcat的层级结构:
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>
这四种容器都符合一个协议:
public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}
我们可以看到该接口具有 getParent、setParent、addChild 和 removeChild 等方法,是一种透明组合模式。

原文地址:https://www.cnblogs.com/nedulee/p/12083709.html