结构型模式——组合模式(Composite Pattern)

组合模式(Composite Pattern)

应用

使用模板(Java)
透明模式

透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构。

组合透明

public class CompositePattern {
    //客户端
    public static void main(String[] args) {
        //创建节点
        Composite root = new Composite("root");
        root.add(new Leaf("Leaf A"));
        root.add(new Leaf("Leaf B"));
        Composite branch = new Composite("Composite c1");
        branch.add(new Leaf("Leaf c1A"));
        branch.add(new Leaf("Leaf c1B"));
        root.add(branch);
        Composite branch2 = new Composite("Composite d1");
        branch2.add(new Leaf("Leaf d1A"));
        branch2.add(new Leaf("Leaf d1B"));
        branch.add(branch2);
        root.add(new Leaf("Leaf E"));
        Leaf leaf = new Leaf("Leaf F");
        root.add(leaf);
        root.remove(leaf);
        root.display();
    }
}
//抽象组件
abstract class Component {
    protected String name;
    public Component(String name) {
        this.name = name;
    }
    //向节点中添加
    public abstract void add(Component c);
    //删除
    public abstract void remove(Component c);
    //展示所有节点
    public abstract void display();
}
//树枝组件
class Composite extends Component {
    //叶子节点集合
    private List<Component> list = new ArrayList<>();
    public Composite(String name) {
        super(name);
    }
    @Override
    public void add(Component component) {
        this.list.add(component);
    }
    @Override
    public void remove(Component component) {
        this.list.remove(component);
    }
    @Override
    public void display() {
        //输出当前的节点内容
        System.out.println(name);
        //下级遍历
        for (Component component : list) {
            component.display();
        }
    }
}
/**
 * 叶子节点
 */
class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }
    @Override
    public void add(Component component) {
        //树枝功能,叶子节点默认异常实现
        throw new UnsupportedOperationException();
    }
    @Override
    public void remove(Component component) {
        //树枝功能,叶子节点默认异常实现
        throw new UnsupportedOperationException();
    }
    @Override
    public void display() {
        //输出树形结构的叶子节点
        System.out.println("|__ " + name);
    }
}
安全模式

安全模式是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全。而安全就意味着牺牲了使用时的便捷性。

UML
组合安全模式

//抽象组件
abstract class SafeComponent {
    protected String name;

    public SafeComponent(String name) {
        this.name = name;
    }
    //展示所有节点
    public abstract void display();
}
class SafeComposite extends SafeComponent {
    //叶子节点集合
    private List<SafeComponent> list = new ArrayList<>();
    public SafeComposite(String name) {
        super(name);
    }
    public void add(SafeComponent component) {
        this.list.add(component);
    }
    public void remove(SafeComponent component) {
        this.list.remove(component);
    }
    @Override
    public void display() {
        //输出当前的节点内容
        System.out.println(name);
        //下级遍历
        for (SafeComponent component : list) {
            component.display();
        }
    }
}
/**
 * 叶子节点
 */
class SafeLeaf extends SafeComponent {
    public SafeLeaf(String name) {
        super(name);
    }
    @Override
    public void display() {
        //输出树形结构的叶子节点
        System.out.println("|__ " + name);
    }
}
实例分析(JDK)

简介

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

组合模式(Composite Pattern)又叫部分-整体模式属于结构型模式。

是用于把一组相似的对象当作一个单一的对象。

组合模式依据树形结构来组合对象,用来表示“部分-整体”层次。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

UML图

透明模式

安全模式

角色
  • 抽象构件(Component)角色:为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。组抽象构件还声明访问和管理子类的接口;
  • 树枝构件(Composite)角色 / 中间构件:分支节点对象,它有子节点,用于继承和实现抽象构件,但可能不具有树叶构件的某些行为。它的主要作用是存储管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
  • 树叶构件(Leaf)角色:叶节点对象,没有子节点,用于继承或实现抽象构件,定义了组合内元素的行为。
目的:
  • 将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:
  • 处理诸多有层级关系的简单且相似元素组成的复杂元素。
何时使用:
  1. 表示对象的部分-整体层次结构(树形结构)。
  2. 表示对象组
如何解决:

