趣谈组合模式
组合模式的定义
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 等方法,是一种透明组合模式。
版权声明:本文为nedulee原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。