设计模式(三)结构型模式介绍及实例

文章目录

  本系列文章共分为六篇:
    设计模式(一)设计模式的分类与区别
    设计模式(二)创建型模式介绍及实例
    设计模式(三)结构型模式介绍及实例
    设计模式(四)行为型模式介绍及实例(上)
    设计模式(五)行为型模式介绍及实例(下)
    设计模式(六)设计模式的常见应用

  上篇文件介绍了创建型模式,本篇将介绍结构型模式。结构型模式的主要关注点是“如何将类或对象按某种布局组成更大的结构”。

一、适配器模式

1.1 适配器模式定义

  将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式对象结构型模式两种,前者之间的耦合度比后者高,且要求开发者了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

一句话总结:在开发者调用的接口和现有接口不一致时,增加一个”转换接口”。

1.2 适配器模式主要角色

  对象适配器模式用的较多一些,其UML图示如下:
《设计模式(三)结构型模式介绍及实例》
  在该图中,Target是客户所期待的接口,目标可以是具体或抽象的类,也可以是接口;Adaptee是需要适配的类;Adapter通过在内部包装一个Adaptee对象,把源接口转换成目标接口。

  注意事项:详细设计阶段不要考虑使用适配器模式,使用主要场景为扩展应用中。

1.3 适配器模式特点

优点

  1. 客户端通过适配器可以透明地调用目标接口。
  2. 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
  3. 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

缺点

  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。

1.4 适配器模式实现方式

  适配器模式的实现方式较多,主要方式如下:

1.4.1 类适配器模式

  该模式是通过类的继承来实现适配器功能,是较为简单的一种适配器模式。以要充电转接口为例,某个电子产品的原始充电器是2口的,但电源是3口的,此时就需要一个3口转2口的适配器。以代码示例如下:

/*目标接口,使用二口数据线充电*/
public interface TwoSocket { 
	public void charge();
}
/*适配者接口,在三口电源座上充电*/
public class ThreeSocket { 
    public void powerOn(){        
        System.out.println("在三口电源座上充电");
    }
}
/*适配器类,充当转接口*/
public class SocketAdapter extends ThreeSocket implements TwoSocket{ 
	public void charge() { 
		System.out.println("使用二口电源线连接电子产品,然后用二口电源线连接三口转两口适配器");
		powerOn();		
	}
}
/*测试类*/
public class AdapterTest { 
    public static void main(String[] args){ 
    	TwoSocket twoSocket = new SocketAdapter();
    	twoSocket.charge();
    }
}
1.4.2 对象适配器模式

  对象适配器目标接口和适配者接口与类适配器模式中的相同,不同的是适配器类和测试类。在适配器类中,不再继承适配者接口,而是从调用方传一个适配者接口对象的参数,其差异代码示例如下:

/*适配器类,充当转接口*/
public class SocketAdapter implements TwoSocket{ 
    private ThreeSocket threeSocket;
    public SocketAdapter(ThreeSocket threeSocket){ 
        this.threeSocket=threeSocket;
    }
    public void charge(){ 
    	System.out.println("使用二口电源线连接电子产品,然后用二口电源线连接三口转两口适配器");
    	threeSocket.powerOn();
    }
}
/*测试类*/
public class AdapterTest { 
    public static void main(String[] args){ 
    	ThreeSocket threeSocket = new ThreeSocket();
    	TwoSocket target = new SocketAdapter(threeSocket);
        target.charge();
    }
}

  两种适配器模式的输出结果是一样的,如下:

使用二口电源线连接电子产品,然后用二口电源线连接三口转两口适配器
在三口电源座上充电

1.5 适配器模式应用场景

  1. 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  2. 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同

二、桥接模式

2.1 桥接模式定义

  将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
  常见的方式是:用抽象类和接口两种方式来定义不同维度,然后用他们的继承类(或实现类)来进行组合,从而达到多种组合的目的。

一句话总结:用抽象类和接口定义两个可变维度,分别对其进行不同的实现,从而组成多种不同对象。

2.2 桥接模式主要角色

  桥接模式UML图示如下:
《设计模式(三)结构型模式介绍及实例》

2.3 桥接模式特点

优点

  1. 由于抽象与实现分离,所以扩展能力强;
  2. 其实现细节对客户透明。

缺点

  1. 聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,增加了系统的理解与设计难度。

