【Java 结构型设计模式上】代理模式、适配器模式详解

IT界大神成长之路 专栏收录该内容
43 篇文章 1 订阅

 
愿你如阳光,明媚不忧伤。

 


1. 结构型模式的特点和分类

Structural Pattern 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

  • 结构型模式总览
范围 \ 目的结构型模式 7
类模式 1(类)适配器
对象模式 7代理
(对象)适配器
桥接
装饰
外观
享元
组合
  1. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  2. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
  3. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
  4. 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
  5. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
  6. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用
  7. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

 


2. 代理模式

Proxy 在有些情况下,无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买;要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间;还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。简单的说就是给对象提供一个代理以控制对该对象的访问

代理模式优点和缺点

  • 优点
  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  2. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
  • 缺点
  1. 代理模式会造成系统设计中类的数量增加
  2. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  3. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
  4. 一旦接口增加方法,目标对象与代理对象都要维护

代理模式的应用场景

  1. 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  2. 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  3. 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  4. 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  5. 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
  6. 防火墙代理,内网通过代理穿透防火墙,实现对公网的访问
  7. 缓存代理,当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。

代理模式的结构

代理模式主要通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
«Interface» 抽象主题Subject +request() : void 真实主题RealSubject +Request() : void 代理类Proxy -RealSubject:realSubject +preRequest() : void +request() : void +postRequest() : void 访问类Client +main() : void Realization Realization Aggregation

代理模式的实现

package com.it.god.controller;

public class ProxyController {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }

}

// 抽象主题
interface Subject {
    void Request();
}

// 真实主题
class RealSubject implements Subject {
    @Override
    public void Request() {
        System.out.println("访问真实主题方法...");
    }
}

// 代理
class Proxy implements Subject {
    private RealSubject realSubject;

    @Override
    public void Request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }

    public void preRequest() {
        System.out.println("访问真实主题之前的预处理。");
    }

    public void postRequest() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

-----------------------------------------------------------------
・【运行结果】
	访问真实主题之前的预处理。
	访问真实主题方法...
	访问真实主题之后的后续处理。

代理模式的扩展

在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点:
1.静态代理只能通过手动完成代理操作,真实主题与代理主题一一对应,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
2.设计代理以前真实主题必须事先存在,不太灵活。
因此需要动态的在内存中构建代理对象,这样,代理对象就不用实现接口,能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为,但是真实主题要实现抽象主题,否则无法进行动态代理。

  • 动态代理模式(JDK代理、接口代理)
«Interface» 抽象主题Subject +request() : void 真实主题1RealSubject1 +Request() : void 真实主题2RealSubject2 +Request() : void 动态代理类DynamicProxy -RealSubject:realSubject +preRequest() : void +request() : void +postRequest() : void «Interface» 调用处理器接口InvocationHandler +invoke(Object proxy, Method method, Object[] args) : Object 访问类Client +main() : void Realization Realization Realization
  1. 代理类所在的包java.lang.reflect.Proxy
  2. JDK实现代理只需要使用newProxyInstance方法。
/*
* ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
* Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
* InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入
*/
package com.it.god.controller;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyController {

    public static void main(String[] args) {
        DynamicProxy dynamicProxy = new DynamicProxy();
        TargetSubject realSubject2 = dynamicProxy.getInstance(new RealSubject2());
        realSubject2.Request();
    }

}

interface TargetSubject {
    void Request();
}

class DynamicProxy implements InvocationHandler {
    private TargetSubject targetSubject;

    public TargetSubject getInstance(TargetSubject target) {
        this.targetSubject = target;
        Class<?> clazz = target.getClass();
        return (TargetSubject) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.targetSubject, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("访问真实主题之前的预处理。");
    }

    private void after() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

class RealSubject2 implements TargetSubject {
    @Override
    public void Request() {
        System.out.println("访问真实主题方法...");
    }

}
-----------------------------------------------------------------
・【运行结果】
	访问真实主题之前的预处理。
	访问真实主题方法...
	访问真实主题之后的后续处理。
	

 


3. 适配器模式

Adapter 适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作,例如用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器。适配器模式分为类结构型模式(继承)和对象结构型模式(依赖)两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

适配器模式优点和缺点

  • 优点
  1. 客户端通过适配器可以透明地调用目标接口。
  2. 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
  3. 可以让任何两个没有关联的类一起运行。
  4. 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  5. 在很多业务场景中符合开闭原则。
  • 缺点
  1. 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。

适配器模式的应用场景

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

适配器模式的结构

1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
3. 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
  • 适配器结构(类)
适配器Adapter +request() : void 适配者Adaptee +specificRequest() : void «Interface» 目标接口Target +request() : void 访问类Client +main() : void Inheritance Realization
  • 适配器结构(对象)
适配器Adapter -adaptee:Adaptee +ObjectAdapter(Adaptee adaptee) +request() : void 适配者Adaptee +specificRequest() : void «Interface» 目标接口Target +request() : void 访问类Client +main() : void Composition Realization

适配器模式的实现

