jtahstu的博客

Git仓库   英文博客  

最新碎语:以后没事写写小的知识点吧

您的位置:jtahstu的博客 >笔记> 设计模式从入门到放弃 - 适配器模式

设计模式从入门到放弃 - 适配器模式

模式定义

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

模式解析

    你想使用一个已经存在的适配器模式,而他的接口不符合你的需求你想创建 一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作你想使用一些已经存在的子类,但是不可能对每一个都进行子类化已一匹配他们的接口, 对象适配器可以适配他的父类接口。 适配器如同一个常见的变压器,也如同电脑的变压器和插线板之间的电源连接线,他们虽然都是3相的,但是电脑后面的插孔却不能直接插到插线板上。

    适配器模式有类的适配器模式对象的适配器模式两种不同的形式。

类适配器模式

    类的适配器模式把适配的类的API转换成为目标类的API。

      在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用 Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee 是继承关系,这决定了这个适配器模式是类的:

  模式所涉及的角色有:

        目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。

        源(Adapee)角色:现在需要适配的接口。

        适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

类适配器UML图

java代码演示

//Robot.java
public interface Robot {
	public void cry();

	public void move();
}

//Dog.java
public class Dog {
	public void wang() {
		System.out.println("狗汪汪叫!");
	}

	public void run() {
		System.out.println("狗快快跑!");
	}
}

//DogAdapter.java
public class DogAdapter extends Dog implements Robot {
	public void cry() {
		System.out.print("机器人模仿:");
		super.wang();
	}

	public void move() {
		System.out.print("机器人模仿:");
		super.run();
	}
}

//Client.java
public class Client {
	public static void main(String args[]) {
		Robot robot = (Robot) XMLUtil.getBean();
		robot.cry();
		robot.move();
	}
}

//XMLUtil.java
//import javax.xml.parsers.*;
//import org.w3c.dom.*;
//import org.xml.sax.SAXException;
//import java.io.*;
//
//public class XMLUtil {
//	// 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
//	public static Object getBean() {
//		try {
//			// 创建文档对象
//			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
//			DocumentBuilder builder = dFactory.newDocumentBuilder();
//			Document doc;
//			doc = builder.parse(new File("config.xml"));
//
//			// 获取包含类名的文本节点
//			NodeList nl = doc.getElementsByTagName("className");
//			Node classNode = nl.item(0).getFirstChild();
//			String cName = classNode.getNodeValue();
//
//			// 通过类名生成实例对象并将其返回
//			Class c = Class.forName(cName);
//			Object obj = c.newInstance();
//			return obj;
//		} catch (Exception e) {
//			e.printStackTrace();
//			return null;
//		}
//	}
//}

//config.xml
<?xml version="1.0"?>
<config>
    <className>DogAdapter</className>
</config>

对象适配器模式

   与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。

    从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用 Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee 的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。

对象适配器UML图


java代码演示

//DataOperation.java
public abstract class DataOperation {
	private String password;

	public void setPassword(String password) {
		this.password = password;
	}

	public String getPassword() {
		return this.password;
	}

	public abstract String doEncrypt(int key, String ps);
}

//Caesar.java
public final class Caesar {
	public String doEncrypt(int key, String ps) {
		String es = "";
		for (int i = 0; i < ps.length(); i++) {
			char c = ps.charAt(i);
			if (c >= 'a' && c <= 'z') {
				c += key % 26;
				if (c > 'z')
					c -= 26;
				if (c < 'a')
					c += 26;
			}
			if (c >= 'A' && c <= 'Z') {
				c += key % 26;
				if (c > 'Z')
					c -= 26;
				if (c < 'A')
					c += 26;
			} es += c;
		}
		return es;
	}
}

//CipherAdapter.java
public class CipherAdapter extends DataOperation {
	private Caesar cipher;

	public CipherAdapter() {
		cipher = new Caesar();
	}

	public String doEncrypt(int key, String ps) {
		return cipher.doEncrypt(key, ps);
	}
}

//NewCipher.java
public final class NewCipher {
	public String doEncrypt(int key, String ps) {
		String es = "";
		for (int i = 0; i < ps.length(); i++) {
			String c = String.valueOf(ps.charAt(i) % key);
			es += c;
		}
		return es;
	}
}

//NewCipherAdapter.java
public class NewCipherAdapter extends DataOperation {
	private NewCipher cipher;

	public NewCipherAdapter() {
		cipher = new NewCipher();
	}

	public String doEncrypt(int key, String ps) {
		return cipher.doEncrypt(key, ps);
	}
}

//Client.java
public class Client {
	public static void main(String args[]) {
		DataOperation dao = (DataOperation) XMLUtil.getBean();
		dao.setPassword("sunnyLiu");
		String ps = dao.getPassword();
		String es = dao.doEncrypt(6, ps);
		System.out.println("明文为:" + ps);
		System.out.println("密文为:" + es);
	}
}