2.4 桥接模式实现方式

  桥接模式的意义在于将系统中不同维度的功能分开来实现,降低耦合性,示例代码如下:

/*实现化角色:颜色*/
public interface Color{ 
    String getColor();
}
/*具体实现化角色:绿色*/
class Green implements Color{ 
    public String getColor(){    return "green";  }
}
/*具体实现化角色:红色*/
public class Red implements Color{ 
    public String getColor(){    return "red";   }
}
/*抽象化角色:包*/
public abstract class Bag{ 
    protected Color color;
    public void setColor(Color color){ 
        this.color=color;
    }   
    public abstract String getName();
}
/*扩展抽象化角色:双肩包*/
public class Knapsack extends Bag{ 
    public String getName(){ 
        return color.getColor()+" Knapsack";
    }   
}
/*扩展抽象化角色:钱包*/
public class Wallet extends Bag{ 
    public String getName(){ 
        return color.getColor()+" Wallet";
    }   
}
/*测试类*/
public class BridgeTest { 
    public static void main(String[] args){ 
    	Color color = new Red();
        Bag bag=new Knapsack();
        bag.setColor(color);
        System.out.println(bag.getName());
    	color = new Green();
        bag=new Knapsack();
        bag.setColor(color);
        System.out.println(bag.getName());
    	color = new Red();
        bag=new Wallet();
        bag.setColor(color);
        System.out.println(bag.getName());
    	color = new Green();
        bag=new Wallet();
        bag.setColor(color);
        System.out.println(bag.getName());        
    }
}

  结果示例如下:

red Knapsack
green Knapsack
red Wallet
green Wallet

2.5 桥接模式应用场景

  1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
  4. 不希望或不适用使用继承的场景。
  5. 接口或抽象类不稳定的场景。
  6. 重用性要求较高的场景。

  注意:发现类的继承有 N 层时,可以考虑使用桥梁模式。桥梁模式主要考虑如何拆分抽象和实现。

三、组合模式

3.1 组合模式定义

  又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性

一句话总结:定义一个接口,让树枝对象和树叶对象都实现该接口,并具有不同的实现,进而表现整体与部分的关系。

3.2 组合模式主要角色

   组合模式UML图示如下:
《设计模式(三)结构型模式介绍及实例》
  Component为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为;Left表示叶对象节点,叶结点没有子节点;Composite定义枝节点行为,用来存储子部件。
  上述方式实现的组合模式称为透明方式,该方式中,在Component中声明了所有管理子对象的方法,其中包括add、remove等。这样实现Component接口的所有子对象都具备了add和remove,这样做的好处是叶结点也枝节点对于外界没有区别,他们具备完全一致的行为接口。但是问题也很明显,因为Left类不具备add、remove方法的功能,所以实现他们也是没有意义的。
  要克服上面的问题,可以采用安全方式,也就是在Component接口中不声明add和remove方法。不过由于不够透明,所以树叶和树枝具有不同的接口,客户端的调用需要做相应的判断,会带来一些不便。

  当发现需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象和单个对象的不同,统一地使用组合结构中的所有对象时,就可以考虑使用组合模式。

3.3 组合模式特点

优点

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”。

缺点

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能。

3.4 组合模式实现方式

  组合模式分为透明式的组合模式安全式的组合模式

3.4.1 透明方式

  在透明方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常)。
  以检查班级里有多少人为例,老师为树枝节点,学生为叶子节点,老师可以在自己的班级中添加、删减学生。该例子代码如下:

/*抽象接口*/
public interface  ClassPeople { 
    public void add(ClassPeople sp);
    public void remove(ClassPeople sp);
    public ClassPeople getChild(int i);
    public void check();
}
/*老师*/
public class Teacher implements ClassPeople { 
    private ArrayList<ClassPeople> children=new ArrayList<ClassPeople>(); 
    private String name;
    public Teacher(String name){ 
        this.name=name;
    }
	public void add(ClassPeople sp) { 
		children.add(sp);
	}
	public void remove(ClassPeople sp) { 
		children.remove(sp);
	}
	public ClassPeople getChild(int i) { 
		return children.get(i);
	}
	public void check() { 
		System.out.println(name+" Teacher");
        for(Object obj:children){ 
            ((ClassPeople)obj).check();
        }
	}
}
/*学生*/
public class Student implements ClassPeople { 
    private String name;
    public Student(String name){ 
        this.name=name;
    }
	public void add(ClassPeople sp) { }
	public void remove(ClassPeople sp) { }
	public ClassPeople getChild(int i) { 	return null;  }
	public void check() { 
		System.out.println(name+" Student");
	}
}
/*测试类*/
public class ComponentTest { 
    public static void main(String[] args) {  
       ClassPeople sp1 = new Teacher("Jack");
       ClassPeople sp2 = new Student("Marry");
       ClassPeople sp3 = new Student("John");
	   sp1.add(sp2);
	   sp1.add(sp3);
	   sp1.check();
    }
}
3.4.2 安全方式

  在安全方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性
  仍以上面的老师学生为例子,抽象接口中不再具有add、remove、getChild方法,而是在Teacher类中实现,具体代码如下:

/*抽象接口*/
public interface  ClassPeople { 
    public void check();
}
/*老师*/
public class Teacher implements ClassPeople { 
    private ArrayList<ClassPeople> children=new ArrayList<ClassPeople>(); 
    private String name;
    public Teacher(String name){ 
        this.name=name;
    }
	public void add(ClassPeople sp) { 
		children.add(sp);
	}
	public void remove(ClassPeople sp) { 
		children.remove(sp);
	}
	public ClassPeople getChild(int i) { 
		return children.get(i);
	}
	public void check() { 
		System.out.println(name+" Teacher");
        for(Object obj:children){ 
            ((ClassPeople)obj).check();
        }
	}
}
/*学生*/
public class Student implements ClassPeople { 
    private String name;
    public Student(String name){ 
        this.name=name;
    }
	public void check() { 
		System.out.println(name+" Student");
	}
}
/*测试类*/
public class ComponentTest { 
    public static void main(String[] args) {  
       Teacher sp1 = new Teacher("Jack");
       ClassPeople sp2 = new Student("Marry");
       ClassPeople sp3 = new Student("John");
	   sp1.add(sp2);
	   sp1.add(sp3);
	   sp1.check();
    }
}

  以上两种方式输出结果相同,如下:

Jack Teacher
Marry Student
John Student

3.5 组合模式应用场景

  1. 在需要表示一个对象整体与部分的层次结构的场合。
  2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
  3. 只要是树形结构,就考虑使用组合模式。

四、装饰器模式

4.1 装饰器模式定义

  在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

一句话总结:不改变现有对象的结构,动态地增加一些功能。

4.2 装饰器模式主要角色

  装饰模式UML类图:
《设计模式(三)结构型模式介绍及实例》
  Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体组件,也可以给这个对象添加一些职责。Decorator是装饰抽象类,集成了Component,从外部类来扩展Component类的功能,但对于Component来说,是无序只知道Decorator来说,是无需知道Decorator的存在的。ConcreteDecorator是具体的装饰对象,起到给Component添加职责的功能。
  在具体实现上述功能的时候,在Decorator类中,有setComponent的方法,该方法用来对对象进行包装。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能。
  当然,如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,可以把Decorator和ConcreteDecorator的责任合并成一个类。

4.3 装饰器模式特点

优点

  1. 采用装饰模式扩展对象的功能比采用继承方式更加灵活
  2. 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

缺点

  1. 增加了许多子类,如果过度使用会使程序变得很复杂。

4.4 装饰器模式实现方式

  以学生为例,进行体育运动时穿运动装,上课时转校服,示例代码如下:

/*抽象构件角色*/
public interface Student { 
	public void act();
}
/*具体构件角色*/
public class Jack implements Student{ 
	public Jack(){ }
	public void act() { 
		System.out.println("Jack开始做事");
	}
}
/*抽象装饰角色*/
public class Decorator implements Student{ 
    private Student student;   
    public Decorator(Student student){ 
        this.student=student;
    }   
    public void act(){ 
    	student.act();
    }
}
/*具体装饰角色1:运动员*/
public class Player extends  Decorator{ 
	public Player(Student student) { 
		super(student);
	}
    public void act(){ 
        super.act();
        addedFunction();
    }
    public void addedFunction(){ 
        System.out.println("要去打篮球,穿运动服");           
    }
}
/*具体装饰角色2:上课的人*/
public class Learner extends  Decorator{ 
	public Learner(Student student) { 
		super(student);
	}
    public void act(){ 
        super.act();
        addedFunction();
    }
    public void addedFunction(){ 
        System.out.println("要去上课,穿校服");           
    }
}
/*测试类*/
public class DecoratorTest { 
    public static void main(String[] args) {  
    	Student student = new Jack();
    	Student player = new Player(student);
    	player.act();
    	Student learner = new Learner(student);
    	learner.act();
     }
}

  测试结果如下:

