【转载】组合模式-系统菜单的设计

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,一棵菜单树构建出来了,客户端也能进行遍历了,但是会发现代码实现的特别别扭啊,一点也不优雅。BranchLeaf存在冗余属性和代码啊,不管是目录还是菜单,nameicon都是有的,应该提取出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;
	}

	@Override
	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(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. 组合模式的优缺点

优点

  1. 高层模块调用简单,整棵树中所有的节点都是Component抽象,高层模块不关心自己处理的是简单对象还是复杂对象,统一了客户端处理节点的方式。
  2. 屏蔽了节点实现细节对高层模块的影响,可以随时加入新的节点。
  3. 节点可以自由增加,扩展性高。

缺点
仔细看看上面的代码,客户端在遍历树时,直接使用了实现类,这违反了「依赖倒置原则」。

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结构等都是树形结构,都可以优先考虑使用组合模式来实现。
组合模式用于将多个对象组合成树形结构以表示「整体-部分」的结构层次,组合模式使得客户端对简单对象和复杂对象的使用具有一致性,方便了客户端的调用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EXCEL实用技巧系列 .中国教程网zhangditony原创(或翻译),转载请保留此信息. 一。将单元格复制成图片: 1 选择区域 2 按住SHIFT不放,点击编辑/复制图片 3 按自己的要求在弹出的对话框里选择 4 确定 二。如何使用照相机 你可以在菜单栏上单击右键,选择自定义,然后在命令-工具-照相机,将照相机拖动到工具栏,可以用他来拍你所想要拍的东西。 三。将以“元”为单位的数变为以“万元”为单位 只需在一单元格中输入10000,复制,再选定转换范围,右击“选择性粘贴”-数值(复选 “除”)即可! 四.多个单元格输入同一数据 想要在多个单元格输入同一数据,先选中所需填写的单元格,然后用键盘输入,最后用Ctrl+Enter即可 五。隐藏单元格内容的方法 1。设置单元格格式为“;;;”(三个分号) 2。设置单元格格式-〉保护,把隐藏钩上。 3。保护工作表 这样无论在单元格里还是在编辑框里都看不见单元格内容了。 六。巧用Excel状态栏 右键点击状态栏,可以选择均值,计数,计数值(只计包含数值的单元格),最大值,最小值或求和。当你在工作表中选定单元格后,在状态栏就会出现相应的提示。 七。快速粘贴 复制所选单元格内容后,再选择目标单元格,再直接回车即可实现快速粘贴! 八。快速缩放显示比例 如果鼠标是带滚轮的,在excel中按住ctrl+向上滚轮,显示比例就会放大,向下滚轮则比例会缩小。 [To quickly increase or decrease the screen magnification percentage: 1. Select a cell. 2. Press Ctrl, and roll the mouse wheel forward (to increase the magnification) or backward (to decrease it).] 九。输入公式的两种方法 1.常规输入:”=“+内容 2.不用”=“,在公式前加”+“号或"-"号,例如 -27 + 14 ;+B2 + B3 (There is one other way that you can enter formulas into Excel: through the use of an implied equal sign. You do this by prefacing the formula with a plus sign or a minus sign) 十。剪贴板的使用1.按编辑-OFFICE剪贴板 (From the edit menu, select office Clipboard) 2.按ctrl+c+c 键,打开剪贴板,就可以管理并使用以前复制过的信息(use the keyboard shortcut Ctrl+C+C to open the Task Pane dialog box, which contains the copied ranges (up to 24) that have been saved to memory.) 十一。隐藏分类汇总分级按钮(Hiding Subtotal Level Buttons) 隐藏时按 Ctrl+8.重新显示时再按Ctrl+8(To hide the Subtotal level buttons:Press Ctrl+8. To redisplay the Subtotal level buttons:Press Ctrl+8 again.) 十二。快速删除分类汇总(To quickly remove subtotals:) 方法一 1.选择分类汇总区域中的任一单元格,后选数据-排序( Select any cell in the List and click Sort Ascending or Sort Descending.) 2. 点确定(Click OK in the dialog message box.) 方法二 1. 选数据-分类汇总(From the Data menu, select Subtotals). 2. 点全部删除(Click Remove All). 十三.快速选定不连续单元格 按下组合键“Shift+F8”,激活“添加选定”模式,此时工作簿下方的状态栏中会显示出“添加”字样,以后分别单击不连续的单元格或单元格区域即可选定,而不必按住Ctrl键不放。 十四.奇特的F4键 Excel中有一个快捷键的作用极其突出,那就是F4键。作为“重复”键,F4键可以重复前一次操作,节省了选择菜单的时间。 十五。打印图表(忽略其他数据)选择要打印的图表,然后选“文件”-页面设置-图表,选其中的“打印预阅”即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值