一、适配器模式

作用:

​ 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题

​ 将一个类的接口转换为客户希望的另外一个接口。Adapter模式使得原本接口不兼容而不能在一起工作的那些类可以在一起工作

​ 类似于USB接口转换器

1.1 代码实现(类适配器)

//客户端类: 想上网,插不上网线
public class Computer {

    //我们的电脑需要插上转接器,才能上网
    public void net(NetToUsb adpter){
        //上网的具体实现,找一个转接头
        adpter.handleRequest();
    }
}
//要被适配的类(网线)
public class Adaptee {
    public void request(){
        System.out.println("连接网线上网");
    }
}
//接口转换器的抽象实现
public interface NetToUsb {
    //作用:处理请求 拔网线插到USB上
    public void handleRequest();
}
/**
 * 1.继承 (类适配器,单继承)
 * 2.组合 (对象适配器,常用)
 */
//真正的适配器   需要连接USB,连接网线
public class Adapter extends Adaptee implements NetToUsb{
    @Override
    public void handleRequest() {
        super.request(); //可以上网了
    }
}
public class Test {
    public static void main(String[] args) {
        //需要电脑、网线、适配器才能上网
        Computer computer = new Computer();//电脑
        
        //Adaptee adaptee = new Adaptee(); //类适配器继承了网线,所以不需要new
        
        Adapter adapter = new Adapter(); //适配器
        //电脑插上适配器,上网
        computer.net(adapter);
    }
}

这样电脑原本无法直连网线,现在也可以上网了

类适配器的缺点:

​ 1、对于java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者

​ 2、在java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性

1.2 代码实现(对象适配器)

只需要修改Adapter类就行了

/**
 * 1.继承 (类适配器,单继承)
 * 2.组合 (对象适配器,常用)
 */
//真正的适配器   需要连接USB,连接网线
public class Adapter2 implements NetToUsb{
    private Adaptee adaptee;

    public Adapter2(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void handleRequest() {
        adaptee.request(); //可以上网了
    }
}
public class Test {
    public static void main(String[] args) {
        //需要电脑、网线、适配器才能上网
        Adaptee adaptee = new Adaptee(); //网线
        Computer computer = new Computer(); //电脑

        //适配器插上网线
        Adapter2 adapter2 = new Adapter2(adaptee);//适配器
        //电脑连接适配器上网
        computer.net(adapter2);

    }
}

对象适配器的优点:

​ 1、一个对象适配器可以把多个不同的适配者适配到同一个目标

​ 2、可以适配一个适配者的子类,由于适配者和适配器之间是关联关系,根据“里氏代换原则”,适配者的子类也可以通过该适配器进行适配

适配器模式的适用场景:

​ 例如: InputStreamReader(InputStream) 字节流转字符流

​ 系统升级、代码维护等

二、桥接模式

桥接模式是将抽象部分与他的实现部分分离,使他们都可以独立的变化,它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(interface)模式

image-20220517192619897

2.1代码实现

品牌实现:

public interface Brand {
    void info();
}
public class Apple implements Brand {
    @Override
    public void info() {
        System.out.print("苹果");
    }
}
public class Lenovo implements Brand{
    @Override
    public void info() {
        System.out.print("联想");
    }
}

产品类型实现:

//抽象的电脑类型
public abstract class Computer {
    //推荐使用组合方式,更灵活方便
    public Computer(Brand brand) {
        this.brand = brand;
    }
    
    protected Brand brand;   //protected子类可继承

    public void info(){
        //自带品牌
        brand.info();
    }
}

//为了方便将实现类放到一起了
class Desktop extends Computer{

    public Desktop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("台式机");
    }
}

class Laptop extends Computer{

    public Laptop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("笔记本");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        //苹果笔记本
        Computer computer = new Laptop(new Apple());
        computer.info();
        //联想台式机
        Computer computer1 = new Desktop(new Lenovo());
        computer1.info();
    }
}

image-20220517193005059

2.2应用场景

Java语言通过java虚拟机实现平台无关性

AWT中的Peer架构(写的代码是一样的,在不同系统实现的效果是不一样的)

JDBC驱动程序也是桥接模式应用之一(一个JDBC可以绑定各种不同的数据库)

三、代理模式

为什么要学习代理模式?因为这里SpringAOP(面向切面编程)的底层

代理模式的分类:

  • 静态代理
  • 动态代理

3.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作
  • 客户:访问代理对象

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共业务就交给代理角色,实现业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会降低

代码步骤:

1、接口(抽象角色)

//租房接口
public interface Rent {
    void rent();
}

2、真实角色

//房东出租房子,实现接口
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}

3、代理角色

//代理房东出租房子
//组合方法
public class Proxy {
    private Host host;

    public Proxy(Host host) {
        this.host = host;
    }

    public Proxy() {
    }
    
