浅谈设计模式 - 组合模式(十二)

2021-11-02 16:38:25 浏览数 (2)

浅谈设计模式 - 组合模式(十二)

前言

组合模式是一种非常重要的设计模式,使用场景几乎随处可见,各类菜单和目录等地方都能看到组合模式的影子,组合模式通常情况下是和树形结构相辅相成的,而树是软件设计里面非常重要的数据结构,这篇文章将介绍什么是组合模式。

什么是组合模式

允许你将对象组合到树形结构表现“整体部分”的结构,组合能让客户以一致的方式处理个别对象和对象组合,组合其实更像是对于对于各种独立组建的“统一性”,可以将一类相似的事物看为一个整体但是拥有完全不同的工作机制。

介绍

可以说将相似的物品形成一个集合的模式就是组合模式,他能看两个相似的物品在一处进行完美的融合以及操作。当我们需要 「整体/部分」的操作时候,就可以使用这种形式。

特点

  • 组合模式讲究的是整体和部分之间的关系,整体可以包含部分,部分可以回溯到整体,互相包含
  • 组合模式可以让对象结构以“树”的形式包含关系。多数情况可以忽略整体和个体之前的差别

优缺点

优点:

  • 组合模式可以帮助对象和组合的对象一视同仁的对待

缺点:

  • 继承结构,修改抽象类违反开放关闭原则
  • 如果层次结构非常深,递归结构影响效率
  • 使用迭代器有可能造成并发遍历菜单的问题

❝组合模式以单一职责的原则换取透明性? 组合模式破坏了的单一职责原则,组合了多个对象的方法,同时在方法里面做了多种操作,但是这样做却是可以让整个对象可以更加直观的了解整体和部分的特性,这是设计模式里面非常常见的操作。 ❞

组合模式的结构图

组合模式的结构图如下:

  • 「Component 组件」:定义组件的接口,这里可以设计为抽象类,可以设计为接口,可以视为组件的“可能的公共行为”。
  • 「Leaf 叶子节点」:用于表示原始对象,叶子节点只需要实现自己的特殊功能即可,比如菜单的菜单子项。
  • 「Composite 组件节点」:定义组件行为,可以具备子节点。同时实现叶子节点的相关操作(继承同一个接口),可以视为一个分类的大类

实际应用场景

由于现实场景当中这样的设计模式结构是有树状结构转换而来的,所以组合模式的使用场景就是出现树形结构的地方。比如:「文件目录显示」,多及目录呈现等「树形结构数据」的操作。下面我们就使用一个菜单的结构来了解一下组合模式的“模板”代码。

实战

模拟场景

组合模式是为树形结构设计的一种设计模式,案例参照一个菜单的管理功能作为模拟,我们需要拿到不同的菜单分类,在菜单的分类里面,我们有需要拿到不同的菜单项,我们可以由任意的菜单项进入到不同的菜单分类,同时可以进入不同的叶子节点。

这次的代码案例是从网上找的例子:

抽象组件

抽象组件定义了组件的通知接口,并实现了增删子组件及获取所有子组件的方法。同时重写了hashCodeequales方法(至于原因,请读者自行思考。如有疑问,请在评论区留言)。

代码语言:javascript复制
package com.jasongj.organization;

import java.util.ArrayList;
import java.util.List;

public abstract class Organization {

  private List<Organization> childOrgs = new ArrayList<Organization>();

  private String name;

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

  public String getName() {
    return name;
  }

  public void addOrg(Organization org) {
    childOrgs.add(org);
  }

  public void removeOrg(Organization org) {
    childOrgs.remove(org);
  }

  public List<Organization> getAllOrgs() {
    return childOrgs;
  }

  public abstract void inform(String info);

  @Override
  public int hashCode(){
    return this.name.hashCode();
  }
  
  @Override
  public boolean equals(Object org){
    if(!(org instanceof Organization)) {
      return false;
    }
    return this.name.equals(((Organization) org).name);
  }

}

简单组件(部门)

简单组件在通知方法中只负责对接收到消息作出响应。

代码语言:javascript复制
package com.jasongj.organization;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Department extends Organization{
  
  public Department(String name) {
    super(name);
  }

  private static Logger LOGGER = LoggerFactory.getLogger(Department.class);
  
  public void inform(String info){
    LOGGER.info("{}-{}", info, getName());
  }

}

复合组件(公司)

复合组件在自身对消息作出响应后,还须通知其下所有子组件

代码语言:javascript复制
package com.jasongj.organization;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Company extends Organization{
  
  private static Logger LOGGER = LoggerFactory.getLogger(Company.class);
  
  public Company(String name) {
    super(name);
  }

  public void inform(String info){
    LOGGER.info("{}-{}", info, getName());
    List<Organization> allOrgs = getAllOrgs();
    allOrgs.forEach(org -> org.inform(info "-"));
  }

}

awt的组合模式

❝ 组合模式因为使用了同样的接口,会让叶子节点实现一些不必要的功能,此时一般可以使用一个「空对象」或者使用更为「激进」的使用抛出异常的形式。 ❞

awt这种老掉牙的东西就不多介绍,java的gui其实就是使用了组合模式,下面是一部分的案例代码:

代码语言:javascript复制
 //创建组件
    public MethodsTank() {
        //创建组件等
        jm = new JMenu("我的菜单(G)");
        jmb = new JMenuBar();
        jl1 = new JMenuItem("开始新游戏(F)");
        jl2 = new JMenuItem("结束游戏");
        jl3 = new JMenuItem("重新开始(R)");
        jl4 = new JMenuItem("存盘退出");
        jl5 = new JMenuItem("回到上次游戏");

        draw = new DrawTank();
        ses = new selectIsSallup();

        //设置快捷键方式
        jm.setMnemonic('G');
        jl1.setMnemonic('f');
        jl3.setMnemonic('r');
        jl4.setMnemonic('q');
        jl5.setMnemonic('w');

        //开启闪烁线程
        new Thread(ses).start();
        //先运行开始画面
        this.addTank();

    }


    public void addTank() {
        //添加菜单栏目
        jm.add(jl1);
        jm.add(jl2);
        jm.add(jl3);
        jm.add(jl4);
        jm.add(jl5);
        jmb.add(jm);

        //运行选关界面
        this.add(ses);


        //对于子菜单添加事件
        jl1.addActionListener(this);
        jl1.setActionCommand("newgame");
        jl2.addActionListener(this);
        jl2.setActionCommand("gameexit");
        jl3.addActionListener(this);
        jl3.setActionCommand("restart");


        //设置窗体的一些基本属性
        this.setTitle("我的坦克大战");
        this.setBounds(600, 350, width, height);
        //添加菜单栏的方式
        this.setJMenuBar(jmb);
        this.setDefaultCloseOperation(this.EXIT_ON_CLOSE);


        this.setVisible(true);

    }

总结

组合模式精髓在于“破而后立”,他虽然违反了设计原则,但是通过更加优雅的形式,实现了将单一的对象由部分变为一个整体。

而组合模式也经常和适配器模式搭配使用,本文的案例只是一个简单的套板,对于组合模式的实际运用场景其实更常见的情况是关于菜单和菜单子项的内容。

结语

组合模式很多情况下可能并不是十分用的上,更多的时候是和其他的设计模式搭配,组合模式我们需要关注的是“整体-部分”的融合统一即可。

参考资料:

这里有一篇讲的更好的资料,在组合模式的基础上给了一个质量稍高的案例代码:

实战组合模式「营销差异化人群发券,决策树引擎搭建场景」

0 人点赞