摘要:適配器模式要比策略模式要好理解一些。書中的適配器模式有兩種實現(xiàn)方式,一種是通過代理,另一種是通過繼承。通過工廠模式獲得具體接口。而且工廠模式承擔(dān)的壓力過重,可能會導(dǎo)致職責(zé)的混亂。工廠方法模式這其實是工廠模式的一個簡單的升級。
前言
最近在看《Think In JAVA》,其中在講解繼承,組合,抽象類和接口的時候,提到了題中的幾個設(shè)計模式。這幾個設(shè)計模式也確實讓我更好的理解了JAVA中各個數(shù)據(jù)結(jié)構(gòu)的含義。今天就結(jié)合書本還有自己的理解,稍微整理一下這幾個設(shè)計模式。
Strategy Pattern | 策略模式這里就必須要提一下向上轉(zhuǎn)化這個概念。在繼承和接口中都有提到這個概念。
向上轉(zhuǎn)化在繼承中是指子類可以向上轉(zhuǎn)化為父類。比如,有一個Instrument類,以及它的一個子類Flute。子類重寫的play方法會覆蓋父類的方法。
class Instrument{ public void play(){ ... //play instrument } } class Flute extends Instrument{ public void play(){ ... //play flute } }
如果這時有一個方法需要接收一個樂器參數(shù)并演奏,那么無需寫多個重載方法接收各種不同的樂器,只需要一個接收Instrument類的方法。
public void play(Instrument instrument){ instrument.play(); }
在這個方法中傳入一個Flute對象,調(diào)用的將是flute中的play方法。這就是向上轉(zhuǎn)化,動態(tài)綁定的一個最簡單的例子。
其實接口也是同理,只是接口允許多種向上轉(zhuǎn)化。也就是說,JAVA中繼承是唯一的,而接口是可以Implement多個的。因此JAVA中繼承向上轉(zhuǎn)化的路徑唯一,而接口向上轉(zhuǎn)化路徑不唯一。
接下來就要講到策略模式。
策略模式的概念如下:
Defines a set of encapsulated algorithms that can be swapped to carry out a specific behaviour
定義了一組封裝好的算法,這些算法分別執(zhí)行不同的操作。在實際運行中,這些算法可以動態(tài)切換來滿足不同場景下的需求
策略模式的使用情景有:
將文件保存為不同的格式
排序算法的多種實現(xiàn)
文件壓縮的多種實現(xiàn)
也就是說,策略模式將一組完成相同工作的不同方式的代碼分別放到不同的類中,并通過策略模式實現(xiàn)在運行中的相互切換。
這是從網(wǎng)上找到的關(guān)于策略模式的UML圖。
策略模式是JAVA中繼承,抽象類以及接口的一種綜合應(yīng)用。在策略模式中,我們可以根據(jù)一個“開放接口”設(shè)計出多種“具體策略”,然后在調(diào)用時只需要輸入“開放接口”,程序運行時會根據(jù)“開放接口”的具體實現(xiàn)來決定具體的運行結(jié)果。
上面設(shè)計模式的代碼如下:
//接口 public interface Strategy { public int doOperation(int num1, int num2); } //接口實現(xiàn)類 public class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } public class OperationSubstract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } } public class OperationMultiply implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 * num2; } } //上下文 public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } } //具體調(diào)用 public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationMultiply()); System.out.println("10 * 5 = " + context.executeStrategy(10, 5)); } }
下面我再講一個具體的例子來說明使用策略模式與不使用策略模式的差距。
假設(shè)我們有一個壓縮文件的功能,壓縮文件有多種算法,如Zip,RAR等等。程序能夠根據(jù)實際的操作系統(tǒng)以及性能等參數(shù)來選擇一個壓縮算法執(zhí)行壓縮操作。我們假設(shè)這個選擇具體算法的功能放置CompressionPreference類中。
其實這些對于客戶端來說都是透明的。也就是說,客戶端只知道會有一個壓縮功能,該功能需要客戶上傳要壓縮的文件。如此場景下,服務(wù)端只需要提供一個壓縮的接口,而無需暴露具體的實現(xiàn)。
代碼如下:
//選擇壓縮方法類,根據(jù)具體情況返回壓縮的方法 public class CompressionPreference{ public static CompressionStrategy getPreferedStrategy(){ //根據(jù)系統(tǒng)的情況或是用戶的選擇返回具體的壓縮算法 } } //壓縮策略接口 public interface CompressionStrategy{ void compress(Listfiles); } //壓縮策略的具體實現(xiàn) public class ZipCompressionStrategy implements CompressionStrategy{ @Override public void compress(List files){ //zip 壓縮 } } public class RarCompressionStrategy implements CompressionStrategy{ @Override public void compress(List files){ //RAR 壓縮 } } public class CompressionContext{ private CompressionStrategy strategy; //這里根據(jù)CompressionPreference選擇壓縮策略 public void setStrategy(CompressionStrategy strategy){this.strategy=strategy);} public void createArchieve(List files){ strategy.compress(files); } } //客戶端調(diào)用 public class Client{ public static void main(String[] args) { CompressionContext ctx = new CompressionContext(); //設(shè)置壓縮上下文 ctx.setCompressionStrategy(CompressionPreference.getPreferedStrategy()); ctx.createArchive(fileList); } }
通過這樣的設(shè)計之后,如果需要添加新的算法,只需要增加一個CompressionStrategy的具體實現(xiàn)類,以及修改一下CompressionPreference中的方法即可。對于客戶端的調(diào)用不會產(chǎn)生任何影響。
如果不對算法進行封裝,直接允許客戶端調(diào)用的話。一方面,暴露了壓縮算法的種種實現(xiàn),另一方面,也增加了可能造成的錯誤調(diào)用。而且一旦增加新的壓縮算法,客戶端也需要知道這些本不需要知道的東西,調(diào)整自己的調(diào)用。這樣的代碼,可維護性實在是太差了。
適配器模式 | Adapter Design Pattern定義:
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn"t otherwise because of incompatible interfaces.
將兩個不想兼容的接口通過適配器進行相互轉(zhuǎn)化。
適配器模式要比策略模式要好理解一些。在書中講解適配器模式時,實際上是為了補充說明如何面向接口編程。適配器模式,顧名思義,就是將本來并不繼承某個接口的類通過適配器轉(zhuǎn)化為可以通過該接口調(diào)用,它充當(dāng)著連個不兼容的接口之間的橋梁。
從原含義上來講,適配器是指一個接口轉(zhuǎn)換器,在生活中最常見的接口轉(zhuǎn)換器就是你的手機充電線頭啦!充電頭將從插座中輸出的標(biāo)準(zhǔn)220V電壓(國內(nèi))轉(zhuǎn)化為可以安全充電的電壓。并且在另一側(cè)提供了一個USB充電口,從而使手機可以在一切含有USB端口的充電線下進行充電。再舉一個例子,也就是SD卡。使用相機的朋友知道,有些電腦是不提供SD卡接口的,那么就需要將SD卡插入SD卡讀卡器,再將讀卡器通過USB接口插入電腦。這時電腦就可以讀取SD卡中的內(nèi)容了。
在書中的例子,適配器應(yīng)用的場景是將不可以修改的類改為繼承某個接口從而可以作為該接口的一個實現(xiàn)類被調(diào)用。書中的適配器模式有兩種實現(xiàn)方式,一種是通過代理,另一種是通過繼承。兩種方式本質(zhì)上是相同的,如果需要原類中的所有實現(xiàn),則通過繼承方式實現(xiàn)適配器,如果只是一部分實現(xiàn),則通過代理的方式。具體情況具體分析。
以上講的都太過抽象了,下面講一個具體的例子。
比如我有一個掃描類Scanner,他有一個方法,可以接收所有繼承了Readable接口的類,并根據(jù)類中的情況將其中的數(shù)據(jù)讀取出來。系統(tǒng)中已經(jīng)有一些類,他們是可以被Scanner讀取的,但是他們并沒有繼承Readable接口。本著開閉原則,我可以給這些類添加一個適配器,使其可以被Scanner讀取。通過這種模式,無論是出現(xiàn)新的Scanner讀取文件或是讀取系統(tǒng)已有的文件,都不必修改Scanner方法。只需要使其支持Readable接口就行。
public class Scanner{ public void read(Readable material){ material.read(); } } public interface Readable{ void read(); } public class TXT implements Readable{ ... public void read(){ //讀取txt文件 } ... } public class HTML{ public void toReadableFormat(){ //html文件也可以被讀取,但是它并沒有繼承Readable接口,所以無法被Scanner 識別 } } //這里才是適配器模式 public class HTMLAdapter implements Readable{ ... private HTML html; public HTMLAdapter(HTML html){this.html = html} public void read(){ html.toReadableFormat(); } ... } //這時候兩個文件都可以被讀取了 public class Test{ public static void main(String[] args){ Scanner s = new Scanner(); s.read(new TXT()); s.read(new HTMLAdapter(new HTML())); } }
一個例子不夠,再來一個~
在自媒體的發(fā)展史中,媒體的格式越來越多樣化,從最初的文本,到MP3,再到視頻格式。如果現(xiàn)在有一個系統(tǒng),它本來只支持MP3格式的文件的讀取,這時候要想該系統(tǒng)可以支持新媒體類的文件的播放。新媒體類文件由另一個團隊開發(fā),擁有自己的開發(fā)接口和具體實現(xiàn)。如何才能將該模塊融入到現(xiàn)有系統(tǒng)中呢?
這時候就需要通過適配器模式來解決這個問題了。
這是這個系統(tǒng)給的UML類圖。通過在原系統(tǒng)中新建一個MediaAdapter適配器繼承原媒體播放器的接口,從而使原系統(tǒng)可以在不知下層變動的基礎(chǔ)上,繼續(xù)調(diào)用原來的play方法來實現(xiàn)播放功能。
具體代碼如下:
public interface MediaPlayer { public void play(String audioType, String fileName); } public interface AdvancedMediaPlayer { public void playVlc(String fileName); public void playMp4(String fileName); } public class VlcPlayer implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: "+ fileName); } @Override public void playMp4(String fileName) { //do nothing } } public class Mp4Player implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { //do nothing } @Override public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: "+ fileName); } } public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType){ if(audioType.equalsIgnoreCase("vlc") ){ advancedMusicPlayer = new VlcPlayer(); }else if (audioType.equalsIgnoreCase("mp4")){ advancedMusicPlayer = new Mp4Player(); } } @Override public void play(String audioType, String fileName) { if(audioType.equalsIgnoreCase("vlc")){ advancedMusicPlayer.playVlc(fileName); } else if(audioType.equalsIgnoreCase("mp4")){ advancedMusicPlayer.playMp4(fileName); } } } public class AdapterPatternDemo { public static void main(String[] args) { MediaPlayer audioPlayer = new AudioPlayer(); audioPlayer.play("mp3", "beyond the horizon.mp3"); MediaPlayer videoPlayer = new MediaAdapter(); videoPlayer.play("vlc", "far far away.vlc"); } }工廠模式 | Factory Design Pattern
終于到工廠模式了~~~~寫了好久了呀QAQ
工廠模式是為了管理一個接口之下眾多實現(xiàn)類。比如最常見的DAO接口。數(shù)據(jù)庫中的表少至10個多可以至百個。在spring框架中,通過依賴倒置和自動注入實現(xiàn)了這么多讀取數(shù)據(jù)庫的接口實現(xiàn)類的管理。那么在沒有框架的場景下,如何才可以使上層代碼和下層具體的DAO接口解耦呢?這時就需要工廠模式。通過工廠模式獲得具體DAO接口。
至于為什么要選擇這樣的一個工廠模式,而不是直接new一個具體的實現(xiàn)類呢?這里舉個例子。比方說,有一個DAO接口,實現(xiàn)該接口的有UserDaoImpl, AccountDaoImpl等。假設(shè)有兩個類均用到UserDaoImpl。如果在這兩個類中均使用new來創(chuàng)建一個新的UserDaoImpl,那么一旦有一天,因為需求變更,需要將UserDaoImpl換成AnotherUserDaoImpl,則需要在兩個類中分別修改。那么如果有十個類,甚至一百個類都用到了這個Dao呢?這時候如果我是通過工廠來獲得這個Dao,也就只需要在工廠中將返回值從原來的UserDaoImpl變成AnotherUserDaoImpl,并不會影響調(diào)用方。
簡單工廠模式 | Static Factory Method下面給一個簡單的工廠模式的例子。
interface Dog { public void speak (); } class Poodle implements Dog { public void speak() { System.out.println("The poodle says "arf""); } } class Rottweiler implements Dog { public void speak() { System.out.println("The Rottweiler says (in a very deep voice) "WOOF!""); } } class SiberianHusky implements Dog { public void speak() { System.out.println("The husky says "Dude, what"s up?""); } } class DogFactory { public static Dog getDog(String criteria) { if ( criteria.equals("small") ) return new Poodle(); else if ( criteria.equals("big") ) return new Rottweiler(); else if ( criteria.equals("working") ) return new SiberianHusky(); return null; } } public class JavaFactoryPatternExample { public static void main(String[] args) { // create a small dog Dog dog = DogFactory.getDog("small"); dog.speak(); // create a big dog dog = DogFactory.getDog("big"); dog.speak(); // create a working dog dog = DogFactory.getDog("working"); dog.speak(); } }
在簡單的工廠模式中,工廠根據(jù)輸入的條件返回給一個接口的具體實現(xiàn)。
簡單工廠模式有一個問題,就是一旦工廠出現(xiàn)新的產(chǎn)品,就必須修改工廠中獲取產(chǎn)品的方法,這有違開閉原則。而且工廠模式承擔(dān)的壓力過重,可能會導(dǎo)致職責(zé)的混亂。最重要的是,簡單工廠模式中,獲取產(chǎn)品的方法是靜態(tài)方法,該方法無法通過繼承等形式得到擴展。
這其實是工廠模式的一個簡單的升級。考慮一個真實工廠的場景。它的產(chǎn)品Product之下往往還有許多分類,如軸承,輪胎。各個子分類往往也對應(yīng)著不同的車間,如軸承車間,輪胎車間。如果還用簡單工廠模式返回一個Product,且不說向上轉(zhuǎn)型可能丟失的一些數(shù)據(jù),而且工廠的壓力也太大了,因為可能要根據(jù)不同場景返回上百個不同類型但繼承了同一接口的類。這不符合設(shè)計原則。
這時候就出現(xiàn)了工廠方法模式。不僅僅對產(chǎn)品抽象,還對工廠抽象。對不同的產(chǎn)品提供不同的工廠,將職責(zé)進一步細化,滿足SRP(單一職責(zé)原則)。同時,因為不需要輸入無關(guān)的判斷數(shù)據(jù),也解除了控制耦合。
具體例子有最常見的日志系統(tǒng)。日志系統(tǒng)之下往往針對各個不同的子系統(tǒng),比如數(shù)據(jù)庫日志子系統(tǒng),比如文件日志子系統(tǒng)。不同的日志系統(tǒng)對應(yīng)的日志文件也不同。這時通過工廠方法模式就可以很好的解決二者之間的關(guān)系。
在這張圖中還可以繼續(xù)延伸,比如數(shù)據(jù)庫包括Mysql數(shù)據(jù)庫,Oracle數(shù)據(jù)庫等等。在UML圖中也可以繼續(xù)根據(jù)MySqlLog創(chuàng)建MysqlLogFactory。
還有一個具體的例子就是JAVA中的數(shù)據(jù)庫連接。
Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://loc alhost:1433; DatabaseName=DB;user=sa;password="); Statement statement=conn.createStatement(); ResultSet rs=statement.executeQuery("select * from UserInfo");
這里通過DriverManager工廠根據(jù)輸入的信息返回一個對應(yīng)的連接。連接中再返回對應(yīng)的抽象語句statement。根據(jù)工廠中的信息可以知道,這個statement的底層實現(xiàn)必定是一個類似SqlServerStatement的實現(xiàn)。
抽象工廠模式 | Abstract Factory Method產(chǎn)品等級結(jié)構(gòu):產(chǎn)品等級結(jié)構(gòu)即產(chǎn)品的繼承結(jié)構(gòu),如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構(gòu)成了一個產(chǎn)品等級結(jié)構(gòu),抽象電視機是父類,而具體品牌的電視機是其子類。
產(chǎn)品族:在抽象工廠模式中,產(chǎn)品族是指由同一個工廠生產(chǎn)的,位于不同產(chǎn)品等級結(jié)構(gòu)中的一組產(chǎn)品,如海爾電器工廠生產(chǎn)的海爾電視機、海爾電冰箱,海爾電視機位于電視機產(chǎn)品等級結(jié)構(gòu)中,海爾電冰箱位于電冰箱產(chǎn)品等級結(jié)構(gòu)中。
抽象工廠模式與工廠方法模式最大的區(qū)別在于,工廠方法模式針對的是一個產(chǎn)品等級結(jié)構(gòu),而抽象工廠模式則需要面對多個產(chǎn)品等級結(jié)構(gòu),一個工廠等級結(jié)構(gòu)可以負責(zé)多個不同產(chǎn)品等級結(jié)構(gòu)中的產(chǎn)品對象的創(chuàng)建 。當(dāng)一個工廠等級結(jié)構(gòu)可以創(chuàng)建出分屬于不同產(chǎn)品等級結(jié)構(gòu)的一個產(chǎn)品族中的所有對象時,抽象工廠模式比工廠方法模式更為簡單、有效率。
在這里的上下文中,抽象工廠之下的子工廠被劃分為海爾子工廠,海信子工廠。在海爾自工廠中可以獲得海爾冰箱(productA),海爾電視機(productB), 同理,在海信子工廠中,可以獲得海信冰箱(productA),海信電視機(productB)。
當(dāng)然了 在大多數(shù)的應(yīng)用場景下,工廠設(shè)計模式已經(jīng)足夠了。
ReferencesTutorialspoint Design Pattern
stackoverflow : how does the strategy design pattern work
dzon strategy design pattern
dzon adapter pattern
工廠模式的一個簡單例子
工廠模式的一篇極好博客
幾種工廠模式之間的對比
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/70204.html
摘要:適配器模式橋接模式過濾器模式組合模式裝飾器模式外觀模式享元模式代理模式行為型模式這些設(shè)計模式特別關(guān)注對象之間的通信。對象適配器另外一種適配器模式是對象適配器,它不是使用多繼承或繼承再實現(xiàn)的方式,而是使用直接關(guān)聯(lián),或者稱為委托的方式。 設(shè)計模式匯總 創(chuàng)建型模式 這些設(shè)計模式提供了一種在創(chuàng)建對象的同時隱藏創(chuàng)建邏輯的方式,而不是使用新的運算符直接實例化對象。這使得程序在判斷針對某個給定實例需...
摘要:我們今天也來做一個萬能遙控器設(shè)計模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。今天要介紹的仍然是創(chuàng)建型設(shè)計模式的一種建造者模式。設(shè)計模式的理論知識固然重要,但 計算機程序的思維邏輯 (54) - 剖析 Collections - 設(shè)計模式 上節(jié)我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進行操作,第二類是返回一個容器接口對象,上節(jié)我們介紹了...
摘要:我們今天也來做一個萬能遙控器設(shè)計模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。今天要介紹的仍然是創(chuàng)建型設(shè)計模式的一種建造者模式。設(shè)計模式的理論知識固然重要,但 計算機程序的思維邏輯 (54) - 剖析 Collections - 設(shè)計模式 上節(jié)我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進行操作,第二類是返回一個容器接口對象,上節(jié)我們介紹了...
摘要:分別為適配器模式,裝飾器模式,代理模式,外觀模式,橋接模式,組合模式,享元模式。設(shè)計模式五適配器模式適配器模式將某個對象的接生成器和協(xié)程的實現(xiàn)在這篇文章中,作者針對那些比較難以理解的概念,以一個更為通俗的方式去講明白。。 PHP 源碼注解 PHP 的詳細源碼注解 PHP 字符串操作整理 一些有關(guān)字符串的常用操作。 Redis 常見七種使用場景 (PHP 實戰(zhàn)) 這篇文章主要介紹利用 R...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進步歡迎點贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
閱讀 1230·2021-11-11 16:54
閱讀 1744·2021-10-13 09:40
閱讀 940·2021-10-08 10:05
閱讀 3503·2021-09-22 15:50
閱讀 3706·2021-09-22 15:41
閱讀 1800·2021-09-22 15:08
閱讀 2345·2021-09-07 10:24
閱讀 3578·2019-08-30 12:52