树枝和叶子实现统一接口,树枝内部组合该接口。

  1. 定义一个接口,并在接口中定义要实现的功能
  2. 叶子节点实现这个接口,并重写接口中的方法
  3. 树枝节点中有一个集合或者数组,可以对接口对象进行管理。同时,树枝节点还要实现这个接口,在重写接口的方法时可以循环集合或数组得到接口对象,并对其进行调用
关键代码:
  • 树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例:
  1. 算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。
  2. 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点:
  • 行为一致。统一的类型。
  • 高层模块调用简单。 调用者无需关系是单个简单节点还是一个组合的复杂节点
  • 节点自由增加。
缺点:
  • 设计较复杂,需要梳理层次关系;
  • 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
  • 不容易处理容器中的构件。对于特殊节点的处理不方便。

JDK使用分析

HashMapArrayList中都使用了组合模式。

HashMap应用分析
UML

Map与组合模式

角色对应
  • 抽象构件(Component):Map以及其中的Entry。二者可以抽象为安全模式,抽象构建拆分为树枝抽像构建、树叶抽象构件。
  • 树叶构件(Leaf)角色:HashMap中的内部类Node作为具体的存储对象的类,即树叶构件。
  • 树枝构件(Composite):HashMap。HashMap作为树枝构件(容器)用来保存树叶构件。
代码分析
  1. 从基础代码开始。创建两个HashMap,并将其中一个复制映射到另一个。

    Map<Integer,String> mapA=new HashMap<Integer,String>();
    mapA.put(0, "零号叶子");
    Map<Integer,String> mapB=new HashMap<Integer,String>();
    mapB.put(1, "一号叶子");
    mapB.put(2, "二号叶子"); 
    //前面不太重要,注意这里↓
    mapA.putAll(mapB);
    
  2. 那么我们进入HashMap.putAll(Map<? extends K, ? extends V> m)

    Copies all of the mappings from the specified map to this map. These mappings will replace any mappings that this map had for any of the keys currently in the specified map.

    将所有映射从指定映射复制到此映射。 这些映射将替换此映射对当前指定映射中的任何键的任何映射。

    public void putAll(Map<? extends K, ? extends V> m) {
    	//主要代码在以下方法中
        putMapEntries(m, true);
    }
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        //传入map的大小
        int s = m.size();
        //传入map的大小大于0
        if (s > 0) {
            //当前map中对象还处于未初始化的状态
            if (table == null) { 
                //此处计算最终容量,具体逻辑参考loadFactor的作用
                float ft = ((float) s / loadFactor) + 1.0F;
                //如果小于最大容量,就进行截断;否则就赋值为最大容量
                int t = ((ft < (float) MAXIMUM_CAPACITY) ?
                         (int) ft : MAXIMUM_CAPACITY);
                //此处判断是否需要扩容,具体逻辑参考threshold的作用
                if (t > threshold)
                    threshold = tableSizeFor(t);
            //如若传入的map大小大于临界值(threshold)则扩容
            } else if (s > threshold)
                resize();
            //遍历存放元素
            for (Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                //存放元素
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
    
  3. 存放元素,此处涉及知识点较多。简单来说就是放进去一个键值对。官方解释如下。

    Associates the specified value with the specified key in this map (optional operation). If the map previously contained a mapping for the key, the old value is replaced by the specified value. (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.)

    将指定值与此映射中的指定键关联(可选操作)。 如果映射先前包含键的映射,则旧值将替换为指定值。 (当且仅当 m.containsKey(k) 返回 true 时,映射 m 被称为包含键 k 的映射。)

    但是以什么形式存入的呢?

    new Node<>(hash, key, value, next)
    

    将键值对封入一个Node,存入参数transient Node<K,V>[] table

  4. 至此代码分析完毕。

    1. HashMap作为一个容器(树枝构件),利用参数table存放数据(树叶构件)。
    2. putAll作为一个数值方法,可以将某树枝下的所有树叶复制传入到另一个树枝下。
      指定值。 (当且仅当 m.containsKey(k) 返回 true 时,映射 m 被称为包含键 k 的映射。)

    但是以什么形式存入的呢?

    new Node<>(hash, key, value, next)
    

    将键值对封入一个Node,存入参数transient Node<K,V>[] table

  5. 至此代码分析完毕。

    1. HashMap作为一个容器(树枝构件),利用参数table存放数据(树叶构件)。
    2. putAll作为一个数值方法,可以将某树枝下的所有树叶复制传入到另一个树枝下。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值