结合JDK源码看设计模式——适配器模式
定义:
将一个类的接口转换成客户期望的另外一个接口(重点理解适配的这两个字),使得接口不兼容的类可以一起工作
适用场景:
- 已经存在的类,它的方法和需求不匹配的时候
- 在软件维护阶段考虑的设计模式
详解
首先来从生活中的常见场景来看,一个电源插座输出都是220v,而我们一些电子设备,比如手机,mp3,mp4,所需要的电压不一样,也不可能直接就是220接上,这就需要一个中间的转换器,每个厂家不同,对应的充电线也有可能不同。这个不同的充电线就可以理解为一个适配器。而220v的输出电压可以看做是我们做好的一套系统,只不过对应到不同客户就需要不同的适配器。下面分为三个模块来讲解
1.类适配器
输出的电压类
public class ac220 { public int outputac220v(){ int output = 220; system.out.println("输出交流电"+output+"v"); return output; } }
5v电压接口
public interface dc5 { int outputdc5v(); }
适配器类
public class poweradapter extends ac220 implements dc5{ @override public int outputdc5v() { int adapterinput=outputac220v(); int adapteroutput = adapterinput/44; system.out.println("使用poweradapter输入ac:"+adapterinput+"v"+"输出dc:"+adapteroutput+"v"); return adapteroutput; } }
测试代码:
public class test { public static void main(string[] args) { dc5 dc5=new poweradapter(); system.out.println(dc5.outputdc5v()); } }
输出结果
可能很多人看到这就会问,这不就是装饰者模式吗?这个不就相当于扩展功能吗,其实呢这两个模式所处的阶段不同,一个是在软件设计的时候需要考虑的,这个呢是在软件后续维护的时候所考虑的。第二个其实就是最大的区别:装饰者和被装饰者之间的接口相同,而适配器和被适配器之间的接口是不相同的(当然有些特殊情况是相同的)。假如我是另外一个电子设备,我需要3v的接口,那么我们只需要再定义一个dc3接口,修改poweradapter里面的适配方法。但是我这个厂家并不再需要5v的接口了,所以我这边有的其实只是dc3不是dc5.看看现在的uml类图
还是和装饰者有区别的,当然你要是通过装饰者来实现上述功能一样能实现
2.对象适配器
上述uml图中我们可以看出ac220和poweradapter连接太过紧密了,如果我们这是个很复杂的系统,那这样做无疑让我们的适配器加载会很慢,毕竟子类要想初始化完,就必须要父类先初始化完,所以我们可以不用继承,而使用成员变量的方式来解决,即修改poweradapter的代码
public class poweradapter implements dc5,dc3{ private ac220 ac220=new ac220(); private int adapterinput=ac220.outputac220v(); @override public int outputdc5v() { int adapteroutput = adapterinput/44; system.out.println("使用poweradapter输入ac:"+adapterinput+"v"+"输出dc:"+adapteroutput+"v"); return adapteroutput; } @override public int outputdc3v() { int adapteroutput=adapterinput/73; system.out.println("使用poweradapter输入ac:"+adapterinput+"v"+"输出dc:"+adapteroutput+"v"); return adapteroutput; } }
uml类图
从继承变成了组合,这就是对象适配
3.jdk解读
xmladapter就是一个最典型的适配器,下面我们来看代码具体分析
定义一个学生类,将学生类序列化成xml文件
import javax.xml.bind.annotation.*; import javax.xml.bind.annotation.adapters.xmljavatypeadapter; import java.util.date; @xmltype(proporder={"id","name","birthday"}) //指定序列成的xml节点顺序 @xmlaccessortype(value=xmlaccesstype.field) //访问类型改为字段 @xmlrootelement public class student { @xmlelement private string id; @xmlelement private string name; @xmljavatypeadapter(value=dateadapter.class) @xmlelement private date birthday; public string getid() { return id; } public void setid(string id) { this.id = id; } public string getname() { return name; } public void setname(string name) { this.name = name; } public date getbirthday() { return birthday; } public void setbirthday(date birthday) { this.birthday = birthday; } @override public string tostring() { return "student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", birthday=" + birthday + '}'; } }
date适配器
import java.text.simpledateformat; import java.util.date; import javax.xml.bind.annotation.adapters.xmladapter; public class dateadapter extends xmladapter<string, date> { //反序列化成日期对象date @override public date unmarshal(string str) throws exception { simpledateformat format = getsimpledateformat("yyyy-mm hh:mm:ss"); return str==null ? null:format.parse(str); } //序列化成xml @override public string marshal(date date) throws exception { simpledateformat format = getsimpledateformat("yyyy-mm hh:mm:ss"); return date==null ? "":format.format(date); } private simpledateformat getsimpledateformat(string pattern){ simpledateformat format = new simpledateformat(pattern); return format; } }
编写测试类
import javax.xml.bind.jaxbcontext; import javax.xml.bind.marshaller; import javax.xml.bind.unmarshaller; import java.io.*; import java.util.date; public class test { public static void main(string[] args) { dateadapter da=new dateadapter(); student stu = new student(); stu.setid("1"); stu.setname("方块人"); stu.setbirthday(new date()); try { jaxbcontext context = jaxbcontext.newinstance(student.class); marshaller marshaller = context.createmarshaller(); marshaller.setproperty(marshaller.jaxb_formatted_output, true); marshaller.setproperty(marshaller.jaxb_encoding, "utf-8"); //marshaller.marshal(stu, system.out); stringwriter writer = new stringwriter(); marshaller.marshal(stu, writer); system.out.println(writer.tostring()); //反序列化 unmarshaller unmarshaller = context.createunmarshaller(); stringreader reader = new stringreader(writer.tostring()); student stu2 = (student) unmarshaller.unmarshal(reader); } catch (exception e) { e.getmessage(); e.printstacktrace(); } } }
输出结果:
注意看birthday这个date类型,我们已经在转化成xml文件的时候将日期格式变化成功,如果是没有进行日期适配器转换的话的输出结果是
这样就不是我们想要的格式
xmladapter源码:
public abstract class xmladapter<valuetype,boundtype> { /** * do-nothing constructor for the derived classes. */ protected xmladapter() {} /** * convert a value type to a bound type. * * @param v * the value to be converted. can be null. * @throws exception * if there's an error during the conversion. the caller is responsible for * reporting the error to the user through {@link javax.xml.bind.validationeventhandler}. */ public abstract boundtype unmarshal(valuetype v) throws exception; /** * convert a bound type to a value type. * * @param v * the value to be convereted. can be null. * @throws exception * if there's an error during the conversion. the caller is responsible for * reporting the error to the user through {@link javax.xml.bind.validationeventhandler}. */ public abstract valuetype marshal(boundtype v) throws exception; }
有两个方法需要实现,一个是反序列化,另外一个是序列化。相信你看懂了适配器模式之后,也能理解这个类中的方法含义
总结:
适配器模式更多的是提供不同接口给不同的厂家,适配器我们知道怎么实现之后,更多的是在适用场景中去思考。这样就会对适配器模式有更加深刻的理解。