设计模式之《结构型模式》版块(基于Java)

结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

适配器模式

泰国旅游使用插座问题

现实生活中的适配器例子 :泰国插座用的是两孔的(欧标),可以买个多功能转换插头 (适配器) ,这样就可以使用了。

适配器模式基本介绍

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

适配器模式工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口 方法
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图

类适配器模式介绍

基本介绍:Adapter类(适配器类),通过继承 src类(被适配者),实现 dst 类接口,完成src->dst的适配。

类适配器模式

应用实例

应用实例说明 以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的dst(即 目标)是5V直流电 (如下图)

UML类图

代码示例

//被适配者
public class Voltage220V {
    /**输出220V的电压*/
    public int output220V(){
        int src = 220;
        System.out.println("电压 = " + src + "伏");
        return src;
    }
}
//-----------------------------------------------------------------------------
//适配器接口/抽象层
public interface IVoltage5V {
    int output5V();
}
//适配器具体实现    缺点:适配器类需要继承被适配类 增加了耦合度
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
        //拿到220V电压
        int src = output220V();
        //转成5V
        int dstV = src / 44;
        return dstV;
    }
}
//-----------------------------------------------------------------------------
//适配器调用者/使用者   结果使用者
public class Phone {

    /**充电方法  iVoltage5V.output5V() 返回的结果是目标*/
    public void charging(IVoltage5V iVoltage5V){
       if (iVoltage5V.output5V() == 5){
           System.out.println("电压为5V 可以充电 ~~");
       }else {
           System.out.println("电压大于5V 不能充电 ~~");
       }
    }
}
//-----------------------------------------------------------------------------
//客户端 测试
public class Client {
    public static void main(String[] args) {
        System.out.println("类适配器模式");
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter());
    }
}

类适配器模式注意事项和细节

  1. 缺点:Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要 求dst必须是接口,有一定局限性;
  2. 缺点:src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
  3. 优点:由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器模式

基本介绍

  1. 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而 是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口, 完成src->dst的适配
  2. 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种

应用实例

  1. 应用实例说明:以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成

  2. 思路分析(类图):只需修改适配器即可, 如下:

    public class VoltageAdapter2 implements Voltage5 { 
    	private Voltage220 voltage220; //持有Voltage220对象,不是继承了
    }
    

UML类图

代码示例

//被适配者
public class Voltage220V {
    /**输出220V的电压*/
    public int output220V(){
        int src = 220;
        System.out.println("电压 = " + src + "伏");
        return src;
    }
}
//-----------------------------------------------------------------------------
//适配器接口/抽象层
public interface IVoltage5V {
    int output5V();
}
//适配器具体实现 
public class VoltageAdapter implements IVoltage5V {
    /**不再使用继承解耦合  使用关联关系中的聚合关系 */
    private Voltage220V output220V;

    /**从外部将Voltage220V聚合进来*/
    public VoltageAdapter(Voltage220V output220V) {
        this.output220V = output220V;
    }

    @Override
    public int output5V() {
        int dst = 0;
        if (output220V != null) {
            //获取220V电压
            int src = output220V.output220V();
            dst = src / 44;
        }
        return dst;
    }
}
//-----------------------------------------------------------------------------
//适配器调用者/使用者   结果使用者
public class Phone {

    /**充电方法  iVoltage5V.output5V() 返回的结果是目标*/
    public void charging(IVoltage5V iVoltage5V){
       if (iVoltage5V.output5V() == 5){
           System.out.println("电压为5V 可以充电 ~~");
       }else {
           System.out.println("电压大于5V 不能充电 ~~");
       }
    }
}
//-----------------------------------------------------------------------------
//客户端 测试
public class Client {
    public static void main(String[] args) {
        System.out.println("=== 对象配器模式 ====");
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter(new Voltage220V()));
    }
}

对象适配器模式注意事项和细节

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的 局限性问题,也不再要求dst必须是接口。
  2. 使用成本更低,更灵活。

接口适配器模式

基本介绍

  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
  2. 核心:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接 口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆 盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。

应用实例

当需要使用一个接口但是仅仅是需要使用接口中的某几个方法时

UML类图

代码示例

//接口
public interface Interface4 {
    void m1();
    void m2();
    void m3();
    void m4();
}
//-----------------------------------------------------------------------------
//默认实现接口的所有方法,都为空方法
public abstract class AbstractAdapter implements Interface4 {
    /**默认实现*/
    @Override
    public void m1() {}
    
    @Override
    public void m2() {}
    
    @Override
    public void m3() {}

    @Override
    public void m4() {}
}
//-----------------------------------------------------------------------------
//使用时只需要使用匿名内部类的方式,覆盖抽象类中的某些当前需要的方法
public class Client {
    public static void main(String[] args) {
        //只需要覆盖我们需要使用接口方法
        new AbstractAdapter(){
            @Override
            public void m1() {
                System.out.println("使用了m1的方法");
            }
        };
    }
}