    public void rent(){
        seeHouse();
        host.rent();
        signDocument();
    }
    
    //除了租房子,代理还可以添加一些业务
    public void seeHouse(){
        System.out.println("中介带你看房子");
    }
    
    public void signDocument(){
        System.out.println("中介签合同");
    }
}

4、客户

//租房子
public class Client {
    public static void main(String[] args) {
        //房东要出租房子
        Host host = new Host();
        //租户找中介租房子
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

image-20220519143449094

image-20220519150842949

3.2 代理模式加深理解

理解业务扩展时,代理模式的优点:

并不会去修改原来的代码,方便集中管理

1、接口

public interface UserService {
    void add();
    void delete();
}

2、原业务

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加");
    }

    @Override
    public void delete() {
        System.out.println("删除");
    }
}

3、原业务扩展日志功能

//现在需要扩展业务
//添加日志功能
public class UserServiceProxy implements UserService {

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    public void log(String msg){
        System.out.println("日志:"+msg);
    }
}

4、客户端

public class Client {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserServiceProxy userServiceProxy = new UserServiceProxy();
        userServiceProxy.setUserService(userService);

        userServiceProxy.add();
        userServiceProxy.delete();
    }

image-20220519145652872

四、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是直接写的
  • 动态代理可以分为两大类:基于接口的、基于类的
    • 基于接口的:JDK动态代理
    • 基于类的:cglib
    • Java字节码实现:javassist

需要了解两个类:Proxy(代理)、InvocationHandler(调用处理程序)

InvocationHandler

InvocationHandler是由代理实例的调用处理程序的接口

每个代理实例都有一个关联的调用处理程序(invocationhandler),当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法

简单来说,实现这个接口以及其中的invoke方法之后,当我们调用动态生成的代理类的方法时,就是在调用这个invoke方法(反射)

所以我们在这个invoke方法中处理被代理对象的方法,并扩展相关业务

Proxy

该类中有四个静态方法

这里介绍 newProxyInstance()方法,该方法用来动态生成代理类,并返回代理类的实例

4.1 代码实现

1、接口

//租房接口
public interface Rent {
    void rent();
}

2、真实角色

//房东出租房子,实现接口
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}

3、动态生成代理类

public class ProxyInvocationHandler implements InvocationHandler {
    /**
     *
     * @param proxy 动态代理类
     *
     * @param method 代理实例的方法
     *
     * @param args 代理实例的方法的参数
     *
     * @return 返回运行结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //当我们调用动态代理类的方法时,就会调用这一方法
        //由我们来编写,相当于代理类的实现
        Object p = method.invoke(rent,args);
        return p;
    }

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //使用组合方法,实现Proxy类
    //被代理类的接口
    private Rent rent;

    //Proxy类返回动态生成的代理实例
    //第1、2个参数是固定的,只需要修改第二个参数就行
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

}

4、客户端

public class Client {
    public static void main(String[] args) {
        //真实对象
        Host host = new Host();

        //代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

代码实现流程:

  • 需要一个被代理对象(真实角色)
  • 需要一个类(代理类)实现invocationhandler,以及其中的invoke方法
  • 调用Proxy类,共有三个参数
    • classloader
    • 被代理类的接口类
    • invocationHandler(调用处理程序)的实例

4.2 代码实现(更灵活)

4.1 实现的代码中的Rent参数是固定的,这个 动态生成代理类的程序只能被Rent类使用,那么能不能把他变成万能的工具类?

public class ProxyInvocationHandler implements InvocationHandler {
   
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object p = method.invoke(object,args);
        return p;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    //Object 是所有类的基类
    private Object object;

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }
    
    //下面可以添加想要扩展的方法
}

上面的代码就是一个万能的工具类了

而且,反射是十分灵活的,假设我需要添加一个日志功能:

1、动态生成代理类

public class ProxyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object p = method.invoke(object,args);
        return p;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    //Object 是所有类的基类
    private Object object;

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }

    //下面可以添加想要扩展的方法
    public void log(String msg){
        System.out.println("日志:"+msg);
    }
}

2、客户端

public class Client {
    public static void main(String[] args) {
        //真实对象不可缺少(被代理对象)
        UserServiceImpl userServiceimpl = new UserServiceImpl();

        //动态生成代理对象,返回代理实例
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setObject(userServiceimpl);

        UserService proxy = (UserService) pih.getProxy();
        proxy.add();
        proxy.delete();
    }
}

image-20220519162934075

所以说反射十分重要

动态代理的好处:

  • 静态代理的好处都有
  • 一个动态代理类代理的是一个借口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一接口即可

注意:为什么只能对应同一业务?

因为动态代理类,其所扩展的的对应业务一般只对应同一业务,对其他业务一般不兼容

当然在同一业务中,可能也会存在同一方法不同内容的情况,但是一般利用反射就能解决情况