定義: 將一個類的介面轉換成客戶期望的另外一個介面(重點理解適配的這兩個字),使得介面不相容的類可以一起工作適用場景: 詳解 首先來從生活中的常見場景來看,一個電源插座輸出都是220V,而我們一些電子設備,比如手機,MP3,MP4,所需要的電壓不一樣,也不可能直接就是220接上,這就需要一個中間的轉 ...
定義:
將一個類的介面轉換成客戶期望的另外一個介面(重點理解適配的這兩個字),使得介面不相容的類可以一起工作
適用場景:
- 已經存在的類,它的方法和需求不匹配的時候
- 在軟體維護階段考慮的設計模式
詳解
首先來從生活中的常見場景來看,一個電源插座輸出都是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; }
有兩個方法需要實現,一個是反序列化,另外一個是序列化。相信你看懂了適配器模式之後,也能理解這個類中的方法含義
總結:
適配器模式更多的是提供不同介面給不同的廠家,適配器我們知道怎麼實現之後,更多的是在適用場景中去思考。這樣就會對適配器模式有更加深刻的理解。