适配器模式在SpringMVC框架应用的源码分析

  1. SpringMvc中的HandlerAdapter, 就使用了适配器模式

  2. 使用HandlerAdapter 的原因分析: 可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么 如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。原码 ( DispatcherServlet中)

    1. doDispatch方法
    2. getHandlerAdapter方法获取handler对应的HandlerAdapter(适配器),需要进行匹配的
    3. supports方法(判断当前传入的handler是否与当前HandlerAdapter适配的是同一个实例(类型))
    4. 执行方法,返回MondelAndView

动手写SpringMVC通过适配器设计模式获取到对应的Controller的源码

UML类图

代码示例

//多种Controller实现  
public interface Controller {}

class HttpController implements Controller {
	public void doHttpHandler() {
		System.out.println("http...");
	}
}

class SimpleController implements Controller {
	public void doSimplerHandler() {
		System.out.println("simple...");
	}
}

class AnnotationController implements Controller {
	public void doAnnotationHandler() {
		System.out.println("annotation...");
	}
}
//-------------------------------------------------------------------------
//定义一个Adapter接口 适 配器抽象层
public interface HandlerAdapter {
	boolean supports(Object handler);
	void handle(Object handler);
}

// 多种适配器类/实现
class SimpleHandlerAdapter implements HandlerAdapter {
	@Override
	public void handle(Object handler) {
        //强转SimpleController类型,调用该类的.doSimplerHandler()方法
		((SimpleController) handler).doSimplerHandler();
	}
	@Override
	public boolean supports(Object handler) {
        //判断传入的handler是否是SimpleController实例/类型的,即返回true/false
		return (handler instanceof SimpleController);
	}
}

class HttpHandlerAdapter implements HandlerAdapter {
	@Override
	public void handle(Object handler) {
		((HttpController) handler).doHttpHandler();
	}
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof HttpController);
	}
}

class AnnotationHandlerAdapter implements HandlerAdapter {
	@Override
	public void handle(Object handler) {
		((AnnotationController) handler).doAnnotationHandler();
	}
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof AnnotationController);
	}
}
//-------------------------------------------------------------------------
//调用方/测试
public class DispatchServlet {
	//所有适配器集合
	public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();

	public DispatchServlet() {
        //将所有的适配器存放到静态变量中
		handlerAdapters.add(new AnnotationHandlerAdapter());
		handlerAdapters.add(new HttpHandlerAdapter());
		handlerAdapters.add(new SimpleHandlerAdapter());
	}

	public void doDispatch() {
		// 此处模拟SpringMVC从request取handler的对象,
		// 适配器可以获取到希望的Controller
		//HttpController controller = new HttpController();
		AnnotationController controller = new AnnotationController();
		//SimpleController controller = new SimpleController();
		// 根据controller 得到对应适配器
		HandlerAdapter adapter = getHandler(controller);
		// 通过适配器执行对应的controller对应方法
		adapter.handle(controller);

	}

	public HandlerAdapter getHandler(Controller controller) {
		//遍历:根据得到的controller(handler), 返回对应适配器
		for (HandlerAdapter adapter : handlerAdapters) {
			if (adapter.supports(controller)) {
				return adapter;
			}
		}
		return null;
	}
	//主方法
	public static void main(String[] args) {
		new DispatchServlet().doDispatch(); //annotation...
	}
}

适配器模式的注意事项和细节

  1. 三种命名方式,是根据 src(被适配者)是以怎样的形式给到Adapter(在Adapter里的形式)来命名的

  2. 类适配器:以类给到,在Adapter里,就是将src当做类,继承

    对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有

    接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现

  3. Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。

  4. 实际开发中,实现起来不拘泥于我们讲解的三种经典形式

桥接模式

应用案例

手机操作问题:现在对不同手机类型的不同品牌实现操作编程(比如: 开机、关机、上网,打电话等),如图:

传统方案解决手机操作问题

UML类图

传统方案解决手机操作问题分析

  1. 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
  3. 解决方案-使用桥接模式