Jack开始做事
要去打篮球,穿运动服
Jack开始做事
要去上课,穿校服

4.5 装饰器模式应用场景

  1. 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。
  2. 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
  3. 对象的功能要求可以动态地添加,也可以再动态地撤销时。

五、外观模式

5.1 外观模式定义

  一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。

一句话总结:在复杂的系统上,提供一个对外的一致性接口(外观)。

5.2 外观模式主要角色

  外观模式UML图示:
《设计模式(三)结构型模式介绍及实例》

  1. 外观角色
     为多个子系统对外提供一个共同的接口。
  2. 子系统角色
     实现系统的部分功能,客户可以通过外观角色访问它。
  3. 客户角色
     通过一个外观角色访问各个子系统的功能。

  注意:

  1. 一个子系统可以有多个外观;
  2. 外观不参与子系统内的业务逻辑。

5.3 外观模式特点

优点

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点

  1. 不能很好地限制客户使用子系统类。
  2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

5.4 外观模式实现方式

  以上班族为例,一个人早上起床后,去上班前要做一系列的事情:穿衣、洗漱、坐公交等,示例代码如下:

/*子系统1*/
public class Step1 { 
    public void executeStep(){ 
    	System.out.println("穿衣服");  
    }
}
/*子系统2*/
public class Step2 { 
    public void executeStep(){ 
    	System.out.println("洗漱");  
    }
}
/*子系统3*/
public class Step3 { 
    public void executeStep(){ 
    	System.out.println("坐公交");  
    }
}
/*外观*/
public class Employee { 
	public Employee(){ 
		System.out.println("一个人上班的流程:"); 
	}
    Step1 step1 = new Step1();
    Step2 step2 = new Step2();
    Step3 step3 = new Step3();
    public void work(){ 
     	step1.executeStep();
        step2.executeStep();
        step3.executeStep();
    }
}
/*测试类,也是客户*/
public class FacadeTest { 
    public static void main(String[] args) {  
    	Employee employee = new Employee();
    	employee.work();
    }
}

  结果如下:

一个人上班的流程:
穿衣服
洗漱
坐公交

5.5 外观模式应用场景

  1. 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  2. 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  3. 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

 换种说法:
  首先,在设计初期阶段,应该要有意识地将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的借款,使得耦合性大大降低。
  其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式适用也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖
  第三,在维护一个遗留的大型系统时,可能这个系统以及非常难维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖它,此时也可以使用外观模式。比如为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较简单清晰的接口,让新系统和Facade对象交互,Facade与遗留代码交互所有复杂的工作。

六、享元模式

6.1 享元模式定义

  运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

一句话总结:复用需要大量使用的、功能较为简单的对象。

6.2 享元模式主要角色

  享元模式:运用共享技术有效抵支持大量细颗粒度的对象,其UML图示如下:
《设计模式(三)结构型模式介绍及实例》
  FlyWeight类,它是所有具体享元类的超类或接口,通过这个接口,FlyWeight可以接受并作用于外部状态。
  ConcreteFlyWeight,是继承FlyWeight超类或实现FlyWeight接口,并为内部状态增加存储空间。
  UnsharedConcreteFlyWeight,是指那些不需要共享的FlyWeight子类,因为FlyWeight接口共享成为可能,但它并不强制共享。
  FlyWeightFactory,是个享元工厂,用来创建并管理FlyWeight对象。它主要是用来确保合理地共享FlyWeight,当用户请求一个FlyWeight时,FlyWeightFactory对象提供一个已创建的实例或创建一个(如果不存在的话)。
  如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销,就可以考虑使用享元模式。

6.3 享元模式特点

优点

  1. 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点

  1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
  2. 读取享元模式的外部状态会使得运行时间稍微变长。

6.4 享元模式实现方式

  此处以学生写字为例,有时需要用蓝色笔写字,有时需要用黑色笔写字,第一次写某颜色的字时,在文具盒中找出相应的笔,下次不用再从哪个文具盒中找,直接使用即可。示例代码如下:

/*非享元角色*/
public class PenInfo { 
    private String info;
    PenInfo(String info){ 
        this.info=info;
    }
    public String getInfo(){ 
        return info;
    }
    public void setInfo(String info){ 
        this.info=info;
    }
}
/*抽象享元角色*/
public interface  Pen { 
	public void operation(PenInfo state);
}
/*具体享元角色1*/
public class BluePen implements Pen{ 
    private String key;
    BluePen(String key){ 
        this.key=key;
        System.out.println(key+"颜色的笔从文具盒中找出!");
    }
    public void operation(PenInfo outState){ 
        System.out.print("具体享元"+key+"被调用,");
        System.out.println("非享元信息是:"+outState.getInfo());
    }
}
/*具体享元角色2*/
public class BlackPen implements Pen{ 
    private String key;
    BlackPen(String key){ 
        this.key=key;
        System.out.println(key+"颜色的笔从文具盒中找出!");
    }
    public void operation(PenInfo outState){ 
        System.out.print("具体享元"+key+"被调用,");
        System.out.println("非享元信息是:"+outState.getInfo());
    }
}
/*享元工厂角色*/
public class PenFactory { 
    private HashMap<String, Pen> pens=new HashMap<String, Pen>();
    public Pen getPen(String key){ 
    	Pen pen=(Pen)pens.get(key);
        if(pen!=null){ 
            System.out.println(key+"颜色的笔已经存在,可直接使用!");
        }else{ 
        	if("blue".equals(key)){ 
               pen=(Pen) new BluePen(key);
            }else{ 
               pen=(Pen) new BlackPen(key);
            }  		
        	pens.put(key, pen);
        }
        return pen;
    }
}
/*测试类*/
public class PenTest { 
    public static void main(String[] args){ 
    	PenFactory factory=new PenFactory();
        Pen pen1=factory.getPen("blue");
        Pen pen2=factory.getPen("blue");
        Pen pen3=factory.getPen("blue");
        Pen pen4=factory.getPen("black");
        Pen pen5=factory.getPen("black");       
        pen1.operation(new PenInfo("第1次使用blue笔"));       
        pen2.operation(new PenInfo("第2次使用blue笔"));       
        pen3.operation(new PenInfo("第3次使用blue笔"));       
        pen4.operation(new PenInfo("第1次使用black笔"));       
        pen5.operation(new PenInfo("第2次使用black笔"));
    }
}

  测试结果如下:

blue颜色的笔从文具盒中找出!
blue颜色的笔已经存在,可直接使用!
blue颜色的笔已经存在,可直接使用!
black颜色的笔从文具盒中找出!
black颜色的笔已经存在,可直接使用!
具体享元blue被调用,非享元信息是:第1次使用blue笔
具体享元blue被调用,非享元信息是:第2次使用blue笔
具体享元blue被调用,非享元信息是:第3次使用blue笔
具体享元black被调用,非享元信息是:第1次使用black笔
具体享元black被调用,非享元信息是:第2次使用black笔

6.5 享元模式应用场景

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
  4. 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对
    象没有特定身份。
  5. 需要缓冲池的场景。

  注意:

  1>享元模式是线程不安全的,只有依靠经验,在需要的地方考虑一下线程安全,在大部分场景下不用考虑。对象池中的享元对象尽量多,多到足够满足为止。
  2>性能安全:外部状态最好以 java 的基本类型作为标志,如 String,int,可以提高效率。

七、代理模式

7.1 代理模式定义

  由于某些原因需要给某对象提供一个代理以控制对该对象的访问

一句话总结:用代理对象来控制对原有对象的访问权限。

7.2 代理模式主要角色

  代理模式:为其他对象提供一种代理以控制对这个对象的访问。 代理模式的UML类图如下:
《设计模式(三)结构型模式介绍及实例》
  在上面的图中,Subject类中,定义了RealSubject和Proxy的公用接口,这样就可以在任何使用RealSubject的地方使用Proxy。

7.3 代理模式特点

优点

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  2. 代理对象可以扩展目标对象的功能
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点

  1. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。
  2. 增加了系统的复杂度。

7.4 代理模式实现方式

  代码模式有两种实现方式:静态代理动态代理

7.4.1 静态代理

  静态代理可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口(常见),或者是共同继承某个类。比如要买电影票,但又不想去电影院买,这时就可以通过购票APP等第三方”代理”来购买。示例代码如下:

/*抽象主题类,电影*/
public interface Movie { 
    public void buyMovieTicket();
}
/*接下来的真实主题类和代理类的例子不太好,真实主题类应该是 某个具体买票的人或群体,TaoPiaoPiao作为代理类 */
/*真实主题类,第三方购票APP*/
public class TaoPiaoPiao implements Movie { 
	public void buyMovieTicket() { 	
		System.out.println("淘票票帮您购买电影票"); 
	}
}
/*代理类,通过第三方APP购票*/
public class Proxy implements Movie{ 
	private TaoPiaoPiao movie;
	public void buyMovieTicket() { 
		if(movie == null){ 
		    movie=new TaoPiaoPiao();
		}
		System.out.println("登录淘票票"); 
		movie.buyMovieTicket();
		System.out.println("淘票票向你推荐爆米花、可乐");
	}
}
/*测试类*/
public class ProxyTest { 
	public static void main(String[] args) {  
		Proxy proxy=new Proxy();
        proxy.buyMovieTicket();
	}
}

  示例结果如下:

登录淘票票
淘票票帮您购买电影票
淘票票向你推荐爆米花、可乐

7.4.2 动态代理

  动态代理代理依靠反射来实现,代理类在JVM运行时动态生成,而不是编译期就能确定。
  动态代理技术就是用来产生一个对象的代理对象的。 使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过代理类来调用目标方法,代理类会将所有的方法调用,分派到目标对象上反射执行,还可以在分派过程中添加”前置通知”和后置处理(是在invoke方法中添加,如在调用目标方法前校验权限,在调用完目标方法后打印日志等)等功能。
《设计模式(三)结构型模式介绍及实例》
  要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个”java.lang.reflect.Proxy”类,通过”Proxy”类提供的一个newProxyInstance方法用来创建一个对象的代理对象,如下所示:

     static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

  newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数:

ClassLoader loader:用来指明生成代理对象使用哪个类装载器
Class<?>[ ] interfaces:用来指明生成哪个对象的代理对象(代理类和目标类实现相同的接口)
InvocationHandler h : 用来指明产生的这个代理对象要做什么事情。(这个InvocationHandler 就是我们自己定义的InvocationHandler 对象,里边有我们编写invok方法)

  newProxyInstance方法生成代理对象过程:

  1. 通过Proxy.getProxyClass(ProxyGenerator.generateProxyClass(proxyName, interfaces);)获得动态代理类的class字节码内容。
  2. 把字节码通过传入的类加载器加载到虚拟机中,然后通过反射机制获得代理类的构造方法(方法签名为getConstructor(InvocationHandler.class)),生成代理类对象

  仍以上面的代码为例,区别在于代理类和测试类。示例代码如下:

/*动态代理类*/
public class Proxy1{ 	
    private TaoPiaoPiao movie;
    public Proxy1(TaoPiaoPiao movie) { 
        this.movie = movie;
    }
    public Object getProxyInstance() { 
        return Proxy.newProxyInstance(movie.getClass().getClassLoader(), movie.getClass().getInterfaces(), new InvocationHandler() { 
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable { 
				
				if(movie == null){ 
				    movie=new TaoPiaoPiao();
				}
				//这里调用对象方法的做法不是反射的相关写法,此处应该
				//用method.invoke的方式,示例:
				//method.invoke(movie, args);
				System.out.println("登录淘票票"); 
				movie.buyMovieTicket();
				System.out.println("淘票票向你推荐爆米花、可乐");
				return null;
			}
        });
    }
}
/*测试类*/
public class ProxyTest { 
	public static void main(String[] args) {  
        TaoPiaoPiao movie = new TaoPiaoPiao();
        Movie dynamicProxy = (Movie) new Proxy1(movie).getProxyInstance();
        dynamicProxy.buyMovieTicket();
	}
}

7.5 代理模式应用场景

  代理模式的应用场景有以下几种:

  • 1、远程代理
      为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在与不同地址空间的事实。
  • 2、虚拟代理
      通过虚拟代理存放实例化需要很长时间的真实对象。
    3、安全代理
      用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限的时候。
  • 4、智能指引
      指当调用真实的对象时,代理处理另一些事情。如计算真实对象的引用次数,这样当对象没有引用时,可以自动释放它。

  总的来说,代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途

    原文作者:解梦者
    原文地址: https://blog.csdn.net/m0_37741420/article/details/106442936
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