设计模式详解——组合模式
前言
今天我们分享的这个设计模式,用一句话来概括的话,就是化零为整,再进一步解释就是,通过这个设计模式,我们可以像操作一个对象一样操作一个对象的集合,不过这个对象在组合模式中被称作叶节点,而对象的集合被称为组合,而这个结合本身也是也节点的树形结构的集合。是不是感觉越来越绕了呢?没关系,下面我就来详细看下组合模式的基本原理和具体实现。
组合模式
组合模式允许我们将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
使用场景
想表示对象的部分-整体层次结构(树形结构)。
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
要点
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象 使用组合模式,我们能把相同的操作应用到组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
示例
下面我们通过一个具体实例来演示下组合模式到底是如何工作的。这里我们直接用了《Head First
设计模式》上的示例,是对餐厅菜单的模拟,只不过我引入了一个通用接口。
组合接口
首先是组合的接口,这个接口不论是叶节点还是叶节点组合都需要继承,不过都不是直接继承。
public interface Component {
void add(Component component);
void remove(Component component);
Component getChild(int i);
}
组合抽象类
这个抽象类就是给叶节点和节点组合继承的,其中方法都有了默认实现,默认都抛出了UnsupportedOperationException
public abstract class MenuComponent implements Component {
@Override
public void add(Component component) {
throw new UnsupportedOperationException();
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException();
}
@Override
public Component getChild(int i) {
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
}
叶节点实现
这里的叶节点主要覆写了父类的getName
、getDescription
、isVegetarian
和getPrice
等方法,这些方法也主要是针对具体菜单的
public class MenuItem extends MenuComponent {
private String name;
private String description;
private boolean vegetarian;
private double price;
public MenuItem(String name, String description, boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isVegetarian() {
return vegetarian;
}
@Override
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.println("==========start=============");
System.out.printf("name: %s price: ¥%s%n", this.getName(), this.getPrice());
System.out.printf("description: %s isVegetarian: %s%n", this.getDescription(), this.isVegetarian());
System.out.println("==========end=============");
}
}
节点组合实现
因为节点组合要管理叶节点,所以这里主要实现了add
、remove
、getChild
等方法,当然也实现了getName
和getDescription
等基础方法:
public class Menu extends MenuComponent {
ArrayList menuComponents = new ArrayList<>();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public void add(Component component) {
menuComponents.add(component);
}
@Override
public void remove(Component component) {
menuComponents.remove(component);
}
@Override
public Component getChild(int i) {
return menuComponents.get(i);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDescription() {
return super.getDescription();
}
@Override
public void print() {
System.out.println("==========start=============");
System.out.printf("name: %s", this.getName());
System.out.printf("description: %s", this.getDescription());
System.out.println("==========child start=============");
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent component = (MenuComponent)iterator.next();
component.print();
}
System.out.println("==========child end=============");
System.out.println("============end===========");
}
}
测试代码
下面我们开始编写测试代码。这里我们分别构造了多个叶节点,并将其中一部分组成节点组合,最后分别执行叶节点和子节点的print
方法(这里的print
方法其实就是我们设计模式原理图中的operation
方法)
@Test
public void testComponent() {
// 叶节点
MenuItem slr = new MenuItem("烧鹿茸", "好吃美味,价格实惠", Boolean.FALSE, 180.0);
MenuItem sxz = new MenuItem("烧熊掌", "好吃美味,价格实惠", Boolean.FALSE, 190.0);
MenuItem hsr = new MenuItem("红烧肉", "好吃美味,价格实惠", Boolean.FALSE, 36.0);
MenuItem hsqz = new MenuItem("红烧茄子", "好吃美味,价格实惠", Boolean.TRUE, 14.0);
MenuItem hsjk = new MenuItem("红烧鸡块", "好吃美味,价格实惠", Boolean.FALSE, 38.0);
MenuItem yxrs = new MenuItem("鱼香肉丝", "好吃美味,价格实惠", Boolean.FALSE, 22.0);
MenuItem ssbc = new MenuItem("手撕包菜", "好吃美味,价格实惠", Boolean.TRUE, 12.0);
// 组合节点
Menu menu = new Menu("家常菜", "美味家常菜");
menu.add(hsr);
menu.add(hsqz);
menu.add(hsjk);
menu.add(yxrs);
menu.add(ssbc);
// 子节点print方法
slr.print();
sxz.print();
// 组合节点print方法
menu.print();
}
运行结果
总结
想必通过上面的示例,各位小伙伴已经对组合模式有了一定的认知,对这种设计模式适用的场景也有了比较明确认识:如果在一个业务中,单个对象和对象的集合需要具备同样的类型和方法,那组合模式就是最佳选择,比如我们这里的菜单和菜单组合,当然,更具体的应用场景,还需要各位小伙伴结合具体应用场景分析,但是学习的时候多思考应用场景才是学习正在的目的。好了,今天就到这里吧!
- END -