桥接模式(Bridge)-基本介绍

  1. 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层 次可以独立改变。
  2. 是一种结构型设计模式
  3. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责它的主要特点是把抽象(Abstraction)与行为实现 (Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能 扩展

桥接模式(Bridge)-原理类图

UML类图

原理类图说明

  1. Client类:桥接模式的调用者
  2. 抽象类(Abstraction) :维护了 Implementor / 即它的实现类ConcreteImplementorA.., 二者是聚合关系, Abstraction 充当桥接类
  3. RefinedAbstraction:是Abstraction抽象类的子类
  4. Implementor:行为实现类的接口
  5. ConcreteImplementorA/B:具体行为的实现类
  6. 这里的抽象类和接口是聚合关系,即调用和被调用关系

桥接模式解决手机操作问题

UML类图

代码示例

//品牌类 接口
public interface Brand {
    void open();
    void close();
    void call();
}
//具体品牌实现
public class HuaWei implements Brand {
    @Override
    public void open() {
        System.out.println("华为手机开机...");
    }
    @Override
    public void close() {
        System.out.println("华为手机关机...");
    }
    @Override
    public void call() {
        System.out.println("华为手机打电话...");
    }
}
public class XiaoMi implements Brand {
    @Override
    public void open() {
        System.out.println("小米手机开机...");
    }
    @Override
    public void close() {
        System.out.println("小米手机关机...");
    }
    @Override
    public void call() {
        System.out.println("小米手机打电话...");
    }
}
//-----------------------------------------------------------------------------
//抽象层  桥接类
public abstract class Phone {
    /**组合品牌*/
    private Brand brand;

    public Phone(Brand brand) {
        this.brand = brand;
    }
    //定义调用方法
    protected void open(){
        this.brand.open();
    }
    protected void close(){
        this.brand.close();
    }
    protected void call(){
        this.brand.call();
    }
}
//具体实现   折叠样式
public class FoldedPhone extends Phone {
     public FoldedPhone(Brand brand) {
            super(brand);
     }
    @Override
 	protected void open() {
        //直接调用父类方法
     super.open();
        System.out.println("折叠样式手机");
    }
    @Override
    protected void close() {
        super.close();
        System.out.println("折叠样式手机");
    }
    @Override
    protected void call() {
        super.call();
        System.out.println("折叠样式手机");
    }
}
public class UpRightPhone extends Phone{
    public UpRightPhone(Brand brand) {
        super(brand);
    }
    @Override
    protected void open() {
        super.open();
        System.out.println("直立样式手机");
    }
    @Override
    protected void close() {
        super.close();
        System.out.println("直立样式手机");
    }
    @Override
    protected void call() {
        super.call();
        System.out.println("直立样式手机");
    }
}
//-----------------------------------------------------------------------------
//调用端 测试
public class Client {
    public static void main(String[] args) {
        //获取折叠式手机 (样式 + 品牌)
        Phone phone = new FoldedPhone(new XiaoMi());
        phone.open();
        phone.call();
        phone.close();
        System.out.println("--------------------------");
        Phone phone2 = new FoldedPhone(new HuaWei());
        phone2.open();
        phone2.call();
        phone2.close();
        System.out.println("--------------------------");
        //直立式
        Phone phone3 = new UpRightPhone(new HuaWei());
        phone3.open();
        phone3.call();
        phone3.close();
    }
}

桥接模式在JDBC的源码剖析

Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有 MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类

UML类图

桥接模式的注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度(抽象,和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。

桥接模式其它应用场景

  1. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
  2. 常见的应用场景:
    1. -JDBC驱动程序
    2. -银行转账系统
      • 抽象)转账分类: 网上转账,柜台转账,AMT转账
      • 行为)转账用户类型:普通用户,银卡用户,金卡用户..
    3. -消息管理
      • 抽象)消息类型:即时消息,延时消息
      • 行为)消息分类:手机短信,邮件消息,QQ消息…

装饰者模式

星巴克咖啡订单项目

星巴克咖啡订单项目(咖啡馆):

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  4. 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。

方案1-解决星巴克咖啡订单项目 (较差的方案)

示意图

方案1-解决星巴克咖啡订单问题分析

  1. Drink 是一个抽象类,表示饮料
  2. des就是对咖啡的描述, 比如咖啡的名字
  3. cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
  4. Decaf 就是单品咖啡, 继承Drink, 并实现cost
  5. Espress && Milk 就是单品咖啡+调料, 这个组合很多
  6. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料, 类的数量就会倍增,就会出现类爆炸

方案2-解决星巴克咖啡订单 (好点)

前面分析到方案1因为咖啡单品 + 调料 组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目 的维护性(如图)

示意图

说明: milk,soy,chocolate 可以设计为Boolean,表示是否要添加相应的调料.

方案2-的问题分析

  1. 方案2可以控制类的数量,不至于造成很多的类
  2. 增加或者删除调料种类时,代码的维护量很大
  3. 考虑到用户可以添加多份调料时,可以将hasMilk返回一个对应int
  4. 考虑使用装饰者 模式

装饰者模式定义

  1. 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
  2. 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代 码的形式体现,请同学们注意体会。

装饰者模式(Decorator)原理

示意图

  1. 装饰者模式就像

    打包一个快递

    • 主体:比如:陶瓷、衣服 (Component) // 被装饰者
    • 包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator) 装饰者
  2. Component

    • 主体:比如类似前面的Drink
  3. ConcreteComponent和Decorator

    • ConcreteComponent:具体的主体, 比如前面的各个单品咖啡
    • Decorator: 装饰者,比如各调料.
  4. 在如图的Component与ConcreteComponent之间,如果 ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。

装饰者模式解决星巴克咖啡订单

示意图

  1. Drink 类就是前面说的抽象类, Componen
  2. ShortBlack 就单品咖啡
  3. Decorator 是一个装饰类,含有一个被装饰的对象(Drink obj)
  4. Decorator 的cost 方法 进行一个费用的叠加计算,递归的计算价格

装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

示意图

  1. Milk包含了LongBlack
  2. 一份Chocolate包含了(Milk+LongBlack)
  3. 一份Chocolate包含了(Chocolate+Milk+LongBlack)
  4. 这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。

UML类图

代码示例

//是一个抽象类,表示饮料    被修饰者的最高父类
public abstract class Drink {
    /**描述*/
    public String des;
    /**价格*/
    public float price = 0.0f;