  • 适配器(类)
    类适配器是通过继承实现的,Java是单继承机制,所以要求目标必须是接口,有一定的局限性
package com.it.god.controller;

public class AdapterController {

    public static void main(String[] args) {
           System.out.println("适配器模式(类)测试:");
           Target target = new Adapter();
           target.request();
    }
}

// 目标接口
interface Target {
    public void request();
}

// 适配者类
class Adaptee {
    public void specificRequest() {
        System.out.println("适配者中的业务代码被调用!");
    }
}

// 适配器类
class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}

-----------------------------------------------------------------
・【运行结果】
	适配器模式(类)测试:
	适配者中的业务代码被调用!

  • 适配器(对象)
    对象适配器不继承适配者类,而是持有他的实例,用组合代替继承,符合合成复用原则
package com.it.god.controller;

public class AdapterController {

    public static void main(String[] args) {

        System.out.println("适配器模式(对象)测试:");
        TargetAudioPlayer targetAudioPlayer = new TargetAudioPlayer();

        targetAudioPlayer.requestPlay("mp3", "beyond the horizon.mp3");
        targetAudioPlayer.requestPlay("wav", "alone.mav");
        targetAudioPlayer.requestPlay("ape", "far far away.Ape");
        targetAudioPlayer.requestPlay("avi", "mind me.avi");

    }
}

// 目标接口,只能播放一种格式的音乐
interface TargetMediaPlayer {
    public void requestPlay(String type, String fileName);
}

// 目标接口实现类
class TargetAudioPlayer implements TargetMediaPlayer {
    TargetMediaPlayer targetMediaPlayer;

    @Override
    public void requestPlay(String type, String fileName) {
        // 播放 mp3 音乐文件的内置支持
        if (type.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file. Name: " + fileName);
        }
        // AdapterPlayer 提供了播放其他文件格式的支持
        else if (type.equalsIgnoreCase("wav") || type.equalsIgnoreCase("ape")) {
            targetMediaPlayer = new AdapterPlayer(type);
            targetMediaPlayer.requestPlay(type, fileName);
        } else {
            System.err.println("Invalid media. " + type + " format not supported");
        }

    }
}

// 适配者接口,可以播放更多格式
interface AdapteeMediaPlayer {
    public void playWav(String fileName);

    public void playApe(String fileName);
}

// 适配者实现Wav
class WavPlayer implements AdapteeMediaPlayer {

    @Override
    public void playWav(String fileName) {
        System.out.println("Playing wav file.Name:" + fileName);

    }

    @Override
    public void playApe(String fileName) {

    }

}

// 适配者实现Ape
class ApePlayer implements AdapteeMediaPlayer {

    @Override
    public void playWav(String fileName) {

    }

    @Override
    public void playApe(String fileName) {
        System.out.println("Playing ape file.Name:" + fileName);

    }
}

// 适配器类
class AdapterPlayer implements TargetMediaPlayer {
    // 聚合关联关系
    private AdapteeMediaPlayer adapteeMediaPlayer;

    // 通过构造器,传入需要适配的格式,返回可以读取新格式的适配者实例
    public AdapterPlayer(String type) {
        if (type.equalsIgnoreCase("wav")) {
            adapteeMediaPlayer = new WavPlayer();
        } else if (type.equalsIgnoreCase("ape")) {
            adapteeMediaPlayer = new ApePlayer();
        }
    }

    @Override
    public void requestPlay(String type, String fileName) {
        if (type.equalsIgnoreCase("wav")) {
            adapteeMediaPlayer.playWav(fileName);
        } else if (type.equalsIgnoreCase("ape")) {
            adapteeMediaPlayer.playApe(fileName);
        }

    }

}

-----------------------------------------------------------------
・【运行结果】
	适配器模式(对象)测试:
	Playing mp3 file. Name: beyond the horizon.mp3
	Playing wav file.Name:alone.mav
	Playing ape file.Name:far far away.Ape
	Invalid media. avi format not supported
	

 


【每日一面】

说说代理模式和适配器模式的区别?

Proxy:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
Adapter:有时候也称包装样式或者包装。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
适配器模式应用在新旧接口不一致的情况,作用是不重构旧接口,继续使用之前的接口,无感追加新功能;代理提供的接口和原本的接口是一样的,作用是不把实现直接暴露给client

  • 1
    点赞
  • 1
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:黑客帝国 设计师:我叫白小胖 返回首页

打赏作者

おうせき碩

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值