鸣谢:《大话设计模式》
简介
- 结构型设计模式,
-
它允许将对象组合成树形结构以表示”部分-整体”的层次关系。组合模式能够使客户端以一致的方式处理单个对象和对象组合,同时也能够递归地处理整个层次结构。
他们之间有统一的接口实现,或者统一的抽象父类
当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。
有爱因斯坦欲统一场论的气势。
主要组成部分:
- 组件(Component):组件是抽象类或接口,它声明了包含叶节点和容器节点的通用操作。这些操作通常包括添加、删除、获取子组件以及业务操作等。
-
叶节点(Leaf):叶节点是组合中的叶子对象,它不包含任何子节点,只实现了组件接口的操作。叶节点表示组合中的基本单元。
-
容器(Composite):容器是组合中的复合对象,它可以包含叶节点和其他容器作为子节点。容器实现了组件接口的操作,并通常用来管理子节点。
工作流程
-
所有组件(包括叶节点和容器)都实现相同的组件接口,这确保了一致的操作和调用方式。
-
容器节点(Composite)可以包含叶节点和其他容器节点,形成一个树形结构,表示部分-整体关系。
-
客户端可以递归地遍历整个组合结构,以执行相同的操作,而无需区分叶节点和容器节点。
例子
下面例子来源于《大话设计模式》
结构
代码
组件(Component)
/**
* @author: lpy
* @Date: 2023/10/30
*/
public abstract class Company {
protected String name;
public Company(String name){
this.name = name;
}
public void add(Company company){
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(Company company){
throw new UnsupportedOperationException("不支持删除操作");
}
public void display(int depth){
throw new UnsupportedOperationException("不支持展示操作");
}
public void lineDuty(){
throw new UnsupportedOperationException("不支持执行操作");
}
}
叶节点(Leaf)
/**
* @author: lpy
* @Date: 2023/10/30
*/
public class Department extends Company{
@Override
public void display(int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("-");
}
System.out.println(this.name);
}
@Override
public void lineDuty() {
super.lineDuty();
}
public Department(String name) {
super(name);
}
}
容器(Composite)
/**
* @author: lpy
* @Date: 2023/10/30
*/
public class ConcreteCompany extends Company{
protected ArrayList<Company> children = new ArrayList<Company>();
public ConcreteCompany(String name) {
super(name);
}
@Override
public void add(Company company) {
children.add(company);
}
@Override
public void remove(Company company) {
children.remove(company);
}
@Override
public void display(int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("-");
}
System.out.println(this.name);
for (Company child : children) {
child.display(depth+2);
}
}
@Override
public void lineDuty() {
for (Company child : children) {
child.lineDuty();
}
}
}
测试类
/**
* @author: lpy
* @Date: 2023/10/30
*/
public class CompositeModeTest {
public static void main(String[] args) {
ConcreteCompany root = new ConcreteCompany("总公司");
root.add(new Department("开发部"));
root.add(new Department("安全部"));
ConcreteCompany y1 = new ConcreteCompany("云科技分公司");
root.add(y1);
y1.add(new Department("逆向部"));
y1.add(new Department("js部"));
ConcreteCompany y2 = new ConcreteCompany("云科技分公司2");
root.add(y2);
y2.add(new Department("天文部"));
y2.add(new Department("app部"));
ConcreteCompany y3 = new ConcreteCompany("云科技分公司3");
root.add(y3);
ConcreteCompany y4 = new ConcreteCompany("云科技分部门3-`1");
y3.add(y4);
y4.add(new Department("天文部"));
y4.add(new Department("app部"));
root.display(0);
}
}
应用
- Java开发窗体用的容器控件java.awt.Container,它继承于java.awt.Component,就有add方法和remove方法,所以在它上面增加控件,比如Button、Label、Checkbox等控件,就变成很自然的事情,这就是典型的组合模式的应用。
-
ArrayList实现了List接口,List接口继承自Collection。ArrayList的addAll()方法,参数是Collection<? extends E> c ,是组合模式的应用
-
HashMap实现了Map接口,HashMap的putAll方法参数是Map<? extends K, ? extends V> m,是组合模式的应用
组合模式优缺点
-
优点:
- 一致性:组合模式允许客户端统一地处理单个对象和对象组合,因为它们都实现了相同的组件接口。
-
灵活性:可以动态地构建和修改对象组合,无需更改客户端代码。
-
简化客户端代码:客户端无需区分叶节点和容器节点,从而减少了客户端的复杂性。
-
递归处理:组合模式支持递归处理整个树形结构,使操作在整个结构中传递。
-
缺点:
- 复杂性增加:在组合模式中,客户端无法直接访问单个叶节点,而必须递归遍历整个树结构。这可能会导致代码变得复杂,尤其在处理深层次的组合结构时。
-
性能问题:递归操作可能导致性能问题,特别是在处理大型组合结构时。每次递归都需要遍历整个结构,可能会影响性能。
-
不适用于所有情况:组合模式并不适用于所有场景。在某些情况下,它可能会引入不必要的复杂性,特别是当对象结构相对简单且操作不需要递归时。
-
一致性要求:使用组合模式时,确保所有组件实现相同的接口是关键的。如果某个组件不实现所需的接口,可能会导致一致性问题。
-
维护困难:在某些情况下,组合模式可能会使维护变得更加困难,特别是在需要频繁修改组合结构的情况下。
7 大设计原则
不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;
- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。
- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。
- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。
- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。
- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。
- 迪米特法则(Law of Demeter - LoD):
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。
源码位置
全部源码github.com/xs-alpha/designPattern
参考文献
- 《大话设计模式》
-
慕课视频《Java设计模式精讲 Debug方式+内存分析》
如有内容侵权,麻烦联系博主删除