    public String getDes() {
        return des;
    }
    public void setDes(String des) {
        this.des = des;
    }
    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    /**计算费用的抽象方法  子类来实现*/
    public abstract float cost();
}
//-----------------------------------------------------------------------------
//单品咖啡 缓冲层
public class Coffee extends Drink{
    @Override
    public float cost() {
        //返回父类的Price值
        return super.getPrice();
    }
}
//具体咖啡的实现   被修饰者
public class LongBlack extends Coffee {
    public LongBlack() {
        setDes("LongBlack");//设置描述
        setPrice(5.0f);//设置金额
    }
}
public class ShortBlack extends Coffee {
    public ShortBlack() {
        setDes("ShortBlack");
        setPrice(4.0f);
    }
}
public class Espresso extends Coffee {
    public Espresso() {
        setDes("意大利咖啡");
        setPrice(6.0f);
    }
}
//-----------------------------------------------------------------------------
//装饰器  定义规范/公共内容
public class Decorator extends Drink {
    //被装饰者对象
    private Drink obj;
	//构造器
    public Decorator(Drink obj) {
        this.obj = obj;
    }
    @Override
    public float cost() {
        //super.getPrice()  拿到自己价格
        //返回  当前对象的价格 + obj.cost()被修饰者的价格  计算形式和递归相似
        return super.getPrice() + obj.cost();
    }
    @Override
    public String getDes() {
        //obj.getDes() 被装饰者的信息    会以递归的形式拼接字符串
        return des + " " + getPrice() + " && " + obj.getDes();
    }
}
//具体调味料的实现   充当修饰者角色
public class Chocolate extends Decorator {
    public Chocolate(Drink obj) {
        super(obj);//传入被修饰者
        setDes("巧克力");//描述
        setPrice(3.0f);//调味品价格
    }
}
public class Milk extends Decorator{
    public Milk(Drink obj) {
        super(obj);
        setDes("牛奶");
        setPrice(2.0f);
    }
}
public class Soy extends Decorator {
    public Soy(Drink obj) {
        super(obj);
        setDes("豆浆");
        setPrice(1.5f);
    }
}
//-----------------------------------------------------------------------------
//咖啡店 调用测试
public class CoffeeBar {
    public static void main(String[] args) {
        //装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
        //1.点一份LongBlack
        Drink order = new LongBlack();
        System.out.println("一份LongBlack费用 = " + order.cost());
        System.out.println("一份LongBlack描述 = " + order.getDes());

        //2.order 加入一份牛奶
        order = new Milk(order);
        System.out.println("order 加入一份牛奶后费用 = " + order.cost());
        System.out.println("order 加入一份牛奶后描述 = " + order.getDes());

        //3.order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("order 加入一份巧克力后费用 = " + order.cost());
        System.out.println("order 加入一份巧克力后描述 = " + order.getDes());

        //3.order 加入两份巧克力
        order = new Chocolate(order);
        System.out.println("order 加入两份巧克力后费用 = " + order.cost());
        System.out.println("order 加入两份巧克力后描述 = " + order.getDes());

    }
}

装饰者模式在JDK应用的源码分析

Java的IO结构,FilterInputStream就是一个装饰者

说明(代码 + 原码截图)

public class Decorator {
    public static void main(String[] args) throws Exception {

        /*说明
            1.InputStream是抽象类,类似前面的Drink
            2.FileInputStream 是 InputStream 的子类,类是前面的DeCaf,LongBlack  被装饰者
            3.FilterInputStream 是 InputStream 的子类,类似前面的Decorator  修饰者
            4.DataInputStream 是 FilterInputStream 的子类,具体的修饰者,类似前面的Milk,Soy等
            5.FilterInputStream 类有一个成员变量protected volatile InputStream in; 即包含被装饰者
            6.分析的出在jdk的io体系中 就是使用了装饰者模式
        */
        DataInputStream dis = new
                DataInputStream(new FileInputStream("d:\test.txt"));
        System.out.println(dis.read());
        dis.close();
    }
}

组合模式

应用案例 - 学校院系展示需求

编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系 组成,一个学校有多个学院,一个学院有多个系。如图:

传统方案解决学校院系展示

示意图

传统方案解决学校院系展示存在的问题分析

  1. 学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分 层次的
  2. 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个 学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如 对学院、系的添加,删除,遍历等
  3. 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是 一个树形结构,可以更好的实现管理操作。 => 组合模式

组合模式基本介绍

  1. 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
  2. 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
  3. 这种类型的设计模式属于结构型模式。
  4. 组合模式使得用户对单个对象和组合对象的访问具有一致性:组合能让客户以一致的方式处理个别对象以及组合对象

组合模式原理类图

角色说明

  1. Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
  2. Leaf:在组合中表示叶子节点,叶子节点没有子节点
  3. Composite:非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作,比如增删改查

组合模式解决学校院系展示

UML类图

代码示例

//Component  这是组合中对象声明抽象类
public abstract class OrganizationComponent {
    /**名字*/
    private String name;
    /**说明*/
    private String des;

    public OrganizationComponent(String name, String des) {
        this.name = name;
        this.des = des;
    }

    protected void add(OrganizationComponent component){
        //默认实现  抛出一个异常 不支持操作的异常
        throw new UnsupportedOperationException();
    }

