目录
结构型模式(Structural Pattern):怎么构造一个对象(行为、属性)
一、适配器模式
二、桥接模式(Bridge)
三、装饰者模式
设计模式在JAVA I/O库中的应用
案例
使用前
使用后
总结:
四、外观模式
案例
使用前
使用后
总结:
五、代理模式
分类:
静态代理
Cglib代理
总结
jdk代理与Cglib代理比较:
应用
适配器介绍:
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
适配器模式的定义:
把一个类的接口变成客户端所期待的另一个接口,使本来不不匹配二无法再一起工作的类可以在一起工作。
使用场景
SpringMvc框架中HandlerAdapter类
1)系统需要使用现有的类,而类的接口不符合要求
2)需要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的类
3)需要一个统一的接口,而输入类型不确定
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
案例:
需求:
电源适配的项目,要让手机能够使用220V的交流电;注意,手机只接受5.5V的电压;
术语
source:待适配的类/对象/接口
Adapter:适配器
destination:适配后可用的类/对象/接口
角色
sourcePower220V===》220V的电源
AdapterPowerAdapt===》电源适配器
DestinationPower5V===》5V的电源
分类
类适配器模式Adapter 类,通过继承 source 类,实现 Destination 类接口,完成 source->Destination 的适配。
对象适配器模式将 Adapter 类作修改,不是继承 source 类,而是持有 source 类的实例,以解决兼容性的问题。 即:持有 source 类,实现 Destination 类接口,完成source->Destination 的适配
接口适配器模式当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求 适用于一个接口不想使用其所有的方法的情况
适配器模式分类三种体现形式思想都大同小异,这里就只介绍最常用的 对象适配器模式;
举例二:假如我们要做一个下单操作,需要两个表 Order 订单 orderItem 订单项目=》需要写两个方法OrderDao.add(order)\OrderItemDao.add(oi);但是在某一些例子中,前台传递待后台的是order,而后台需要使用的却是OrderHis,所以这里要使用到我们的适配器。
示例代码:
package com.javaxl.design.adapter.before;
/**
* @author 周大福ye
* @site www.javaxl.com
* @company
* @create 2020-02-22 15:51
*
* 220V的电压
*/
public class Voltage220V {
private double voltage;
public Voltage220V() {
this.voltage = 220;
}
public double getVoltage() {
return voltage;
}
public void setVoltage(double voltage) {
this.voltage = voltage;
}
}
方法中参数的Voltage220V voltage220V是不合理的,应该是适配器
例如:Person人:吃饭、睡觉、休息
Dog 狗:吃饭、睡觉、休息
人和狗的习惯大致相似,那么Dog能不能继承Preson呢?答案:看似正确,但是不合理,上述方法中也是同理。
public class Phone {
// 充电
public void charge(Voltage220V voltage220V){
double voltage = voltage220V.getVoltage() / 40;
System.out.println("最终手机充电所用电压:" + voltage + "V");
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
// 已知有一个220V的电源,要用它给手机进行充电,我们只能将220V的电源进行处理后才能给手机充上电
// 还一种方案:新增5V的一个Voltage5V,Voltage的电压可以被手机使用
// 但是这违背现实生活现象,现实生活中只有220V的电源,其他的电源都是通过适配得来的
phone.charge(new Voltage220V());
}
}
使用适配器后(充电头相当于适配器)
package com.javaxl.design.adapter.after;
/**
* @author 周大福ye
* @site www.javaxl.com
* @company
* @create 2020-02-22 15:51
*
* 220V的电压
*/
public class Voltage220V {
private double voltage;
public Voltage220V() {
this.voltage = 220;
}
public double getVoltage() {
return voltage;
}
public void setVoltage(double voltage) {
this.voltage = voltage;
}
}
/**
* 目标接口
*/
interface Voltage5V{
double getVoltage();
}
/**
* 适配器:里面封装了source源到destination目标的过程
*/
class VoltageAdapter implements Voltage5V{
private Voltage220V voltage220V;
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public double getVoltage() {
return voltage220V.getVoltage() / 40;
}
}
package com.javaxl.design.adapter.after;
public class Phone {
// 充电
public void charge(Voltage5V voltage5V){
double voltage = voltage5V.getVoltage();
System.out.println("最终手机充电所用电压:" + voltage + "V");
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
// 已知有一个220V的电源,要用它给手机进行充电,我们只能将220V的电源进行处理后才能给手机充上电
// VoltageAdapter适配器对Voltage220V这个不能直接使用的电源适配后就可以使用了
phone.charge(new VoltageAdapter(new Voltage220V()));
}
}
通过适配器把220V电压进行适配。
适配器: VoltageAdapter
Voltage220V()相当于上面例子中的order
voltage相当于上面例子中的OrderHis
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
应用场景:JDBC的Driver驱动类
关键代码:抽象类依赖实现类。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
术语
Bridge:桥接
Abstraction:抽象类
Implementor:实现
concrete:具体的
角色
Client 类:桥接模式的调用者
Abstraction:维护了 Implementor/ 即它的实现类 ConcreteImplementorA.., 二者是聚合关系,Abstraction充当桥接类
xxxAbstraction :抽象的具体子类
Implementor :行为实现类的接口
ConcreteImplementorA /B:具体行为的实现类A、B
案例
需求:手机(型号 + 品牌)操作问题;
完成手机各品牌各型号的项目设计;
列如:折叠式的华为、小米、Vivo,直立式的华为、小米、Vivo,旋转式的、滑盖的...
要求该项目设计易于扩展
上图是常见的需求设计方案,非常不便于维护,手机的型号与品牌耦合度太高,当要新增滑盖(Slide)式的手机时,对应的品牌手机也要新增;
手机品牌有很多,就需要创建的子类就很多,品牌依托与规格,耦合性太高,很臃肿。
解决方案:
有多少个规格就设计多少个规格类,有多少个品牌就设计多少个品牌类?把它们相互组合(桥接)如下图
示例代码
抽象部分包含实现
package com.javaxl.design.bridge;
/**
* @author 周大福ye
* @site www.javaxl.com
* @company
* @create 2020-02-22 17:29
*
* 手机型号
*/
public abstract class Abstraction {
protected Implementor implementor;
public abstract void call();
}
class Folded extends Abstraction {
private String name = "折叠式";
Folded(Implementor implementor) {
this.implementor = implementor;
}
@Override
public void call() {
System.out.println(this.implementor.getName() + this.name + "正在通话中");
}
}
class Upright extends Abstraction {
private String name = "直立式";
Upright(Implementor implementor) {
this.implementor = implementor;
}
@Override
public void call() {
System.out.println(this.implementor.getName() + this.name + "正在通话中");
}
}
class Slide extends Abstraction {
private String name = "滑盖式";
Slide(Implementor implementor) {
this.implementor = implementor;
}
@Override
public void call() {
System.out.println(this.implementor.getName() + this.name + "正在通话中");
}
}
package com.javaxl.design.bridge;
/**
* @author 周大福ye
* @site www.javaxl.com
* @company
* @create 2020-02-22 17:29
* 手机品牌
*/
public interface Implementor {
String getName();
}
class HW implements Implementor{
private String name = "华为";
@Override
public String getName() {
return name;
}
}
class Mi implements Implementor{
private String name = "小米";
@Override
public String getName() {
return name;
}
}
class Vivo implements Implementor{
private String name = "Vivo";
@Override
public String getName() {
return name;
}
}
public class Client {
public static void main(String[] args) {
Folded folded = new Folded(new HW());
folded.call();
Upright upright = new Upright(new Mi());
upright.call();
Slide slide = new Slide(new Vivo());
slide.call();
}
}
后台输出结果:
从结果可以看出来:
使用桥接模式,对该项目进行设计,型号或品牌的扩展,都不会影响另一方;
即手机型号扩展,手机品牌不受影响;手机品牌的上市退市,不会影响到手机型号;
注意事项及细节
实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性
对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
术语:
Component:组件(主体)
concreteComponent:被装饰者
Decorator:装饰者
注意:concreteComponent、Decorator都会实现或继承Component
角色:
Client 类:装饰模式的调用者
Component:主体
concreteComponent:被装饰者
xxxConcreteComponent:具体的被装饰者
Decorator:装饰器
xxxDecorator:子装饰器
1、Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
2、ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
3、Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
4、ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。
由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。
Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。
抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。
单体咖啡与调味组合的饮料计价项目
这个项目最容易想到的设计方案就是采用继承的设计方案,设计思路如下:
Drink===》饮品
Juice===》果汁
.......
Coffee===》咖啡
ChinaCoffee===》中式咖啡
ASeasoningChinaCoffee===》被A调料修饰的中式咖啡
BSeasoningChinaCoffee===》被B调料修饰的中式咖啡
......
XxxCoffee===》其它咖啡
ASeasoningXxxCoffee===》被A调料修饰的其它咖啡
BSeasoningXxxCoffee===》被B调料修饰的其它咖啡
......
上面每个类中都有getPrice计价的功能,从结果上来看,确实可以完成项目需求,但是整个设计体系过于臃肿,不便于后期的扩展与维护;
/**
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
protected double price;
protected int n;
protected DrinkSeasoning seasoning;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee{
ChinaCoffee(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n+this.seasoning.getPrice();
}
}
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2020-02-22 18:32
*
* 调料的抽象接口
*/
public interface DrinkSeasoning {
public abstract double getPrice();
}
/**
* A类调料
*/
class ADrinkSeasoning implements DrinkSeasoning{
protected double price;
protected int n;
ADrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
/**
* B类调料
*/
class BDrinkSeasoning implements DrinkSeasoning{
private double price;
protected int n;
BDrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
ADrinkSeasoning aDrinkSeasoning = new ADrinkSeasoning(2,2);
chinaCoffee.seasoning = aDrinkSeasoning;
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+chinaCoffee.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
}
}
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
protected double price;
protected int n;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee {
ChinaCoffee(double price, int n) {
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n;
}
}
package com.javaxl.design.decorator.after;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2020-02-22 22:26
*/
public class DecoratorDrink extends Drink {
private Drink drink;
public DecoratorDrink(Drink drink, double price, int n) {
this.drink = drink;
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n + drink.getPrice();
}
}
class ADecoratorDrink extends DecoratorDrink {
public ADecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
class BDecoratorDrink extends DecoratorDrink {
public BDecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
package com.javaxl.design.decorator.after;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2020-02-22 18:50
*/
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
// 假定A类调料2元一份,B类调料3元一份
Drink order = new ADecoratorDrink(chinaCoffee, 2, 2);
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+order.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new ADecoratorDrink(order,2,1);
System.out.println("式咖啡1份+A调料3份,最终价格为:"+order.getPrice());
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料2份,最终价格为:"+order.getPrice());
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料4份,最终价格为:"+order.getPrice());
}
}
装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。在软件开发中,装饰模式应用较为广泛,例如在JavaIO中的输入流和输出流的设计、javax.swing包中一些图形界面构件功能的增强等地方都运用了装饰模式。
主要优点
主要缺点
适用场景
注意事项及细节
装饰者模式一般用于对原有功能进行增强/装饰
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性
应用
IO流体系:缓冲流
外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
术语:Facade:外观
角色:
模式结构
Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
需求:组装一个家庭影院;
电脑打开、投影仪打开、音箱打开、灯光调暗、零食拿出来,电影开始;
零食收起来、灯光调亮、音箱关闭、投影仪关闭、电脑关闭,电影结束
/** 电脑(故意写两个用不上的功能,依次体现外观模式的优点)
*/
public class ComponentA {
public void m1(){
System.out.println("电脑功能一...");
}
public void m2(){
System.out.println("电脑功能二...");
}
public void on(){
System.out.println("电脑打开...");
}
public void off(){
System.out.println("电脑关闭...");
}
}
//投影仪
class ComponentB {
public void on(){
System.out.println("投影仪打开...");
}
public void off(){
System.out.println("投影仪关闭...");
}
}
//音箱
class ComponentC {
public void on(){
System.out.println("音箱打开...");
}
public void off(){
System.out.println("音箱关闭...");
}
}
//、灯光
class ComponentD {
public void on(){
System.out.println("灯光调亮...");
}
public void off(){
System.out.println("灯光调暗...");
}
}
//零食
class ComponenE {
public void on(){
System.out.println("零食拿出来...");
}
public void off(){
System.out.println("零食收起来...");
}
}
public class Client {
public static void main(String[] args) {
new ComponentA().on();
new ComponentB().on();
new ComponentC().on();
new ComponentD().off();
new ComponenE().on();
System.out.println("电影开始了...");
System.out.println();
new ComponenE().off();
new ComponentD().on();
new ComponentC().off();
new ComponentB().off();
new ComponentA().off();
System.out.println("电影结束了...");
}
}
从上面代码可以看出:
客户端调用依赖了所有的子系统(ABCDE),如果该需求反复出现,对于客户端调用而言,就不是很方便了;
另一方面,此需求完成只需要依赖各个子系统的其中一部分功能,其它功能客户端用不上,依照迪米特法则我们也应该解耦客户端与各个子系统的关系;
/** 电脑(故意写两个用不上的功能,依次体现外观模式的优点)
*/
public class ComponentA {
public void m1(){
System.out.println("电脑功能一...");
}
public void m2(){
System.out.println("电脑功能二...");
}
public void on(){
System.out.println("电脑打开...");
}
public void off(){
System.out.println("电脑关闭...");
}
}
//投影仪
class ComponentB {
public void on(){
System.out.println("投影仪打开...");
}
public void off(){
System.out.println("投影仪关闭...");
}
}
//音箱
class ComponentC {
public void on(){
System.out.println("音箱打开...");
}
public void off(){
System.out.println("音箱关闭...");
}
}
//、灯光
class ComponentD {
public void on(){
System.out.println("灯光调亮...");
}
public void off(){
System.out.println("灯光调暗...");
}
}
//零食
class ComponentE {
public void on(){
System.out.println("零食拿出来...");
}
public void off(){
System.out.println("零食收起来...");
}
}
public class ComponentFacade {
ComponentA componentA =new ComponentA();
ComponentB componentB = new ComponentB();
ComponentC componentC = new ComponentC();
ComponentD componentD = new ComponentD();
ComponentE componenE = new ComponentE();
public void on(){
componentA.on();
componentB.on();
componentC.on();
componentD.off();
componenE.on();
System.out.println("电影开始了...");
}
public void off(){
componenE.off();
componentD.on();
componentC.off();
componentB.off();
componentA.off();
System.out.println("电影结束了...");
}
}
public class Client {
public static void main(String[] args) {
ComponentFacade componentFacade = new ComponentFacade();
componentFacade.on();
System.out.println();
componentFacade.off();
}
}
从上面可以看出,客户端只依赖了外观类,彻底解耦了与各个子系统之间的关系;
注意事项及细节
外观模式是一种使用频率非常高的设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,使子系统与客户端的耦合度降低,且客户端调用非常方便。
主要优点
外观模式的主要优点如下:
主要缺点
适用场景
概述:
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问
术语
Proxy:代理
代理模式包含如下三个角色:
角色
细节
代理对象与目标对象要实现相同的接口 调用的时候通过调用代理对象的方法来调用目标对象
/** 目标类
*/
public class TeacherDAO implements ITeacherDao {
public void teach() {
System.out.println("老师传授知识");
}
}
//目标接口
interface ITeacherDao {
void teach();
}
//代理类
class TeacherDAOProxy implements ITeacherDao {
private ITeacherDao teacherDAO;
public TeacherDAOProxy(ITeacherDao teacherDAO) {
this.teacherDAO = teacherDAO;
}
@Override
public void teach() {
System.out.println("老师正式授课前的准备工作,如学生全部签到...");
teacherDAO.teach();
System.out.println("老师结束授课,如下课铃声响起...");
}
}
public class Client {
public static void main(String[] args) {
TeacherDAOProxy proxy = new TeacherDAOProxy(new TeacherDAO());
proxy.teach();
}
}
动态代理jdk代理
角色
细节
不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象 代理类所在包:java.lang.reflect.Proxy
/**
* 目标接口
*/
interface ITeacherDao {
String teach();
ITeacherDao sleep(int minutes);
}
class TeacherDao implements ITeacherDao{
@Override
public String teach() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date())+":老师传授知识";
}
@Override
public ITeacherDao sleep(int minutes) {
System.out.println("老师睡了" + minutes + "分钟");
return this;
}
}
//真实代理类的外衣
class TeacherDaoProxy{
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
public Object xxx(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
String methodName = method.getName();
System.out.println("目标方法" + methodName + ":jdk代理开始...");
System.out.println("真实代理对象:"+proxy.getClass());
System.out.println("目标对象:"+target.getClass());
if("sleep".equals(methodName)){
// method.invoke(target, args);
// obj = proxy;
obj = method.invoke(target, args);
}else {
// proxy是真实代理类,method是目标方法,args是目标方法携带的参数
obj = method.invoke(target, args);
}
System.out.println("目标方法" + methodName + ":jdk代理结束...");
return obj;
}
});
}
}
public class Client {
public static void main(String[] args) {
TeacherDaoProxy proxy = new TeacherDaoProxy(new TeacherDao());
ITeacherDao ins = (ITeacherDao) proxy.xxx();
System.out.println("===========代理类实例被使用 begin=============");
System.out.println(ins);
System.out.println("===========代理类实例被使用 end=============");
System.out.println(ins.teach());
// System.out.println(proxy.execute());
System.out.println("===========代理类实例被使用 begin=============");
ins.sleep(10);
System.out.println("===========代理类实例被使用 end=============");
ins.sleep(20).sleep(60);
}
}
注意:java.lang.reflect.InvocationHandler.invoke第一个参数proxy的使用场景,链式编程中会用到;
角色
细节
注意:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
class TeacherDao {
public String teach() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date()) + ":老师传授知识";
}
public TeacherDao sleep(int minutes) {
System.out.println("老师睡了" + minutes + "分钟");
return this;
}
}
//真实代理类的外衣
class TeacherDaoProxy implements MethodInterceptor {
private Object target;
public TeacherDaoProxy(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
/**
* @param proxyIns 由CGLib动态生成的代理类实例
* @param method 上文中实体类所调用的被代理的方法引用
* @param args 参数值列表
* @param methodProxy 生成的代理类对方法的代理引用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxyIns, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
Object res;
System.out.println("目标方法" + methodName + ":cglib代理开始...");
System.out.println("真实代理对象:" + proxyIns.getClass());
System.out.println("目标对象:" + target.getClass());
if ("sleep".equals(methodName)) {
// method.invoke(target, args);
// obj = proxy;
res = method.invoke(target, args);
// res = methodProxy.invokeSuper(proxyIns,args);
res = proxyIns;
} else {
// proxy是真实代理类,method是目标方法,args是目标方法携带的参数
res = method.invoke(target, args);
}
System.out.println("目标方法" + methodName + ":cglib代理结束...");
return res;
}
}
public class Client {
public static void main(String[] args) {
TeacherDao proxy = (TeacherDao) new TeacherDaoProxy(new TeacherDao()).getProxyInstance();
proxy.sleep(111).sleep(222);
}
}
代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。
模式优点
代理模式的共同优点如下:
模式缺点
代理模式的主要缺点如下:
JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高
使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
Spring框架的AOP:Cglib代理的体现