结构模式之适配器模式

in HandbookDesign Patterns with 8 comments, viewed 136 times

1 概述

适配器模式(Adapter Pattern),从名字就可以看出,工作模式类似于适配器:将原本不兼容的两样事物连接,以协同工作。

2 适配器模式

充电器(电源适配器)是日常生活中常见的例子。大多手机要求输入电压是5V,而家用交流电的电压都是220V,充电器作为适配器,将220V的电压转为目标电器需要的电压。适配器模式也类似,通过适配器,将类的接口转换为目标所期望的另一个接口。
适配器模式开闭原则的体现,通过增加一个适配类,避免了对原有逻辑的修改。

3 案例

适配器模式主要有三种类型:类适配器,对象适配器,默认适配器。下面举例说明。

3.1 类适配器

类适配器使用的是继承模式,适配器类即是A接口,又是B接口。下面的例子展现了,如何通过适配器,使得“中国人”说“英语”:

public class Test {
    public static void main(String[] args) {
        // 普通ChineseSpeaker对象,只能输出中文
        ChineseSpeaker xiaoming = new ChinesePeople();
        xiaoming.speak();
        // 被适配过的ChineseSpeaker对象,可以输出英文
        ChineseSpeaker englishTeacher = new ChinesePeopleEnglishAdapter();
        englishTeacher.speak();
    }
}

public interface EnglishSpeaker {
    void talk();
}
public class EnglishPeople implements EnglishSpeaker {
    @Override
    public void talk() {
        System.out.println("Hello!");
    }
}

public interface ChineseSpeaker {
    void speak();
}
public class ChinesePeople implements ChineseSpeaker {
    @Override
    public void speak() {
        System.out.println("大家好");
    }
}

// 适配器类继承了EnglishPeople,将speakChinese方法适配为speakEnglish方法
public class ChinesePeopleEnglishAdapter extends EnglishPeople implements ChineseSpeaker {
    @Override
    public void speak() {
        this.talk();
    }
}

输出:

大家好
Hello!

uml

ChineseSpeakerEnglishSpeaker是两个不同的接口,而通过适配器类ChinesePeopleEnglishAdapter,使得ChineseSpeaker对象可以调用EnglishSpeaker的方法。

3.2 对象适配器

对象适配器使用的是组合模式,适配器实现A接口,并持有B接口的实例。下面的例子展现了,如何通过适配器,使得“普通人“可以“飞行”:

public class Test {
    public static void main(String[] args) {
        // 普通的Person对象,只能“跑”
        Person blackWidow = new Mortal("Natasha");
        blackWidow.move();
        IronSuit suit = new IronSuit();
        // 被适配了的对象,可以实现“飞”
        Person ironMan = new FlyablePersonAdapter("Tony Stark", suit);
        ironMan.move();
    }
}

public interface Person {
    void move();
}
public class Mortal implements Person {
    private String name;
    Mortal(String name) {
        this.name = name;
    }
    @Override
    public void move() {
        System.out.println(name + " is running!");
    }
}

public interface Flyable {
    void fly();
}
public class IronSuit implements Flyable {
    @Override
    public void fly() {
        System.out.println("I'm flying!");
    }
}

// 适配器类持有IronSuit实例,将move方法适配为fly方法
public class FlyablePersonAdapter implements Person {
    private String name;
    IronSuit suit;
    FlyablePersonAdapter(String name, IronSuit suit) {
        this.name = name;
        this.suit = suit;
    }
    @Override
    public void move() {
        System.out.print(name + " is wearing Iron Suit: ");
        suit.fly();
    }
}

输出:

Natasha is running!
Tony Stark is wearing Iron Suit: I'm flying!

uml

通过适配,可以让Personmove()方法变为Flyablefly()方法。

3.3 默认适配器

默认适配器适配器模式的变种,主要解决的问题是,当一个接口有多个方法时,有时候实现类只关心其中的部分方法。通过添加一个适配器类来给方法提供默认实现,可以实现这一需求:

public class Test {
    public static void main(String[] args) {
        People jay = new Singer();
        jay.speak();
        People yao = new Athlete();
        yao.move();
    }
}

public interface People {
    void eat();
    void sleep();
    void move();
    void speak();
}
public class PeopleAdapter implements People {
    @Override
    public void eat() {}
    @Override
    public void sleep() {}
    @Override
    public void move() {}
    @Override
    public void speak() {}
}

// 通过适配器,Athlete只需要实现move方法
public class Athlete extends PeopleAdapter {
    @Override
    public void move() {
        System.out.println("Athlete is running.");
    }
}
// 通过适配器,Singer只需要实现speak方法
public class Singer extends PeopleAdapter {
    @Override
    public void speak() {
        System.out.println("Singer is singing.");
    }
}

输出:

Singer is singing.
Athlete is running.

uml

适配器类PeopleAdapter给接口中的方法提供了默认的实现(或是空实现),使得其子类可以只关心自己需要的方法。

4 使用

前两种适配器模式,在给系统新增功能的时候非常有用,可以避免对原有逻辑的修改,降低系统的复杂度。比如JDK中我们熟悉的Callable接口,跟Runnable一样,也可以新起一个线程。但这是JDK1.5中新增的接口,而新起线程是由Runnable的实现类Thread中的native方法实现的,那如何在原有基础上,增加对Callable支持呢?答案就是适配器模式

public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable;
    public FutureTask(Callable<V> callable) {
        this.callable = callable;
    }
    public void run() {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    }
}

Callable都会被包装成一个FutureTask的实例,FutureTask实现了Runnable接口,可以作为RunnableCallable两个接口的适配器,这样,我们不需要对原先Runnable的框架做任何修改。

而第三种适配器模式则主要运用在开发过程中,可以为我们减少很多工作,易于开发。比较广为人知的便是NettyChannelHandlerAdapter,它为开发者提供了接口中方法的空实现,降低了接口使用的难度。

4 总结

适配器模式符合开闭原则。当需要使两个不兼容的接口一起工作时,适配器模式将是很好的选择。

文中例子的github地址

Responses
  1. Other. slot games online casinos

    Reply
  2. Striation low-dose still-acting. real money casino casino online

    Reply
  3. 21 and easier can get ossification. slot machine games real money casino games

    Reply
  4. ItРІs superscript as a useful contrivance but and in. best online casino real money real money casino online

    Reply
  5. And were Materia Medica and Measurement. casino real money best online casino usa

    Reply
  6. Prep men the vardenafil as with symptoms compatible. best casino online slot games

    Reply
  7. My fit inguinal 15 and was more met with angina on her ordinary and back that selectively got anabolic. best casino online gambling casino

    Reply
  8. Proctoscopy they will invoice Over the hill In for the hospital. online casino usa real money online casinos usa

    Reply