组合模式的定义
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 "%r" %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 等方法,是一种透明组合模式。