组合模式主要包含三种角色:
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

抽象根节点:
- //菜单组件:抽象根节点
- //不管是菜单还是菜单项,都应该继承该类
- public abstract class MenuComponent {
-
- protected String name;
- protected int level;
-
- public MenuComponent(String name, int level) {
- this.name = name;
- this.level = level;
- }
-
- //添加菜单
- public void add(MenuComponent menuComponent){
- throw new UnsupportedOperationException();
- }
-
- //移除菜单
- public void remove(MenuComponent menuComponent){
- throw new UnsupportedOperationException();
- }
-
- //获取指定的子菜单
- public MenuComponent getChild(int i){
- throw new UnsupportedOperationException();
- }
-
- //获取菜单名称
- public String getName(){
- return name;
- }
-
- public void print(){
- throw new UnsupportedOperationException();
- }
- }
树枝节点:
- import java.util.ArrayList;
- import java.util.List;
-
- //菜单:树枝节点
- public class Menu extends MenuComponent {
-
- private List
menuComponentList; -
- public Menu(String name, int level) {
- super(name, level);
- menuComponentList = new ArrayList
(); - }
-
- @Override
- public void add(MenuComponent menuComponent) {
- menuComponentList.add(menuComponent);
- }
-
- @Override
- public void remove(MenuComponent menuComponent) {
- menuComponentList.remove(menuComponent);
- }
-
- @Override
- public MenuComponent getChild(int i) {
- return menuComponentList.get(i);
- }
-
- @Override
- public void print() {
- for (int i = 1; i < level; i++) {
- System.out.print("--");
- }
- System.out.println(name);
- for (MenuComponent menuComponent : menuComponentList) {
- menuComponent.print();
- }
- }
- }
叶子节点:
- //菜单项:叶子节点
- public class MenuItem extends MenuComponent {
-
- public MenuItem(String name, int level) {
- super(name, level);
- }
-
- @Override
- public void print() {
- for (int i = 1; i < level; i++) {
- System.out.print("--");
- }
- System.out.println(name);
- }
- }
测试类:
- public class Client {
- public static void main(String[] args) {
- //创建菜单树
-
- //创建一级菜单
- MenuComponent component = new Menu("系统管理", 1);
- //创建二级菜单
- MenuComponent menu1 = new Menu("菜单管理", 2);
- component.add(menu1);
- //创建三级菜单
- 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);
- component.add(menu2);
- menu2.add(new MenuItem("页面访问", 3));
- menu2.add(new MenuItem("提交保存", 3));
-
- MenuComponent menu3 = new Menu("角色管理", 2);
- component.add(menu3);
- menu3.add(new MenuItem("页面访问", 3));
- menu3.add(new MenuItem("新增角色", 3));
- menu3.add(new MenuItem("修改角色", 3));
-
- //打印菜单
- component.print();
- }
- }
在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 add、remove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
享元(Flyweight )模式中存在以下两种状态:
内部状态,即不会随着环境的改变而改变的可共享部分。
外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
享元模式的主要有以下角色:
抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
抽象享元角色:
- //抽象享元
- public abstract class AbstractBox {
-
- public abstract String getShape();
-
- public void display(String color) {//外部状态
- System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
- }
- }
具体享元:
- //具体享元
- public class IBox extends AbstractBox {
- @Override
- public String getShape() {
- return "I";
- }
- }
-
- //具体享元
- public class LBox extends AbstractBox {
-
- @Override
- public String getShape() {
- return "L";
- }
- }
-
- //具体享元
- public class OBox extends AbstractBox {
-
- @Override
- public String getShape() {
- return "O";
- }
- }
享元工厂:
- import java.util.HashMap;
-
- //享元工厂
- public class BoxFactory {
-
- private static HashMap
map; -
- //单例:构造方法私有
- private BoxFactory() {
- map = new HashMap
(); - AbstractBox iBox = new IBox();
- AbstractBox lBox = new LBox();
- AbstractBox oBox = new OBox();
- map.put("I", iBox);
- map.put("L", lBox);
- map.put("O", oBox);
- }
-
- //单例:外部访问接口
- public static final BoxFactory getInstance() {
- return SingletonHolder.INSTANCE;
- }
-
- //单例:静态内部类
- private static class SingletonHolder {
- private static final BoxFactory INSTANCE = new BoxFactory();
- }
-
- public AbstractBox getBox(String key) {
- return map.get(key);
- }
- }
测试类:
- public class Client {
- public static void main(String[] args) {
- //获取享元工厂对象
- BoxFactory boxFactory = BoxFactory.getInstance();
-
- //获取I图形对象
- AbstractBox IBox = boxFactory.getBox("I");
- IBox.display("red");
-
- //获取L图形对象
- AbstractBox LBox = boxFactory.getBox("L");
- LBox.display("yellow");
-
- //获取O图形对象
- AbstractBox Obox = boxFactory.getBox("O");
- Obox.display("green");
- AbstractBox Obox2 = boxFactory.getBox("O");
- Obox2.display("orange");
- System.out.println(Obox == Obox2);//true
-
- }
- }
1,优点
极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态
2,缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
一个系统有大量相同或者相似的对象,造成内存的大量耗费。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
- public class Demo {
- public static void main(String[] args) {
- Integer i1 = 127;
- Integer i2 = 127;
- System.out.println(i1 == i2);//true
-
- Integer i3 = 128;
- Integer i4 = 128;
- System.out.println(i3 == i4);//false
- }
- }
反编译后:
- public class Demo {
- public static void main(String[] args) {
- Integer i1 = Integer.valueOf((int)127);
- Integer i2 Integer.valueOf((int)127);
- System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
- Integer i3 = Integer.valueOf((int)128);
- Integer i4 = Integer.valueOf((int)128);
- System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
- }
- }
Integer中valueOf(int i)的源码:
- public final class Integer extends Number implements Comparable
{ -
- public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];//根据数组索引拿值
- return new Integer(i);
- }
-
- private static class IntegerCache {
- static final int low = -128;
- static final int high;//静态代码块中赋值为127
- static final Integer cache[];
-
- static {
- int h = 127;
- String integerCacheHighPropValue =
- sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
- if (integerCacheHighPropValue != null) {
- try {
- int i = parseInt(integerCacheHighPropValue);
- i = Math.max(i, 127);
- // Maximum array size is Integer.MAX_VALUE
- h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
- } catch( NumberFormatException nfe) {
- }
- }
- high = h;
- cache = new Integer[(high - low) + 1];
- int j = low;
- for(int k = 0; k < cache.length; k++)
- cache[k] = new Integer(j++);
- // range [-128, 127] must be interned (JLS7 5.1.7)
- assert IntegerCache.high >= 127;
- }
-
- private IntegerCache() {}
- }
- }
可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。