狐言不胡言

导航

指方画圆之Java设计模式:适配器模式

应用场景

使用者依赖的接口与提供者的接口不匹配时,就加一层适配,而不修改两端的代码
生活中使用的风扇,假设插头是两孔的,但是我们家里墙上的插座只有三孔的,这个时候就需要一个适配器了,这个适配器就是外面买的插座,以上场景为例,写一个例子:

三孔的插座:

public class ThreeOutlet {
    /** 三孔插座 */
    public void link(String positive, String negative, String earthWire) {
        System.out.println(String.format("多一根地线,保证安全用电:%s %s %s", positive, negative, earthWire));
    }
}

我们使用电风扇需要启动和关闭的接口:

public interface TwoOutlet {
    /** 启动 */
    void power(String positive, String negative);

    /** 关闭 */
    void shutDown();
}

适配插座,外面买来的,可以使用两孔的、三孔的:

public class Adapter1 extends ThreeOutlet implements TwoOutlet {

    private String ew = "多一根地线";	// 私有内置地线

    @Override
    public void power(String positive, String negative) {
        link(positive, negative, ew);
    }

    @Override
    public void shutDown() {
        System.out.println("拔掉插头");
    }

    @Override
    public void link(String positive, String negative, String earthWire) {
        super.link(positive, negative, earthWire);
    }
}

电风扇类:

public class Fanner {

    private TwoOutlet outlet;

    public void run() {
        outlet.power("1", "0");
        System.out.println("获得动力");
        System.out.println("风扇运转");
        outlet.shutDown();
        System.out.println("关闭电力,风扇停止运转");
    }

    public void setOutlet(TwoOutlet outlet) {
        this.outlet = outlet;
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        Fanner fanner = new Fanner();
        Adapter1 adapter1 = new Adapter1();
        fanner.setOutlet(adapter1);
        fanner.run();
    }
}

在这里插入图片描述
类图:
在这里插入图片描述

适配器模式

定义

把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

意图

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

主要解决问题

要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的

何时使用

  1. 系统需要使用现有的类,而此类的接口不符合系统的需要
  2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口
  3. 通过接口转换,将一个类插入另一个类系中

优缺点

优点:

  1. 可以让任何两个没有关联的类一起运行
  2. 提高了类的复用
  3. 增加了类的透明度
  4. 灵活性好

缺点:

  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握
  2. 由于JAVA至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类

类图:
在这里插入图片描述
涉及到的角色:

  1. 目标(Target)角色:这就是所期待的接口,目标可以是具体的或抽象的类
  2. 源(Adaptee)角色:现有需要适配的接口
  3. 适配器(Adapter)角色:适配器类是本模式的核心;适配器将源接口转换为目标接口;显然,这一角色不能是接口,而必须是具体类

类的适配器模式的结构:
Target接口:

public interface Target {

    /** 源类也有的方法 */
    void sampleOperation1();

    /** 源类没有的方法 */
    void sampleOperation2();
}

Adaptee源角色:

public class Adaptee {

    public void sampleOperation1(){
        System.out.println("源类也有的方法");
    }
}

Adapter适配器角色:

public class Adapter extends Adaptee implements Target {

    /** 源类和目标类都有的方法 */
    @Override
    public void sampleOperation1() {
        super.sampleOperation1();
    }

    /** 源类没有的方法,期望有这个方法 */
    @Override
    public void sampleOperation2() {

    }
}

对象的适配器模式的结构:
Target接口:

public interface Target {

    /** 源类也有的方法 */
    void sampleOperation1();

    /** 源类没有的方法 */
    void sampleOperation2();
}

Adaptee源角色:

public class Adaptee {

    public void sampleOperation1(){
        System.out.println("源类也有的方法");
    }
}

Adapter适配器角色:

public class Adapter implements Target {
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    /** 源类和目标类都有的方法 */
    @Override
    public void sampleOperation1() {
        this.adaptee.sampleOperation1();
    }

    /** 源类没有的方法,期望有这个方法 */
    @Override
    public void sampleOperation2() {
        System.out.println("适配一个方法");
    }
}

需要注意的是,适配器不是在开始设计的时候用的,而是在解决现有的项目中的问题

指鹿为马VS指方为圆

指鹿为马

赵高指鹿为马的故事都应该听说过,下面以适配器模式做个小例子:

现在需要的是一匹马:

public interface NeedHorse {

    /** 需要一匹马 */
    void needHorse();
}

但是有的是一只鹿:

public class DeerAdaptee {

    public void deer() {
        System.out.println("我是一头鹿,有人要指鹿为马!");
    }
}

把鹿适配为马,指鹿为马:

public class DeerChangeAdapter implements NeedHorse {

    private DeerAdaptee deerAdaptee;

    public DeerChangeAdapter(DeerAdaptee deerAdaptee) {
        this.deerAdaptee = deerAdaptee;
        this.deerAdaptee.deer();
    }

    public void deer() {
        System.out.println("我是一头鹿,有人要指鹿为马!");
    }

    @Override
    public void needHorse() {
        System.out.println("我是一匹马(其实我的真实身份是一只鹿)");
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        System.out.println("这个是鹿还是马?");
        DeerAdaptee adaptee = new DeerAdaptee();
        DeerChangeAdapter adapter = new DeerChangeAdapter(adaptee);
        adapter.needHorse();
    }
}

在这里插入图片描述
类图:
在这里插入图片描述

指方为圆

古有指鹿为马,现有指方为圆,nice:
我们需要的是一个球:

public interface BallIF {

    /** 计算体积 */
    Double calculateVolume();

    /** 计算面积 */
    Double calculateAcreage();

    /** 半径取值 */
    Double getRadius();

    /** 半径赋值 */
    Double setRadius(double radius);
}

现在有的是一个立方体:

public class Cube {

    private Double width;

    public Cube(Double width) {
        this.width = width;
    }

    /** 计算体积 */
    public Double calculateVolume() {
        return width * width * width;
    }

    /** 计算面积 */
    public Double calculateAcreage() {
        return width * width;
    }

    /** 长度取值 */
    public Double getWidth() {
        return width;
    }

    /** 长度赋值 */
    public void setWidth(Double width) {
        this.width = width;
    }
}

适配器适配一下,指方为圆:

public class MagicFinger implements BallIF {

    private double radius = 0;

    private static double PI = 3.14d;

    private Cube adaptee;

    public MagicFinger(Cube adaptee) {
        super();
        this.adaptee = adaptee;
        radius = this.adaptee.getWidth();
    }

    @Override
    public Double calculateVolume() {
        return  (4.0d/3.0d) * PI * (radius * radius);
    }

    @Override
    public Double calculateAcreage() {
        return PI * 4.0d * (radius * radius * radius);
    }

    @Override
    public Double getRadius() {
        return radius;
    }

    @Override
    public Double setRadius(double radius) {
        return this.radius = radius;
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        Cube cube = new Cube(2.0);
        MagicFinger finger = new MagicFinger(cube);
        System.out.println("半径:" + finger.getRadius());
        System.out.println("体积:" + finger.calculateVolume());
        System.out.println("面积:" + finger.calculateAcreage());
    }
}

在这里插入图片描述
类图:
在这里插入图片描述
如果想有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式

posted on 2021-04-17 10:34  狐言不胡言  阅读(123)  评论(0编辑  收藏  举报