这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战java
「系统菜单」应该是全部ERP系统都必备的一个基础模块,反正我是没见过哪一个ERP系统是没有菜单设计的。典型的设计就是一个「树状结构」,数据库表用一个parent_id
来构建整棵菜单树。 既然「菜单设计」如此重要,那么今天就来说讲如何利用「组合模式」来优雅的设计系统菜单。数据库
菜单分为两类:菜单目录和具有URL地址可点击的菜单,即树枝节点和树叶节点。分别设计Branch
类表明菜单目录,Leaf
类表明菜单,类图以下,很是简单。 Branch安全
public class Branch {
private String name;
private String icon;
private List children;
// 省略全参构造函数
public void add(Branch branch) {
this.children.add(branch);
}
public void add(Leaf leaf) {
this.children.add(leaf);
}
public void print(){
System.out.println("name:" + name + ",icon:" + icon);
}
public List getChildren() {
return children;
}
}
复制代码
Leafmarkdown
public class Leaf {
private String name;
private String icon;
private String url;
// 省略全参构造函数
// 省略print()
}
复制代码
客户端构建菜单树,代码省略。。。函数
如此,一棵菜单树构建出来了,客户端也能进行遍历了,可是会发现代码实现的特别别扭啊,一点也不优雅。Branch
和Leaf
存在冗余属性和代码啊,无论是目录仍是菜单,name
和icon
都是有的,应该提取出Menu
抽象类,菜单只是一个抽象,我无论你是树枝节点仍是树叶节点,你都是菜单啊。树枝节点能够包含树枝和树叶,树叶节点就仅仅包含自身的属性特征,这些区别能够放到子类作特殊处理,可是对于客户端来讲,他们应该都是Menu
。post
从新设计一下类,优化后的类图以下: 编写
Menu
类,将菜单抽象出来,提取公共属性和方法:优化
public abstract class Menu {
protected String name;
protected String icon;
// 省略全参构造函数
// 省略print()
}
复制代码
树枝节点Branchthis
public class Branch extends Menu {
private List<Menu> children = new ArrayList<>();
public Branch(String name, String icon) {
super(name, icon);
}
public void add(Menu menu) {
children.add(menu);
}
public List<Menu> getChildren() {
return children;
}
}
复制代码
树叶节点Leafurl
public class Leaf extends Menu{
private String url;
// 省略全参构造函数
// 省略print()
}
复制代码
将对象组合成树形结构以表示“部分-总体”的层次结构,使得用户对单个对象和组合对象的使用具备一致性。spa
组合模式又被称为「部分-总体模式」,只要你须要维护「部分-总体」的关系,例如:树形菜单,文件菜单等就能够考虑使用组合模式。
细心的读者可能已经发现了,客户端在遍历整棵树时,须要判断遍历的节点是树枝仍是树叶,说白了,客户端须要知道具体的实现,这实际上是对「依赖倒置原则」的破坏,不过能够经过「透明模式」来解决。
优势
缺点 仔细看看上面的代码,客户端在遍历树时,直接使用了实现类,这违反了「依赖倒置原则」。
上述实现采用的是「安全模式」,安全模式将树枝和树叶的功能区分开了,只有树枝才能进行组合。 透明模式则是将组合使用的方法所有放到了抽象组件中,这样无论是树枝对象仍是树叶对象,都具备相同的结构,这种方式须要客户端判断子类类型,操做不当容易致使异常。
将组合方法提取到抽象Menu
类中,默认不支持组合操做,Branch
重写父类方法,实现组合功能,Leaf
不重写,由于叶子节点自己确实不支持组合操做。
public abstract class Menu {
protected String name;
protected String icon;
// 省略全参构造函数
public void add(Menu menu){
// 默认不支持操做
throw new UnsupportedOperationException();
}
public List<Menu> getChildren(){
// 默认不支持操做
throw new UnsupportedOperationException();
}
}
复制代码
这样,无论是Branch
仍是Leaf
,结构上它们都是相同的,只是各自的实现不同而已。
组合模式在项目中随处可见:树形菜单、文件系统结构、XML结构等都是树形结构,均可以优先考虑使用组合模式来实现。 组合模式用于将多个对象组合成树形结构以表示「总体-部分」的结构层次,组合模式使得客户端对简单对象和复杂对象的使用具备一致性,方便了客户端的调用。