组合模式属于结构型模式,又可以叫做部分-整体模式,主要解决客户程序在具有整体和部分的层次结构中,处理一组相似对象比处理单一对象费时费力的问题。例如,一个图形,它可以是一个简单的圆形、方形或一条线(部分),同时它也可以是有圆形、方形和直线组合而成的复杂图形(整体),而实现这些简单图形相比复杂图形肯定一个简单一个复杂。此时客户端程序不干了,既然复杂图形是由多个简单图形构成,其本质都是一样的,那为什么不能像操作简单图形一样操作复杂图形,这就需要使用组合模式了。
组合模式通过将一组相似的简单对象按照树形结构组合成一个单一的复杂对象,用以表示“部分-整体”的层次结构,使得客户程序在调用组合对象和单一对象时具有一致性。此时客户程序可以忽略组合对象与单一对象的不同,能够像处理单一对象一样处理组合对象。
组合模式规定简单对象和组合对象都需继承一个相同的父类,此时将父类看作根节点,一个个不能拆分的简单对象是叶子节点,而组合对象就是其他子节点。这样就完成了一个清晰的树形组合。
JTextComponent
有子类 JTextField
、JTextArea
,容器组件 Container
也有子类 Window
、Panel
假设我现在要设计一套UI界面,而UI界面是由多个组件构成的,我现在要准备一些简单的组件:按钮组件、文本组件、输入框组价,以及用这些简单组件组合的登录框组件。
在此方式中,由于抽象构件声明所有子类中的全部方法,所以客户程序没必要区分简单组件和组合组件,这对客户程序来说是透明的、看不见的。但缺点是简单组件本身不具备增加、删除子组件等管理方法,但现在必须却要实现它们,这样会带来一些安全性问题。
抽象组件
package 设计模式.组件模式;
/**
* 组件的抽象类,抽象构件角色
*/
public abstract class 抽象组件 {
public String name; // 组件名称
public 抽象组件(String name) {
this.name = name;
}
public abstract void 绘制();
public abstract void 添加(抽象组件 组件);
public abstract void 删除(抽象组件 组件);
}
按钮组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 按钮组件 extends 抽象组件 {
public 按钮组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【按钮组件】绘制完成");
}
@Override
public void 添加(抽象组件 组件) {
// 因为简单组件无法添加或移除其他组件,所以简单组件的添加和删除方法没有任何意义
System.out.println("基础组件不支持添加!");
}
@Override
public void 删除(抽象组件 组件) {
// 因为简单组件无法添加或移除其他组件,所以简单组件的添加和删除方法没有任何意义
System.out.println("基础组件不支持删除!");
}
}
文本域组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 文本域组件 extends 抽象组件 {
public 文本域组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【文本域组件】绘制完成");
}
@Override
public void 添加(抽象组件 组件) {
System.out.println("基础组件不支持添加!");
}
@Override
public void 删除(抽象组件 组件) {
System.out.println("基础组件不支持删除!");
}
}
输入框组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 输入框组件 extends 抽象组件 {
public 输入框组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【输入框组件】绘制完成");
}
@Override
public void 添加(抽象组件 组件) {
System.out.println("基础组件不支持添加!");
}
@Override
public void 删除(抽象组件 组件) {
System.out.println("基础组件不支持删除!");
}
}
登录框组件
package 设计模式.组件模式;
import java.util.ArrayList;
import java.util.List;
/**
* 组合组件,由多个简单组件组成
*/
public class 登录框聚合组件 extends 抽象组件 {
public List<抽象组件> 子组件列表 = new ArrayList<>();
public 登录框聚合组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 编写复制的绘制方法,无需让客户程序(测试类)再编写
System.out.println("名为" + name + "的【登录框组件】开始绘制:");
for (抽象组件 子组件 : 子组件列表) {
System.out.print("\t├---"); // 做一点显示的加工
子组件.绘制();
}
System.out.println(name + "绘制完成!");
}
@Override
public void 添加(抽象组件 组件) {
// 添加子组件
子组件列表.add(组件);
}
@Override
public void 删除(抽象组件 组件) {
// 删除不需要的子组件
子组件列表.remove(组件);
}
}
测试类
package 设计模式.组件模式;
public class 测试类 {
public static void main(String[] args) {
// 绘制两个按钮
new 按钮组件("一号按钮").绘制();
new 按钮组件("二号按钮").绘制();
// 绘制一个输入框和文本域
System.out.println();
new 输入框组件("一号输入框").绘制();
new 文本域组件("测试文本").绘制();
// 绘制一个登陆框
System.out.println();
// 在透明方式下,客户程序无序知道是简单组件还是组合组件,直接用抽象组件接收就行
抽象组件 条令条例文本域 = new 文本域组件("条令条例文本域");
抽象组件 登录框 = new 登录框聚合组件("一号登录框");
登录框.添加(new 按钮组件("登陆按钮"));
登录框.添加(new 按钮组件("忘记密码按钮"));
登录框.添加(new 输入框组件("账号输入框"));
登录框.添加(new 输入框组件("密码输入框"));
登录框.添加(条令条例文本域);
// 从登录框中删除了文本组件
登录框.删除(条令条例文本域);
登录框.绘制();
}
}
测试结果
名为“一号按钮”的【按钮组件】绘制完成
名为“二号按钮”的【按钮组件】绘制完成
名为“一号输入框”的【输入框组件】绘制完成
名为“测试文本”的【文本域组件】绘制完成
名为一号登录框的【登录框组件】开始绘制:
├---名为“登陆按钮”的【按钮组件】绘制完成
├---名为“忘记密码按钮”的【按钮组件】绘制完成
├---名为“账号输入框”的【输入框组件】绘制完成
├---名为“密码输入框”的【输入框组件】绘制完成
一号登录框绘制完成!
Process finished with exit code 0
在安全方式中,抽象构件将管理子构件的方法移到树枝构件中,抽象构件和树叶构件不再持有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户程序在调用时就必须要知道树叶对象和树枝对象的存在,这样也就失去了透明性。
仅仅去掉了无用的添加删除方法
抽象组件
package 设计模式.组件模式;
/**
* 组件的抽象类,抽象构件角色
*/
public abstract class 抽象组件 {
public String name; // 组件名称
public 抽象组件(String name) {
this.name = name;
}
public abstract void 绘制();
}
仅仅去掉了无用的添加删除方法
按钮组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 按钮组件 extends 抽象组件 {
public 按钮组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【按钮组件】绘制完成");
}
}
文本域组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 文本域组件 extends 抽象组件 {
public 文本域组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【文本域组件】绘制完成");
}
}
输入框组件
package 设计模式.组件模式;
/**
* 简单组件
*/
public class 输入框组件 extends 抽象组件 {
public 输入框组件(String name) {
super(name);
}
@Override
public void 绘制() {
// 重写父类抽象方法
System.out.println("名为“" + name + "”的【输入框组件】绘制完成");
}
}
登录框组件
无需变化,和上面的一样,只需要去掉@Override
注解
仅仅修改两行代码:
文本域组件 条令条例文本域 = new 文本域组件("条令条例文本域");
登录框聚合组件 登录框 = new 登录框聚合组件("一号登录框");
测试类
package 设计模式.组件模式;
public class 测试类 {
public static void main(String[] args) {
new 按钮组件("一号按钮").绘制();
new 按钮组件("二号按钮").绘制();
// 绘制一个输入框和文本域
System.out.println();
new 输入框组件("一号输入框").绘制();
new 文本域组件("测试文本").绘制();
// 绘制一个登陆框
System.out.println();
// 在安全方式下,客户程序需要知道一个组件是简单组件还是组合组件
文本域组件 条令条例文本域 = new 文本域组件("条令条例文本域");
登录框聚合组件 登录框 = new 登录框聚合组件("一号登录框");
登录框.添加(new 按钮组件("登陆按钮"));
登录框.添加(new 按钮组件("忘记密码按钮"));
登录框.添加(new 输入框组件("账号输入框"));
登录框.添加(new 输入框组件("密码输入框"));
登录框.添加(条令条例文本域);
// 从登录框中删除了文本组件
登录框.删除(条令条例文本域);
登录框.绘制();
}
}
测试结果
和之前的实现方法中的一样