//XMLUtil.java
//import javax.xml.parsers.*;
//import org.w3c.dom.*;
//import org.xml.sax.SAXException;
//import java.io.*;
//
//public class XMLUtil {
//	// 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
//	public static Object getBean() {
//		try {
//			// 创建文档对象
//			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
//			DocumentBuilder builder = dFactory.newDocumentBuilder();
//			Document doc;
//			doc = builder.parse(new File("config.xml"));
//
//			// 获取包含类名的文本节点
//			NodeList nl = doc.getElementsByTagName("className");
//			Node classNode = nl.item(0).getFirstChild();
//			String cName = classNode.getNodeValue();
//
//			// 通过类名生成实例对象并将其返回
//			Class c = Class.forName(cName);
//			Object obj = c.newInstance();
//			return obj;
//		} catch (Exception e) {
//			e.printStackTrace();
//			return null;
//		}
//	}
//}

//config.xml
<?xml version="1.0"?>
<config>
    <className>NewCipherAdapter</className>
</config>

PHP代码演示

    类适配器使用的是继承

<?php

/**
 * 目标角色
 */
interface Target {

	/**
	 * 源类也有的方法1
	 */
	public function sampleMethod1();

	/**
	 * 源类没有的方法2
	 */
	public function sampleMethod2();
}

/**
 * 源角色
 */
class Adaptee {

	/**
	 * 源类含有的方法
	 */
	public function sampleMethod1() {
		echo 'Adaptee sampleMethod1 <br />';
	}

}

/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {

	/**
	 * 源类中没有sampleMethod2方法,在此补充
	 */
	public function sampleMethod2() {
		echo 'Adapter sampleMethod2 <br />';
	}

}

class Client {

	/**
	 * Main program.
	 */
	public static function main() {
		$adapter = new Adapter();
		$adapter -> sampleMethod1();
		$adapter -> sampleMethod2();

	}

}

Client::main();
?>

    对象适配器使用的是委派

<?php

/**
 * 目标角色
 */
interface Target {

	/**
	 * 源类也有的方法1
	 */
	public function sampleMethod1();

	/**
	 * 源类没有的方法2
	 */
	public function sampleMethod2();
}

/**
 * 源角色
 */
class Adaptee {

	/**
	 * 源类含有的方法
	 */
	public function sampleMethod1() {
		echo 'Adaptee sampleMethod1 <br />';
	}

}

/**
 * 类适配器角色
 */
class Adapter implements Target {

	private $_adaptee;

	public function __construct(Adaptee $adaptee) {
		$this -> _adaptee = $adaptee;
	}

	/**
	 * 委派调用Adaptee的sampleMethod1方法
	 */
	public function sampleMethod1() {
		$this -> _adaptee -> sampleMethod1();
	}

	/**
	 * 源类中没有sampleMethod2方法,在此补充
	 */
	public function sampleMethod2() {
		echo 'Adapter sampleMethod2 <br />';
	}

}

class Client {

	/**
	 * Main program.
	 */
	public static function main() {
		$adaptee = new Adaptee();
		$adapter = new Adapter($adaptee);
		$adapter -> sampleMethod1();
		$adapter -> sampleMethod2();

	}

}

Client::main();
?>


类适配器和对象适配器的权衡

  类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式

  对于类适配器,由于适配器直接继承了Adapter,使得适配器不能和Adapter的子类一起工作,因为继承是静态的关系,当适配器继承了Adapter后,就不可能再去处理  Adapter的子类了。

  对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。

  对于类适配器,适配器可以重定义Adapter的部分行为,相当于子类覆盖父类的部分实现方法。

  对于对象适配器,要重定义Adapter的行为比较困难,这种情况下,需要定义Adapter的子类来实现重定义,然后让适配器组合子类。虽然重定义Adapter的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。

  对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adapter。

  对于对象适配器,需要额外的引用来间接得到Adapter。

  建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。

缺省适配器

    缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省是适配模式在JAVA语言中有着特殊的应用。

缺省适配模式的结构

  缺省适配模式是一种“平庸”化的适配器模式。

  

public interface AbstractService {
    public void serviceOperation1();
    public int serviceOperation2();
    public String serviceOperation3();
}

public class ServiceAdapter implements AbstractService{

    @Override
    public void serviceOperation1() {
    }

    @Override
    public int serviceOperation2() {
        return 0;
    }

    @Override
    public String serviceOperation3() {
        return null;
    }

}
    可以看到,接口AbstractService要求定义三个方法,分别是serviceOperation1()、serviceOperation2()、serviceOperation3();而抽象适配器类ServiceAdapter则为这三种方法都提供了平庸的实现。因此,任何继承自抽象类ServiceAdapter的具体类都可以选择它所需要的方法实现,而不必理会其他的不需要的方法。

  适配器模式的用意是要改变源的接口,以便于目标接口相容。缺省适配的用意稍有不同,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。

  在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法。

适配器模式的优点

  • 更好的复用性

  系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

  • 更好的扩展性

  在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

适配器模式的缺点

  过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

适配器模式适用环境

    系统需要使用现有的类,而这些类的接口不符合系统的需要。

    想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

参考

  1. 博客园 http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html

  2. 百度百科 http://baike.baidu.com/view/3371585.htm

  3. phppan http://www.phppan.com/2010/07/php-design-pattern-10-adapter/
  4. 课本





---

本文章采用 知识共享署名2.5中国大陆许可协议 进行许可,欢迎转载,演绎或用于商业目的。

---

二维码加载中...

扫一扫移动端访问O(∩_∩)O

发表评论

66 + 60 =
路人甲 表情
看不清楚?点图切换 Ctrl+Enter快速提交
正在加载中……