文章示例代码
你也可以在这些平台阅读本文:
将对象组合成树形结构以表示“部分-整体”的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有一致性。
首页、专题推荐、设计模式、Spring Cloud这些可以看作具体菜单项。编程手册则可以看作一个菜单目录,菜单目录下拥有设计模式和Spring Cloud这些菜单项。
菜单项是包含具体的页面访问地址的。
创建一个菜单组件的抽象类,内部包含一些默认实现,交由子类决定是否重写。
/**
* @author zhh
* @description 菜单组件类
* @date 2020-02-22 11:13
*/
public abstract class MenuComponent {
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException("不支持删除操作");
}
public String getUrl(MenuComponent menuComponent) {
throw new UnsupportedOperationException("不支持获取地址操作");
}
public abstract String getName(MenuComponent menuComponent);
public abstract void print();
}
这里菜单项包含名称和页面访问地址两个属性。菜单项是不能进行菜单的 add
和 remove
操作的,只有菜单目录是可以的。所以这里不重写上述的 add
和 remove
方法。
/**
* @author zhh
* @description 菜单项类
* @date 2020-02-22 11:44
*/
public class MenuItem extends MenuComponent {
/**
* 菜单项名称
*/
private String name;
/**
* 页面访问地址
*/
private String url;
public MenuItem(String name, String url) {
this.name = name;
this.url = url;
}
@Override
public String getName(MenuComponent menuComponent) {
return this.name;
}
@Override
public String getUrl(MenuComponent menuComponent) {
return this.url;
}
@Override
public void print() {
System.out.println(String.format("%s, 菜单项的页面访问地址是: %s", name, url));
}
}
菜单目录中可以包含很多的菜单项,而菜单项又是作为菜单组件。所以我们可以用一个容器属性去持有这些菜单组件。
/**
* @author zhh
* @description 菜单目录类
* @date 2020-02-22 15:03
*/
public class MenuCatalog extends MenuComponent {
/**
* 菜单目录名称
*/
private String name;
/**
* 菜单目录层级, 方便区分
*/
private Integer level;
/**
* 子菜单项列表
*/
private List<MenuComponent> menuItems = new ArrayList<MenuComponent>();
public MenuCatalog(String name, Integer level) {
this.name = name;
this.level = level;
}
@Override
public void add(MenuComponent menuComponent) {
menuItems.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuItems.remove(menuComponent);
}
@Override
public String getName(MenuComponent menuComponent) {
return this.name;
}
@Override
public void print() {
System.out.println(this.name);
for (MenuComponent menuComponent : menuItems) {
if (this.level != null) {
for (int i = 0; i < this.level; i++) {
System.out.print("*");
}
}
menuComponent.print();
}
}
}
/**
* @author zhh
* @description 测试类
* @date 2020-02-22 15:15
*/
public class Test {
public static void main(String[] args) {
String site = "www.zhaohaihao.com";
// 首页
MenuComponent index = new MenuItem("首页", site);
// 编程手册
MenuComponent programmingManual = new MenuCatalog("编程手册", 2);
programmingManual.add(new MenuItem("设计模式", site + "/category/design-patterns"));
programmingManual.add(new MenuItem("Spring Cloud", site + "/category/spring-cloud"));
// 主题推荐
MenuComponent topic = new MenuItem("主题推荐", site + "/topic");
// 网站导航栏, 顶级目录, 目录层级用1标记
MenuComponent main = new MenuCatalog("网站导航目录", 1);
main.add(index);
main.add(programmingManual);
main.add(topic);
main.print();
}
}
测试类的输出结果如下:
网站导航目录
*首页, 菜单项的页面访问地址是: www.zhaohaihao.com
*编程手册
**设计模式, 菜单项的页面访问地址是: www.zhaohaihao.com/category/design-patterns
**Spring Cloud, 菜单项的页面访问地址是: www.zhaohaihao.com/category/spring-cloud
*主题推荐, 菜单项的页面访问地址是: www.zhaohaihao.com/topic
由于菜单项类和菜单目录类都继承了菜单组件类,它们继承了菜单组件的所有行为。
组合模式有透明方式和安全方式两种。
抽象构件声明了所有子类的全部方法,客户端无需区分叶节点对象和分支节点对象,对于客户端来说是透明的。
但是叶节点对象本身不包含子节点,而抽象构件又声明实现了一些针对于子节点的 add
、 remove
等操作,这样会带来一些安全性的问题。
安全方式与透明方式相反。
其将针对于子节点的 add
、 remove
等操作移到了分支节点对象中,而抽象构件和叶节点对象并不包含这些方法,这样一来就避免了透明方式所带来的安全性问题。
但是由于分支节点对象和叶节点对象接口行为的不同,客户端需要区分叶节点对象和分支节点对象,所以就失去了透明性。
上述场景示例中的代码实际上是透明方式的组合模式。笔者这里对上述场景示例代码进行调整,使用安全方式的组合模式来实现。
移除菜单组件中的 add
、 remove
、 getUrl
等操作。
public abstract class MenuComponent {
public abstract String getName(MenuComponent menuComponent);
public void print() {
throw new UnsupportedOperationException("不支持打印操作");
}
}
菜单项实现 getUrl
方法,而菜单目录则不实现该方法。
public class MenuItem extends MenuComponent {
/**
* 菜单项名称
*/
private String name;
/**
* 页面访问地址
*/
private String url;
public MenuItem(String name, String url) {
this.name = name;
this.url = url;
}
public String getUrl(MenuComponent menuComponent) {
return this.url;
}
@Override
public String getName(MenuComponent menuComponent) {
return this.name;
}
@Override
public void print() {
System.out.println(String.format("%s, 菜单项的页面访问地址是: %s", name, url));
}
}
菜单目录实现 add
、 remove
方法,而菜单项则不实现该方法。
public class MenuCatalog extends MenuComponent {
/**
* 菜单目录名称
*/
private String name;
/**
* 菜单目录层级, 方便区分
*/
private Integer level;
/**
* 子菜单项列表
*/
private List<MenuComponent> menuItems = new ArrayList<MenuComponent>();
public MenuCatalog(String name, Integer level) {
this.name = name;
this.level = level;
}
public void add(MenuComponent menuComponent) {
menuItems.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuItems.remove(menuComponent);
}
@Override
public String getName(MenuComponent menuComponent) {
return this.name;
}
@Override
public void print() {
System.out.println(this.name);
for (MenuComponent menuComponent : menuItems) {
if (this.level != null) {
for (int i = 0; i < this.level; i++) {
System.out.print("*");
}
}
menuComponent.print();
}
}
}
public class Test {
public static void main(String[] args) {
String site = "www.zhaohaihao.com";
// 首页
MenuComponent index = new MenuItem("首页", site);
// 编程手册
MenuCatalog programmingManual = new MenuCatalog("编程手册", 2);
programmingManual.add(new MenuItem("设计模式", site + "/category/design-patterns"));
programmingManual.add(new MenuItem("Spring Cloud", site + "/category/spring-cloud"));
// 主题推荐
MenuComponent topic = new MenuItem("主题推荐", site + "/topic");
// 网站导航栏, 顶级目录, 目录层级用1标记
MenuCatalog main = new MenuCatalog("网站导航目录", 1);
main.add(index);
main.add(programmingManual);
main.add(topic);
main.print();
}
}