    protected void remove(OrganizationComponent component){
        //默认实现  抛出一个异常 不支持操作的异常
        throw new UnsupportedOperationException();
    }

    /**打印方法做成抽象的  因为每个组织都应该有打印方法  子类都需要实现*/
    protected abstract void print();


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }
}
//-----------------------------------------------------------------------------
//学校类   University就是Composite 可以管理 College
public class University extends OrganizationComponent {
    /**学院集合*/
    private List<OrganizationComponent> colleges =
            new ArrayList<>();

    public University(String name, String des) {
        super(name, des);
    }

    /**重写add方法 添加学院功能*/
    @Override
    protected void add(OrganizationComponent component) {
        colleges.add(component);
    }
    /**重写remove方法 添加删除学院功能*/
    @Override
    protected void remove(OrganizationComponent component) {
        colleges.remove(component);
    }

    /**print方法 就是输出University 包含的学院*/
    @Override
    protected void print() {
        System.out.println("-------------------"+ getName() +"-------------------");
        //遍历所有学院
        for (OrganizationComponent college : colleges) {
            college.print();
        }
    }

    /**重写get方法*/
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDes() {
        return super.getDes();
    }
}
//-----------------------------------------------------------------------------
//学院类   College就是Composite 可以管理 Department
public class College extends OrganizationComponent {
    /**系集合*/
    private List<OrganizationComponent> departments =
            new ArrayList<>();

    public College(String name, String des) {
        super(name, des);
    }

    @Override
    protected void print() {
        System.out.println("------------"+getName()+"------------");
        for (OrganizationComponent department : departments) {
            department.print();
        }
    }
    /**重写add方法 添加系功能*/
    @Override
    protected void add(OrganizationComponent component) {
        departments.add(component);
    }
    /**重写remove方法 添加删除系s功能*/
    @Override
    protected void remove(OrganizationComponent component) {
        departments.remove(component);
    }

    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDes() {
        return super.getDes();
    }
}
//-----------------------------------------------------------------------------
//系类  叶子节点
public class Department extends OrganizationComponent {

    public Department(String name, String des) {
        super(name, des);
    }

    //add,remove 就不需要重写了 因为他是叶子节点不需要去管理其它组件

    
    protected void print() {
        System.out.println(getName());
    }

    
    public String getName() {
        return super.getName();
    }

    
    public String getDes() {
        return super.getDes();
    }
}
//-----------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {

        //创建学校
        OrganizationComponent university
                = new University("南京大学", "中国知名院校");
        //创建学院
        OrganizationComponent computerCollege = new College("计算机学院", "计算机学院");
        OrganizationComponent infoEngineerCollege = new College("信息工程学院", "信息工程学院");

        //创建各个学院下面的系(专业)
        computerCollege.add(new Department("软件工程","软件工程"));
        computerCollege.add(new Department("网络工程","网络工程"));
        computerCollege.add(new Department("计算机科学与技术","计算机科学与技术是老牌专业"));

        infoEngineerCollege.add(new Department("通信工程","通信工程不好学"));
        infoEngineerCollege.add(new Department("信息工程","信息工程很好学"));

        //将学院加入到学校中
        university.add(computerCollege);
        university.add(infoEngineerCollege);

        computerCollege.print();
    }
}

组合模式在JDK集合的源码分析

Java的集合类-HashMap就使用了组合模式

UML类图 + 代码分析 + 截图

public class Composite {
    public static void main(String[] args) {
        /*
            1.Map 就是一个抽象的构建(类似Component层)
            2.HashMap是一个中间的构建(Composite)实现/继承了相关方法
                put,putAll...
            3.Node 是HashMap的静态内部类,类似Leaf叶子节点
                这里没有put,putAll... 不管理其它组件
        */
        Map<Integer,String> hashMap=new HashMap<Integer,String>();
        //直接存放叶子节点
        hashMap.put(0, "东游记");
        Map<Integer,String> map=new HashMap<Integer,String>();
        map.put(1, "西游记");
        map.put(2, "红楼梦");
        hashMap.putAll(map);
        System.out.println(hashMap);
    }
}

组合模式的注意事项和细节

  1. 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子 的问题。
  2. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
  3. 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构
  4. 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
  5. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性 都不一样,不适合使用组合模式

外观模式

应用案例 - 影院管理项目

组建一个家庭影院: DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的 功能,其过程为: 直接用遥控器:统筹各设备开关

开爆米花机 ,放下屏幕 ,开投影仪 ,开音响 ,开DVD,选dvd ,去拿爆米花 ,调暗灯光 ,播放,观影结束后,关闭各种设备

传统方式解决影院管理

示意图

传统方式解决影院管理问题分析

  1. 在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象) 相关方法,会造成调用过程混乱,没有清晰的过程
  2. 不利于在ClientTest 中,去维护对子系统的操作
  3. 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的 一群接口
  4. 也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节, 使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式

外观模式基本介绍

  1. 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供 一个一致的界面(接口/方法),此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
  2. 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端 只需跟这个接口发生调用,而无需关心这个子系统的内部细节

