【转载】组合模式-系统菜单的设计
1. 系统菜单的设计
「系统菜单」应该是所有ERP系统都必备的一个基础模块,反正我是没见过哪个ERP系统是没有菜单设计的。典型的设计就是一个「树状结构」,数据库表用一个parent_id
来构建整棵菜单树。
既然「菜单设计」如此重要,那么今天就来讲讲如何利用「组合模式」来优雅的设计系统菜单。
菜单分为两类:菜单目录和具备URL地址可点击的菜单,即树枝节点和树叶节点。分别设计Branch
类代表菜单目录,Leaf
类代表菜单,类图如下,非常简单。
Branch
public class Branch {private String name;private String icon;private List children;public Branch(String name, String icon) {this.name = name;this.icon = icon;this.children = new ArrayList<>();}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;}
}
Leaf
public class Leaf {private String name;private String icon;private String url;public Leaf(String name, String icon, String url) {this.name = name;this.icon = icon;this.url = url;}public void print() {System.out.println("name:" + name + ",icon" + icon + ",url:" + url);}
}
客户端构建菜单树,并遍历输出:
public class Client {public static void main(String[] args) {Branch root = new Branch("系统管理", "system.icon");Branch menu = new Branch("菜单管理", "menu.icon");menu.add(new Leaf("菜单列表", "menuList.icon", "/menu/list.html"));menu.add(new Leaf("用户菜单", "userMenu.icon", "/menu/user.html"));root.add(menu);root.add(new Leaf("角色管理", "role.icon", "/role.html"));root.add(new Leaf("权限管理", "auth.icon", "/auth.html"));show(root, 0);}// 展示菜单static void show(Branch branch,int tier) {for (int i = 0; i < tier; i++) {System.out.print("····");}branch.print();for (Object o : branch.getChildren()) {if (o instanceof Branch) {show((Branch) o, tier + 1);}else {for (int i = 0; i <= tier; i++) {System.out.print("····");}((Leaf)o).print();}}}
}
输出:
name:系统管理,icon:system.icon
····name:菜单管理,icon:menu.icon
········name:菜单列表,iconmenuList.icon,url:/menu/list.html
········name:用户菜单,iconuserMenu.icon,url:/menu/user.html
····name:角色管理,iconrole.icon,url:/role.html
····name:权限管理,iconauth.icon,url:/auth.html
OK,一棵菜单树构建出来了,客户端也能进行遍历了,但是会发现代码实现的特别别扭啊,一点也不优雅。Branch
和Leaf
存在冗余属性和代码啊,不管是目录还是菜单,name
和icon
都是有的,应该提取出Menu
抽象类,菜单只是一个抽象,我不管你是树枝节点还是树叶节点,你都是菜单啊。树枝节点可以包含树枝和树叶,树叶节点就仅仅包含自身的属性特征,这些区别可以放到子类做特殊处理,但是对于客户端来说,他们应该都是Menu
。
重新设计一下类,优化后的类图如下:
编写Menu
类,将菜单抽象出来,提取公共属性和方法:
public abstract class Menu {protected String name;protected String icon;public Menu(String name, String icon) {this.name = name;this.icon = icon;}public void print(){System.out.println("name:" + name + ",icon:" + icon);}
}
树枝节点Branch
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;}
}
树叶节点Leaf
public class Leaf extends Menu{private String url;public Leaf(String name, String icon, String url) {super(name, icon);this.url = url;}@Overridepublic void print() {System.out.println("name:" + name + ",icon:" + icon + ",url:" + url);}
}
客户端这样调用:
public class Client {public static void main(String[] args) {Branch root = new Branch("系统管理", "system.icon");Branch menu = new Branch("菜单管理", "menu.icon");menu.add(new Leaf("菜单列表", "menuList.icon", "/menu/list.html"));menu.add(new Leaf("用户菜单", "userMenu.icon", "/menu/user.html"));root.add(menu);root.add(new Leaf("角色管理", "role.icon", "/role.html"));root.add(new Leaf("权限管理", "auth.icon", "/auth.html"));show(root, 0);}static void show(Menu menu,int tier) {for (int i = 0; i < tier; i++) {System.out.print("····");}if (menu instanceof Branch) {menu.print();for (Menu child : ((Branch) menu).getChildren()) {show(child, tier + 1);}} else if (menu instanceof Leaf) {menu.print();}}
}
运行结果相同,一颗完整的菜单树展现出来了。
2. 组合模式的定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式通用类图
- Component:抽象组件,提取公共的方法和属性。
- Leaf:叶子节点,没有分支,遍历的最小单位。
- Composite:树枝节点,复杂对象,组合树枝和树叶形成树状结构。
组合模式又被称为「部分-整体模式」,只要你需要维护「部分-整体」的关系,例如:树形菜单,文件菜单等就可以考虑使用组合模式。
细心的读者可能已经发现了,客户端在遍历整棵树时,需要判断遍历的节点是树枝还是树叶,说白了,客户端需要知道具体的实现,这其实是对「依赖倒置原则」的破坏,不过可以通过「透明模式」来解决。
3. 组合模式的优缺点
优点
- 高层模块调用简单,整棵树中所有的节点都是Component抽象,高层模块不关心自己处理的是简单对象还是复杂对象,统一了客户端处理节点的方式。
- 屏蔽了节点实现细节对高层模块的影响,可以随时加入新的节点。
- 节点可以自由增加,扩展性高。
缺点
仔细看看上面的代码,客户端在遍历树时,直接使用了实现类,这违反了「依赖倒置原则」。
4. 透明的组合模式
上述实现采用的是「安全模式」,安全模式将树枝和树叶的功能区分开了,只有树枝才能进行组合。
透明模式则是将组合使用的方法全部放到了抽象组件中,这样不管是树枝对象还是树叶对象,都具有相同的结构,这种方式需要客户端判断子类类型,操作不当容易导致异常。
透明的组合模式通用类图
将组合方法提取到抽象Menu
类中,默认不支持组合操作,Branch
重写父类方法,实现组合功能,Leaf
不重写,因为叶子节点本身确实不支持组合操作。
public abstract class Menu {protected String name;protected String icon;public Menu(String name, String icon) {this.name = name;this.icon = icon;}public void add(Menu menu){// 默认不支持操作throw new UnsupportedOperationException();}public List<Menu> getChildren(){// 默认不支持操作throw new UnsupportedOperationException();}public void print(){System.out.println("name:" + name + ",icon:" + icon);}
}
这样,不管是Branch
还是Leaf
,结构上它们都是相同的,只是各自的实现不一样而已。
5. 总结
组合模式在项目中随处可见:树形菜单、文件系统结构、XML结构等都是树形结构,都可以优先考虑使用组合模式来实现。
组合模式用于将多个对象组合成树形结构以表示「整体-部分」的结构层次,组合模式使得客户端对简单对象和复杂对象的使用具有一致性,方便了客户端的调用。
【转载】组合模式-系统菜单的设计相关推荐
- C++习题(系统菜单设计)
题目1: 系统菜单的设计 界面要求: 1. 要求每次进入各级菜单时清屏 2. "主菜单"标题:学生信息管理系统 3. 以"主菜单"形式显示内容 a) " ...
- 1、【设计模式】组合模式
java设计模式之组合模式 [学习难度:★★★☆☆,使用频率:★★★★☆] 树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式 ...
- 设计模式学习笔记(十一)-组合模式
树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题,组合模式通过一种巧妙的设计方案使得 ...
- 设计模式GOF23之-------------------结构型模式(适配器模式、代理模式、桥接模式、装饰模式、组合模式、外观模式、享元模式)
一 结构型模式 二 适配器模式 下面我将用代码模拟键盘usb接口和ps/2的转接器 的适配器过程: 首先定义客户需求: package GOF23;public interface Target {v ...
- 组合模式 -- 树形结构处理
树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题,组合模式通过一种巧妙的设计方案使得 ...
- java设计模式之组合模式(树形层级)
java设计模式之组合模式 学习难度:★★★☆☆,使用频率:★★★★☆] 树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式来 ...
- 深入浅出设计模式——组合模式(Composite Pattern)
模式动机 对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行.(递归调用) 由于 ...
- 组合模式的安全模式与透明模式
转载自 树形结构的处理--组合模式(四) 1 透明组合模式与安全组合模式 通过引入组合模式,Sunny公司设计的杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有类库代码,只需增加一个新的 ...
- 《Head First设计模式》第九章(2)组合模式
组合模式 基于前一篇迭代模式的案例进行需求更新,餐厅的菜单管理系统需要有煎饼屋菜单和披萨菜单.现在希望在披萨菜单中能够加上一份餐后甜点的子菜单. 在迭代模式中,披萨菜单是用数组维护的,我们需要让披 ...
最新文章
- (简要介绍)Winograd schema challenge(Winograd question)
- Xamarin Anroid开发教程之下载安装Xamarin
- python项目实例初学者-python 初学者必备14张思维导图
- java的或等于_Java中的“小于或等于”比较运算符是__________: !=|||=|=
- 工作260:js判断一个数组是否包含一个指定的值
- 【CodeForces - 438D】The Child and Sequence(线段树区间取模操作)
- java基础案例教程前4章知识点_java学习记录4 Java基础知识点
- 漂亮的页面过渡动画源码
- python小游戏贪吃蛇源码下载
- EasyUI TextBox的onkeypress事件
- ASPack 2.x (without poly) - Alexey Solodovnikov [Overlay]脱壳
- APP设计~切图那些事儿
- 选课系统软件功能测试报告,学生选课系统测试报告.doc
- html chm用浏览器打开方式,访问chm文件出现 已取消到该网页的导航的解决方法
- C语言编程练习 7.13个人围成一圈,从第1个人开始顺序报号1、2、3,凡报到3的人退出圈子。
- Python数据处理(三)——美国西雅图自行车流量可视化
- 在线文件不落地转base64
- 区块链上的随机性(一)概述与构造
- 激光雷达与深度相机对比——以RS-LIDAR-16和Realsense D455为例
- 1176: 【入门】买蛋糕