测试
package com.jsxs.structure.flyweight; /** * @Author Jsxs * @Date 2023/4/22 15:00 * @PackageName:com.jsxs.structure.flyweight * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { // 获取工厂对象 BoxFactory factory = BoxFactory.getInstance(); // 获取指定图形 AbstractBox i = factory.getShape("I"); // 打印指定颜色图形 i.display("红色"); // 获取工厂对象 BoxFactory factory1 = BoxFactory.getInstance(); // 获取指定图形 AbstractBox i1 = factory1.getShape("I"); // 打印指定颜色图形 i1.display("蓝色"); System.out.println(i==i1); } }
(6).优缺点和使用场景
优点:
极大的减少内存中像是或相同对象数量
,节约系统资源,提供系统性能。享元模式中的外部状态相对独立,且不影响内部状态
。
缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,是程序逻辑复杂。
使用场景:
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费
- 对象的大部分状态可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池(HashMap),而这些需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
7.组合模式
(1).概述
上面是一个文件系统,也就是目录结构。对于这种的结构我们称之为数型构
。在树结构中可以通过调用某一个方法来遍历整个树,当我们找到某一个叶子节点后,就可以对叶子节点进行相关操作。可以将这课树比喻成一个大的容器,容器里面包含很多的成员对象,这些成员对象既可以是容器对象也可以是叶子对象
。但是由于容器和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象
和叶子对象
,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象(文件夹)
和叶子对象(文件)
。
定义:
又名部分整体模式
,是用于把一组相似的对象当作一个的单一的对象,组合模式依据属性结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,他创建了对象组的树形结构
。
(2).结构
组合模式主要包含三种角色:
- 抽象根节点: 定义系统各层次对象的公有方法和属性,可以预先定义一些默认行为和属性
- 树枝结点(文件夹): 定义树枝节点的行为,存储子节点,组合树子结点形成一个树形结构
- 叶子节点(文件): 叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
(3).案列实现
eg: 软件菜单
我们在访问别的一些管理系统时,经常可以看到类似的菜单,一个菜单可以包含菜单项
(菜单项是指不在包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单
,因此使用组合模式描述菜单就很恰当,当我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
抽象根节点
package com.jsxs.structure.combination; /** * @Author Jsxs * @Date 2023/4/22 12:56 * @PackageName:com.jsxs.structure.combination * @ClassName: MenuComponent * @Description: TODO 抽象根节点 * @Version 1.0 */ public abstract class MenuComponent { // 菜单组件的名称 protected String name; // 菜单组件的层级 protected int level; // 添加子菜单 public void add(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } // 移除子菜单 public void remove(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } // 获取指定的子菜单 public MenuComponent getChild(int index){ throw new UnsupportedOperationException(); } // 获取菜单或者菜单项的名称 public String getName(){ return name; } // 打印菜单名称的方法 public abstract void print(); }
树枝节点
package com.jsxs.structure.combination; import java.util.ArrayList; import java.util.List; /** * @Author Jsxs * @Date 2023/4/22 13:17 * @PackageName:com.jsxs.structure.combination * @ClassName: Menu * @Description: TODO 菜单栏、属于树子结点 * @Version 1.0 */ public class Menu extends MenuComponent{ // 菜单可以有多个子菜单或者子菜单项 private List<MenuComponent> menuComponentList=new ArrayList<>(); // 构造方法 public Menu(String name,int leve){ this.name=name; this.level=leve; } @Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(int index) { return menuComponentList.get(index); } @Override public void print() { // 打印菜单名称 for (int i = 0; i < level; i++) { System.out.print("-"); } System.out.println(name); // 打印子菜单名称 for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
叶子节点
package com.jsxs.structure.combination; /** * @Author Jsxs * @Date 2023/4/22 13:24 * @PackageName:com.jsxs.structure.combination * @ClassName: MenuUtem 菜单项: 叶子节点 * @Description: TODO * @Version 1.0 */ public class MenuItem extends MenuComponent{ public MenuItem(String name,int level){ this.name=name; this.level=level; } @Override public void print() { // 打印菜单项的名称 for (int i = 0; i < level; i++) { System.out.print("-"); } System.out.println(name); } }
测试:
package com.jsxs.structure.combination; /** * @Author Jsxs * @Date 2023/4/22 13:26 * @PackageName:com.jsxs.structure.combination * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { // 创建菜单树 MenuComponent menu1 = new Menu("菜单管理", 2); menu1.add(new MenuItem("页面访问",3)); menu1.add(new MenuItem("展开菜单",3)); menu1.add(new MenuItem("编辑菜单",3)); menu1.add(new MenuItem("删除菜单",3)); menu1.add(new MenuItem("新增菜单",3)); MenuComponent menu2 = new Menu("权限管理", 2); menu2.add(new MenuItem("提交保存",3)); menu2.add(new MenuItem("展开菜单",3)); MenuComponent menu3 = new Menu("角色管理", 2); menu3.add(new MenuItem("页面访问",3)); menu3.add(new MenuItem("新增角色",3)); // 创建一级菜单 MenuComponent RootMenu = new Menu("windows系统",1); // 将二级系统添加到一级菜单中 RootMenu.add(menu1); RootMenu.add(menu2); RootMenu.add(menu3); // 打印菜单名称(如果有子菜单一起打印) RootMenu.print(); } }
(4).组合模式的分类
在使用组合模式时,根据抽象构建类的定义形式,我们可以将组合模式分为透明组合和安全组合模式两种形式。
透明组合模式
:
透明组和模式中,抽象根节点角色声明了所有用于管理成员对象的方法,比如在实列中:MenuComponent
声明了add、remove、getChild
方法,这样做的好处是确保所有的构建类都有相同的接口
。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象不可能有下一个层次的对象,既不可能包含成员对象,因此为其提高add()、remove()、等方法时没有意义的,这在编译阶段不会出错,但在运行阶段,如果调用这些方法可能会出错。安全组合模式
在安全组合模式中,在抽象构建角色中没有声明任何用于管理成员对象的方法,而是树枝节点Menu
类中声明并实现这些方法。安全组合模式的缺点是不够透明
,因为叶子构建和容器构建具有不同的放啊,且容器构件中那些用于管理成员对象饿方法没有在抽象构建类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构建和容器构建。
(5).使用场景
优点:
- 组合模式可以清除地定义分层次地复杂对象,表示对象地全部或部分层次,
它让客户端忽略了层次地差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关于处理地是单个对象还是整个组合结构,
简化客户端代码
。 - 在组合模式中增加新地树枝节点和叶子节点都很方便,无需对现有类库进行任何修改,
符合"开闭原则"
。 - 组合模式为
树形结构地面向对象实现
提供了一种灵活地解决方案,通过叶子节点和树枝节点地递归组合,可以形成复杂地树形结构,但对属性结构地控制却非常简单。
组合模式正是因为树形结构而生,所以组合模式地使用场景就是出现属性结构的地方。比如: 文件目录显示,多级目录呈现等树形结构数据的操作。
(7).JDK源码分析
Integer类使用了享元模式。
package com.jsxs.structure.flyweight; /** * @Author Jsxs * @Date 2023/4/22 15:00 * @PackageName:com.jsxs.structure.flyweight * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Integer i1=127; Integer i2=127; System.out.println(i1==i2); Integer i3=128; Integer i4=128; System.out.println(i3==i4); } }
结论: -128~127 是一个对象。否则创建一个新的对象。