外观模式原理类图 + 说明

  1. 原理类图的说明(外观模式的角色)
    1. 外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
    2. 调用者(Client): 外观接口的调用者
    3. 子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者

外观模式解决影院管理

传统方式解决影院管理说明

  1. 外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在pc上安装软件的时候经 常有一键安装选项(省去选择安装目录、安 装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
  2. 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
  3. 示意图说明

外观模式应用实例

UML类图

代码示例

//子系统
public class DVDPlayer {
    //私有化构造器
    private DVDPlayer() {
    }
    /**使用单例模式 饿汉式*/
    private static DVDPlayer instance = new DVDPlayer();
	//返回对象实例
    public static DVDPlayer getInstance() {
        return instance;
    }
    public void on(){
        System.out.println("DVD 打开");
    }
    public void off(){
        System.out.println("DVD 关闭");
    }
    public void play(){
        System.out.println("DVD is playing");
    }
    public void pause(){
        System.out.println("DVD 暂停");
    }
}
public class Popcorn {
    private Popcorn() {
    }
    /**使用单例模式 饿汉式*/
    private static Popcorn instance = new Popcorn();

    public static Popcorn getInstance() {
        return instance;
    }
    public void on(){
        System.out.println("Popcorn 打开");
    }
    public void off(){
        System.out.println("Popcorn 关闭");
    }
    public void pop(){
        System.out.println("Popcorn is poping");
    }
}
public class Projector {
    private Projector() {
    }
    /**使用单例模式 饿汉式*/
    private static Projector instance = new Projector();

    public static Projector getInstance() {
        return instance;
    }
    public void on(){
        System.out.println("Projector 打开");
    }
    public void off(){
        System.out.println("Projector 关闭");
    }
    public void focus(){
        System.out.println("Projector is focus");
    }
}
public class Screen {
    private Screen() {
    }
    /**使用单例模式 饿汉式*/
    private static Screen instance = new Screen();

    public static Screen getInstance() {
        return instance;
    }
    public void up(){
        System.out.println("Screen up");
    }
    public void down(){
        System.out.println("Screen down");
    }
}
public class Stereo {
    private Stereo() {
    }
    /**使用单例模式 饿汉式*/
    private static Stereo instance = new Stereo();

    public static Stereo getInstance() {
        return instance;
    }
    public void on(){
        System.out.println("Stereo on");
    }
    public void off(){
        System.out.println("Stereo off");
    }
    public void up(){
        System.out.println("Stereo 音量调大...");
    }
}
public class TheaterLight {
    private TheaterLight() {
    }
    /**使用单例模式 饿汉式*/
    private static TheaterLight instance = new TheaterLight();

    public static TheaterLight getInstance() {
        return instance;
    }
    public void on(){
        System.out.println("TheaterLight on");
    }
    public void off(){
        System.out.println("TheaterLight off");
    }
    public void dim(){
        System.out.println("TheaterLight 调暗...");
    }
    public void bright(){
        System.out.println("TheaterLight 调亮...");
    }
}
//-------------------------------------------------------------------------
//外观类
public class HomeTheaterFacade {
    /**定义各个子系统对象   组合进来*/
    private TheaterLight theaterLight;
    private Popcorn popcorn;
    private Stereo stereo;
    private Projector projector;
    private Screen screen;
    private DVDPlayer dvdPlayer;

    /**构造器*/
    public HomeTheaterFacade() {
        //拿到所有子系统的实例对象
        this.theaterLight = TheaterLight.getInstance();
        this.popcorn = Popcorn.getInstance();
        this.stereo = Stereo.getInstance();
        this.projector = Projector.getInstance();
        this.screen = Screen.getInstance();
        this.dvdPlayer = DVDPlayer.getInstance();
    }
    /**操作分成4步   对外只有四步  具体功能实现交给子系统,调用交给外观类*/
    //准备
    public void ready(){
        popcorn.on();
        popcorn.pop();
        screen.down();
        projector.on();
        stereo.on();
        dvdPlayer.on();
        theaterLight.dim();
    }
    //开始
    public void play(){
        dvdPlayer.play();
    }
    //暂停
    public void pause(){
        dvdPlayer.pause();
    }
    //关闭
    public void end(){
        //关闭爆米花机
        popcorn.off();
        //剧院灯调亮
        theaterLight.bright();
        //屏幕升起
        screen.up();
        //投影仪关掉
        projector.off();
        //立体声关掉
        stereo.off();
        //dvd关掉
        dvdPlayer.on();
    }
}
//-------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {
        HomeTheaterFacade homeTheaterFacade =
                new HomeTheaterFacade();
        //准备影院
        homeTheaterFacade.ready();
        //开始
        homeTheaterFacade.play();

        //暂停
        homeTheaterFacade.pause();
        //关闭影院
        homeTheaterFacade.end();
    }
}

外观模式在MyBatis框架应用的源码分析

原码截图

MyBatis 中的Configuration 去创建MetaObject对象使用到外观模式

UML类图

外观模式的注意事项和细节

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  2. 外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  4. 当系统需要进行分层设计时,可以考虑使用Facade模式
  5. 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。

享元模式

应用案例 - 展示网站项目需求

小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希

