适配器模式

《大话设计模式》第17章读书笔记,介绍适配器模式

Posted by GrayWind on November 17, 2018

适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。也就是系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。

在GoF的设计模式中,对适配器模式讲了两种类型:类适配器模式和对象适配器模式。由于类适配器模式通过多次继承对一个接口与另一个接口进行匹配,而Java不支持多重继承,所以主要讲对象适配器。

adapter

Target是客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口

public class Target {
	public void request() {
		System.out.println("普通请求");
	}
}

Adaptee需要适配的类代码如下

public class Adaptee {
	public void specificRequest() {
		System.out.println("特殊请求");
	}
}

Adapter通过在内部包装一个Adaptee对象,把源接口转换成目标接口,代码如下

public class Adapter extends Target{
	private Adaptee adaptee = new Adaptee();
	
	@Override
	public void request() {
		adaptee.specificRequest();
	}
}

客户端代码

	public static void main(String[] args) {
		Target target = new Adapter();//对客户端来说,调用的是Target.request()
		target.request();//特殊请求
	}

当两个类所做的事情相同或相似,但是具有不同的接口时要使用适配器模式,这样客户端代码可以统一调用同一接口,更加简单、直接、紧凑。但是,最好还是在最初设计时就将接口设计的一致或者重构统一接口,但是对于双方都不太容易修改的时候再考虑适配器模式,比如使用第三方组件的接口与自己系统接口不相同,没必要为了迎合它修改自己的接口。

以姚明刚到NBA时为例,他当时还无法直接与其他人沟通,需要依靠翻译,翻译就是适配器。假设现在教练正在对球员布置战术,首先定义球员抽象类,他们有进攻、防守两个抽象方法,教练只能借助球员这个抽象类来发布命令

public abstract class Player {
	protected String name;
	public Player(String name) {
		this.name = name;
	}
	
	public abstract void attack();
	public abstract void defense();
}

前锋、后卫、中锋是具体的球员类,他们分别实现了对应的进攻和防守方法

public class Forwards extends Player {

	public Forwards(String name) {
		super(name);
	}

	@Override
	public void attack() {
		System.out.printf("前锋%s进攻\n", name);
	}

	@Override
	public void defense() {
		System.out.printf("前锋%s防守\n", name);
	}

}

public class Center extends Player {

	public Center(String name) {
		super(name);
	}

	@Override
	public void attack() {
		System.out.printf("中锋%s进攻\n", name);
	}

	@Override
	public void defense() {
		System.out.printf("中锋%s防守\n", name);
	}

}

public class Guards extends Player {

	public Guards(String name) {
		super(name);
	}

	@Override
	public void attack() {
		System.out.printf("后卫%s进攻\n", name);
	}

	@Override
	public void defense() {
		System.out.printf("后卫%s防守\n", name);
	}

}

但是对于姚明这样无法直接英语交流的外籍中锋,教练无法直接对他发出命令

public class ForeignCenter {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public void attack() {
		System.out.printf("外籍中锋%s进攻\n", name);
	}

	public void defense() {
		System.out.printf("外籍中锋%s防守\n", name);
	}
}

所以,此时需要增加翻译来完成从外籍中锋到球员的适配,他包含一个外籍中锋对象

public class Translator extends Player {
	private ForeignCenter center;

	public Translator(String name) {
		super(name);
		center = new ForeignCenter();
		center.setName(name);
	}

	@Override
	public void attack() {
		center.attack();
	}

	@Override
	public void defense() {
		center.defense();
	}

}

客户端代码如下,通过Translator来生成Player接口完成调用

public class Test {
	public static void main(String[] args) {
		Player b = new Forwards("巴蒂尔");
		b.attack();//前锋巴蒂尔进攻
		Player t = new Guards("麦迪");
		t.attack();//后卫麦迪进攻
		
		Player m = new Center("穆大叔");
		m.attack();//中锋穆大叔进攻
		m.defense();//中锋穆大叔防守
		
		Player ym = new Translator("姚明");
		ym.attack();//外籍中锋姚明进攻
		ym.defense();//外籍中锋姚明防守
	}
}