望做这样的产品展示网站,但是要求都有些不同:

  1. 有客户要求以新闻的形式发布
  2. 有客户人要求以博客的形式发布
  3. 有客户希望以微信公众号的形式发布

传统方案解决网站展现项目 + 问题分析

  1. 直接复制粘贴一份,然后根据客户不同要求,进行定制修改

  2. 给每个网站租用一个空间

  3. 方案设计示意图

    问题分析

    1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来 处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
    2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、 数据库空间等服务器资源都可以达成共享,减少服务器资源
    3. 对于代码来说,由于是一份实例,维护和扩展都更加容易
    4. 上面的解决思路就可以使用 享元模式 来解决

享元模式基本介绍

  1. 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
  2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  3. 享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需 总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
  4. 享元模式经典的应用场景就是池技术了,String常 量池、数据库连接池、缓冲池等等都是享元模式 的应用,享元模式是池技术的重要实现方式

享元模式的原理类图

对原理图的说明-即(模式的角色及职责)

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口/方法或实现
  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
  4. FlyWeightFactory 享元工厂,用于构建一个池容器(集合),同时提供从池中获取对象的方法

内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后, 落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

  1. 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态外部状态
  2. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  3. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
  4. 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对 象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用 享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解 决了对象的开销问题

享元模式解决网站展现项目

UML类图

代码示例

//抽象的享元角色
public abstract class WebSite {
    /**抽象方法*/
    public abstract void use(User user);
}
//-----------------------------------------------------------------------------
//具体享元角色实例
public class ConcreteWebSite extends WebSite {
    /**网站发布的形式(类型)   共享的部分  内部状态*/
    private String type = "";

    public ConcreteWebSite(String type) {
        this.type = type;
    }

    /** User是享元模式中的外部状态*/
    @Override
    public void use(User user) {
        System.out.println("网站的发布形式为:" + type + " 正在使用... 用户为:" + user.getName());
    }
}
//此处是外部状态
public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
//-----------------------------------------------------------------------------
//享元工厂
public class WebSiteFactory {
    /**集合 充当池的作用*/
    private Map<String,ConcreteWebSite> pool = new HashMap<>();

    /**根据网站的类型 返回一个网站 如果没有就创建一个网站 并放入到池中 并返回*/
    public WebSite getWebSiteCategory(String type){
        if(!pool.containsKey(type)){
            pool.put(type,new ConcreteWebSite(type));
        }
        return (WebSite)pool.get(type);
    }

    /**获取网站分类的总数(池中有多少个网站类型)*/
    public int getWebSiteCount(){
        return pool.size();
    }
}
//-----------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();

        WebSite webSite1 = factory.getWebSiteCategory("新闻");
        webSite1.use(new User("Tom"));

        WebSite webSite2 = factory.getWebSiteCategory("博客");
        webSite2.use(new User("jack"));

        WebSite webSite3 = factory.getWebSiteCategory("博客");
        webSite3.use(new User("smith"));

        WebSite webSite4 = factory.getWebSiteCategory("博客");
        webSite4.use(new User("king"));

        System.out.println("网站的实际实例对象/分类数量:" + factory.getWebSiteCount());
    }
}

享元模式在JDK-Interger的应用源码分析

测试源码

public class Flyweight {
    public static void main(String[] args) {
        /*
            如果Integer.valueOf(x) x在-128 ~ 127 之间就使用享元模式返回 否则仍然使用new Integer返回
            1.在valueOf() 方法中,先判断值是否在IntegerCache范围之内,如果不在就创建新的Integer(new的方式),
                否则就直接从缓存池中返回
            2.valueOf()方法使用到了享元模式
            3.如果使用valueOf()方法得到一个Integer实例 并且范围在-128 ~ 127之间,执行速度比new Integer(x)快
         */
        Integer x = Integer.valueOf(127);
        Integer y = new Integer(127);
        Integer z = Integer.valueOf(127);
        Integer w = new Integer(127);
        System.out.println(x.equals(y)); // true
        System.out.println(x == y ); // false
        System.out.println(x == z ); // true
        System.out.println(w == x ); // false
        System.out.println(w == y ); // false

        Integer x1 = Integer.valueOf(200);
        Integer x2 = Integer.valueOf(200);
        System.out.println(x1 == x2);// false

    }
}

截图分析

享元模式的注意事项和细节

  1. 在享元模式这样理解,“享”就表示共享,“元”表示对象
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式
  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
  7. 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池

代理模式(Proxy)

基本介绍

  1. 代理模式:为一个目标对象提供一个替身,以控制对这个被代理对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的 功能操作,即扩展目标对象的功能。
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  3. 代理模式有不同的形式, 主要有三种静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。

静态代理

静态代理基本介绍

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

应用实例

  1. 定义一个接口:ITeacherDao
  2. 目标对象TeacherDAO实现接口ITeacherDAO
  3. 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
  4. 调用的时候通过调用代理对象的方法来调用目标对象.
  5. 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来 调用目标对象的方法。

UML类图

代码示例

//目标对象的抽象层,接口
public interface ITeacherDao {
    /**授课方法*/
    void teach();
}
//目标对象/被代理对象
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师授课中...");
    }
}
//-----------------------------------------------------------------------------
//代理对象  也需要实现/继承  被代理对象的父类
public class TeacherDaoProxy implements ITeacherDao {

    /**被代理对象/目标对象  通过接口来进行聚合*/
    private ITeacherDao teacher;
	//构造器聚合
    public TeacherDaoProxy(ITeacherDao teacher) {
        this.teacher = teacher;
    }

    @Override
    public void teach() {
        System.out.println("代理开始 完成某些操作...");
        teacher.teach();
        System.out.println("提交...");
    }
}
//-----------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {
        //被代理对象
        TeacherDao teacherDao = new TeacherDao();
        //创建代理对象,同时将被代理对象传入
        TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);

        //通过代理对象调用目标对象方法
        //执行的是代理对象的方法,代理对象再去调用目标对象的方法
        proxy.teach();

    }
}

静态代理优缺点

  1. 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
  3. 一旦接口增加方法,目标对象与代理对象都要维护

动态代理

动态代理模式的基本介绍

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
  3. 动态代理也叫做:JDK代理、接口代理
  4. 使用 JavaAPI完成Proxy.newProxyInstance()方法 通过反射机制动态的返回一个代理对象

动态代理应用实例

使用动态代理完成静态代理案例

UML类图

代码示例

//目标对象的抽象层,接口父类
public interface ITeacherDao {
    /**授课方法*/
    void teach();
    void sayHello(String name);
}
//目标对象/被代理对象
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师授课中...");
    }

    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}
//-----------------------------------------------------------------------------
//代理对象
public class ProxyFactory {

    /**维护一个目标对象  Object*/
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**给目标对象生成一个代理对象*/
    public Object getProxyInstance(){
        /*
            public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            1.ClassLoader:指定当前目标对象使用的类加载器
            2.Class<?>[] interfaces:目标对象实现的接口类型集合,使用泛型方法确认类型
            3.InvocationHandler:事情处理,执行目标对象的方法时,会触发事情处理器方法,
                会把当前执行的目标对象方法作为参数传入
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy proxy是真实对象(目标对象)的真实代理对象,invoke方法可以返回调用代理对象方法的返回结果,
                     		也可以返回对象的真实代理对象(com.sun.proxy.$Proxy0)
                     * @param method 是调用的方法,可以用来方法过滤,得到方法的声明类等等。
                     * @param args 仅仅是被调用方法的参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass());
                        System.out.println("JDK代理开始...");
                        Object result = method.invoke(target, args);
                        System.out.println("JDK代理结束...");
                        return result;
                    }
                });
    }
}
//-----------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {
        //创建目标对象
        TeacherDao teacherDao = new TeacherDao();
        //给目标对象创建代理对象
        ITeacherDao proxyTeacherDao = (ITeacherDao)new ProxyFactory(teacherDao).getProxyInstance();
        //class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
        // System.out.println(proxyTeacherDao.getClass());
        //通过代理对象调用方法
        //proxyTeacherDao.teach();
        proxyTeacherDao.sayHello("tom");
    }
}

Cglib代理

Cglib代理模式的基本介绍

  1. 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只 是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现 代理-这就是Cglib代理
  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功 能扩展, 有些书也将Cglib代理归属到动态代理。
  3. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接 口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
  4. 在AOP编程中如何选择代理模式:
    1. 目标对象需要实现接口,用JDK代理
    2. 目标对象不需要实现接口,用Cglib代理
  5. Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

Cglib代理模式实现步骤 + 注意事项

  1. 需要引入cglib的jar文件
  2. 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException:
  3. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的 业务方法.

应用实例

完成静态代理的应用案例

UML类图

代码示例

//目标对象/被代理对象
public class TeacherDao{
    public void teach() {
        System.out.println("老师授课中... < cglib代理不需要实现接口 > ");
    }
}
//-------------------------------------------------------------------------
//代理对象工厂
public class ProxyFactory implements MethodInterceptor {

    /**维护一个目标对象*/
    private Object target;

    /**传入被代理的对象*/
    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**返回当前维护的目标对象(target)的代理对象*/
    public Object getProxyInstance(){
        //1.创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(target.getClass());
        //3.设置回调函数   this反过来调自己
        enhancer.setCallback(this);
        //4.创建子类对象 即代理对象
        return enhancer.create();
    }

    /**
     * 重写 intercept方法,会调用目标对象的方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理模式...开始");
        Object result = method.invoke(target, objects);
        System.out.println("Cglib代理提交...");
        return result;
    }
}
//-------------------------------------------------------------------------
//测试
public class Client {
    public static void main(String[] args) {
        //创建目标对象
        TeacherDao teacherDao = new TeacherDao();
        //获取到代理对象,并且将目标对象传递给代理对象
        TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(teacherDao).getProxyInstance();

        //执行代理对象的方法  触发intercept方法 从而实现对目标对象的调用
        proxyInstance.teach();

    }
}

代理模式(Proxy)的变体

  1. 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
  2. 缓存代理:比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源, 再到公网或者数据库取,然后缓存。
  3. 远程代理远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和 真正的远程对象沟通信息。
原文地址:https://www.cnblogs.com/mikisakura/p/12983223.html