Java中的23種設(shè)計(jì)模式(二)

(適配器模式呜叫、裝飾模式帐要、代理模式侦香、外觀模式落塑、橋接模式、組合模式罐韩、享元模式)
原文作者:終點(diǎn) 出處:Java之美[從菜鳥到高手演變]之設(shè)計(jì)模式

本章涉及7種結(jié)構(gòu)型模式:適配器模式憾赁、裝飾模式、代理模式散吵、外觀模式龙考、橋接模式、組合模式矾睦、享元模式晦款。其中對象的適配器模式是各種模式的起源,我們看下面的圖:


7種結(jié)構(gòu)型模式
6顷锰、適配器模式(Adapter)

適配器模式將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口表示柬赐,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為三類:類的適配器模式官紫、對象的適配器模式肛宋、接口的適配器模式州藕。首先,我們來看看類的適配器模式酝陈,先看類圖:

類的適配器模式

核心思想就是:有一個(gè)Source類床玻,擁有一個(gè)方法,待適配沉帮,目標(biāo)接口時(shí)Targetable锈死,通過Adapter類,將Source的功能擴(kuò)展到Targetable里穆壕,看代碼:

public class Source {
 
    public void method1() {
        System.out.println("this is original method!");
    }
}

public interface Targetable {
 
    /* 與原類中的方法相同 */
    public void method1();
 
    /* 新類的方法 */
    public void method2();
}

public class Adapter extends Source implements Targetable {
 
    @Override
    public void method2() {
        System.out.println("this is the targetable method!");
    }
}

Adapter類繼承Source類待牵,實(shí)現(xiàn)Targetable接口,下面是測試類:

public class AdapterTest {
 
    public static void main(String[] args) {
        Targetable target = new Adapter();
        target.method1();
        target.method2();
    }
}

輸出:

this is original method!
this is the targetable method!

這樣Targetable接口的實(shí)現(xiàn)類就具有了Source類的功能喇勋。
對象的適配器模式
基本思路和類的適配器模式相同缨该,只是將Adapter類作修改,這次不繼承Source類川背,而是持有Source類的實(shí)例贰拿,以達(dá)到解決兼容性的問題∠ㄔ疲看圖:

對象的適配器模式

只需要修改Adapter類的源碼即可:

public class Wrapper implements Targetable {
 
    private Source source;
    
    public Wrapper(Source source){
        super();
        this.source = source;
    }
    @Override
    public void method2() {
        System.out.println("this is the targetable method!");
    }
 
    @Override
    public void method1() {
        source.method1();
    }
}

測試類:

public class AdapterTest {
 
    public static void main(String[] args) {
        Source source = new Source();
        Targetable target = new Wrapper(source);
        target.method1();
        target.method2();
    }
}

輸出與第一種一樣膨更,只是適配的方法不同而已。

第三種適配器模式是接口的適配器模式缴允,接口的適配器是這樣的:有時(shí)我們寫的一個(gè)接口中有多個(gè)抽象方法荚守,當(dāng)我們寫該接口的實(shí)現(xiàn)類時(shí),必須實(shí)現(xiàn)該接口的所有方法练般,這明顯有時(shí)比較浪費(fèi)健蕊,因?yàn)椴⒉皇撬械姆椒ǘ际俏覀冃枰模袝r(shí)只需要某一些踢俄,此處為了解決這個(gè)問題缩功,我們引入了接口的適配器模式,借助于一個(gè)抽象類都办,該抽象類實(shí)現(xiàn)了該接口嫡锌,實(shí)現(xiàn)了所有的方法,而我們不和原始的接口打交道琳钉,只和該抽象類取得聯(lián)系势木,所以我們寫一個(gè)類,繼承該抽象類歌懒,重寫我們需要的方法就行啦桌。看一下類圖:

接口的適配器模式

這個(gè)很好理解,在實(shí)際開發(fā)中甫男,我們也常會遇到這種接口中定義了太多的方法且改,以致于有時(shí)我們在一些實(shí)現(xiàn)類中并不是都需要“宀担看代碼:

public interface Sourceable {
    
    public void method1();
    public void method2();
}

抽象類Wrapper2:

public abstract class Wrapper2 implements Sourceable{
    
    public void method1(){}
    public void method2(){}
}
public class SourceSub1 extends Wrapper2 {
    public void method1(){
        System.out.println("the sourceable interface's first Sub1!");
    }
}
public class SourceSub2 extends Wrapper2 {
    public void method2(){
        System.out.println("the sourceable interface's second Sub2!");
    }
}
public class WrapperTest {
 
    public static void main(String[] args) {
        Sourceable source1 = new SourceSub1();
        Sourceable source2 = new SourceSub2();
        
        source1.method1();
        source1.method2();
        source2.method1();
        source2.method2();
    }
}

測試輸出:

the sourceable interface's first Sub1!
the sourceable interface's second Sub2!

達(dá)到了我們的效果又跛!
講了這么多,總結(jié)一下三種適配器模式的應(yīng)用場景:

類的適配器模式:當(dāng)希望將一個(gè)轉(zhuǎn)換成滿足另一個(gè)新接口的類時(shí)若治,可以使用類的適配器模式慨蓝,創(chuàng)建一個(gè)新類,繼承原有的類端幼,實(shí)現(xiàn)新的接口即可礼烈。

對象的適配器模式:當(dāng)希望將一個(gè)對象轉(zhuǎn)換成滿足另一個(gè)新接口的對象時(shí),可以創(chuàng)建一個(gè)Wrapper類婆跑,持有原類的一個(gè)實(shí)例济丘,在Wrapper類的方法中,調(diào)用實(shí)例的方法就行洽蛀。

接口的適配器模式:當(dāng)不希望實(shí)現(xiàn)一個(gè)接口中所有的方法時(shí),可以創(chuàng)建一個(gè)抽象類Wrapper疟赊,實(shí)現(xiàn)所有方法郊供,我們寫別的類的時(shí)候,繼承抽象類即可近哟。

7驮审、裝飾模式(Decorator)

顧名思義,裝飾模式就是給一個(gè)對象增加一些新的功能吉执,而且是動態(tài)的疯淫,要求裝飾對象和被裝飾對象實(shí)現(xiàn)同一個(gè)接口,裝飾對象持有被裝飾對象的實(shí)例戳玫,關(guān)系圖如下:

裝飾器模式

Source類是被裝飾類熙掺,Decorator類是一個(gè)裝飾類,可以為Source類動態(tài)的添加一些功能咕宿,代碼如下:

public interface Sourceable {
    public void method();
}
public class Source implements Sourceable {
 
    @Override
    public void method() {
        System.out.println("the original method!");
    }
}
public class Decorator implements Sourceable {
 
    private Sourceable source;
    
    public Decorator(Sourceable source){
        super();
        this.source = source;
    }
    @Override
    public void method() {
        System.out.println("before decorator!");
        source.method();
        System.out.println("after decorator!");
    }
}

測試類:

public class DecoratorTest {
 
    public static void main(String[] args) {
        Sourceable source = new Source();
        Sourceable obj = new Decorator(source);
        obj.method();
    }
}

輸出:

before decorator!
the original method!
after decorator!

裝飾器模式的應(yīng)用場景:

1币绩、需要擴(kuò)展一個(gè)類的功能。
2府阀、動態(tài)的為一個(gè)對象增加功能缆镣,而且還能動態(tài)撤銷。(繼承不能做到這一點(diǎn)试浙,繼承的功能是靜態(tài)的董瞻,不能動態(tài)增刪。)

缺點(diǎn):產(chǎn)生過多相似的對象田巴,不易排錯(cuò)钠糊!

8挟秤、代理模式(Proxy)

其實(shí)每個(gè)模式名稱就表明了該模式的作用,代理模式就是多一個(gè)代理類出來眠蚂,替原對象進(jìn)行一些操作煞聪,比如我們在租房子的時(shí)候回去找中介,為什么呢逝慧?因?yàn)槟銓υ摰貐^(qū)房屋的信息掌握的不夠全面昔脯,希望找一個(gè)更熟悉的人去幫你做,此處的代理就是這個(gè)意思笛臣。再如我們有的時(shí)候打官司云稚,我們需要請律師,因?yàn)槁蓭熢诜煞矫嬗袑iL沈堡,可以替我們進(jìn)行操作静陈,表達(dá)我們的想法。先來看看關(guān)系圖:


代理模式

根據(jù)上文的闡述诞丽,代理模式就比較容易的理解了鲸拥,我們看下代碼:

public interface Sourceable {
    public void method();
}
public class Source implements Sourceable {
 
    @Override
    public void method() {
        System.out.println("the original method!");
    }
}
public class Proxy implements Sourceable {
 
    private Source source;
    public Proxy(){
        super();
        this.source = new Source();
    }
    @Override
    public void method() {
        before();
        source.method();
        atfer();
    }
    private void atfer() {
        System.out.println("after proxy!");
    }
    private void before() {
        System.out.println("before proxy!");
    }
}

測試類:

public class ProxyTest {
 
    public static void main(String[] args) {
        Sourceable source = new Proxy();
        source.method();
    }
 
}

輸出:

before proxy!
the original method!
after proxy!

代理模式的應(yīng)用場景:

如果已有的方法在使用的時(shí)候需要對原有的方法進(jìn)行改進(jìn),此時(shí)有兩種辦法:

1僧免、修改原有的方法來適應(yīng)刑赶。這樣違反了“對擴(kuò)展開放,對修改關(guān)閉”的原則懂衩。

2撞叨、就是采用一個(gè)代理類調(diào)用原有的方法,且對產(chǎn)生的結(jié)果進(jìn)行控制浊洞。這種方法就是代理模式牵敷。

使用代理模式揩徊,可以將功能劃分的更加清晰窟哺,有助于后期維護(hù)!

9寝优、外觀模式(Facade)

外觀模式是為了解決類與類之家的依賴關(guān)系的苫亦,像spring一樣尖淘,可以將類和類之間的關(guān)系配置到配置文件中,而外觀模式就是將他們的關(guān)系放在一個(gè)Facade類中著觉,降低了類類之間的耦合度村生,該模式中沒有涉及到接口,看下類圖:(我們以一個(gè)計(jì)算機(jī)的啟動過程為例)

外觀模式

我們先看下實(shí)現(xiàn)類:

public class CPU {
    
    public void startup(){
        System.out.println("cpu startup!");
    }
    
    public void shutdown(){
        System.out.println("cpu shutdown!");
    }
}
public class Memory {
    
    public void startup(){
        System.out.println("memory startup!");
    }
    
    public void shutdown(){
        System.out.println("memory shutdown!");
    }
}
public class Disk {
    
    public void startup(){
        System.out.println("disk startup!");
    }
    
    public void shutdown(){
        System.out.println("disk shutdown!");
    }
}
public class Computer {
    private CPU cpu;
    private Memory memory;
    private Disk disk;
    
    public Computer(){
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }
    
    public void startup(){
        System.out.println("start the computer!");
        cpu.startup();
        memory.startup();
        disk.startup();
        System.out.println("start computer finished!");
    }
    
    public void shutdown(){
        System.out.println("begin to close the computer!");
        cpu.shutdown();
        memory.shutdown();
        disk.shutdown();
        System.out.println("computer closed!");
    }
}

User類如下:

public class User {
 
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.startup();
        computer.shutdown();
    }
}

輸出:

start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!

如果我們沒有Computer類饼丘,那么趁桃,CPU、Memory、Disk他們之間將會相互持有實(shí)例卫病,產(chǎn)生關(guān)系油啤,這樣會造成嚴(yán)重的依賴,修改一個(gè)類蟀苛,可能會帶來其他類的修改益咬,這不是我們想要看到的,有了Computer類帜平,他們之間的關(guān)系被放在了Computer類里幽告,這樣就起到了解耦的作用,這裆甩,就是外觀模式冗锁!

10、橋接模式(Bridge)

橋接模式就是把事物和其具體實(shí)現(xiàn)分開嗤栓,使他們可以各自獨(dú)立的變化冻河。橋接的用意是:將抽象化與實(shí)現(xiàn)化解耦,使得二者可以獨(dú)立變化茉帅,像我們常用的JDBC橋DriverManager一樣叨叙,JDBC進(jìn)行連接數(shù)據(jù)庫的時(shí)候,在各個(gè)數(shù)據(jù)庫之間進(jìn)行切換堪澎,基本不需要?jiǎng)犹嗟拇a擂错,甚至絲毫不用動,原因就是JDBC提供統(tǒng)一接口全封,每個(gè)數(shù)據(jù)庫提供各自的實(shí)現(xiàn),用一個(gè)叫做數(shù)據(jù)庫驅(qū)動的程序來橋接就行了桃犬。我們來看看關(guān)系圖:

橋接模式

實(shí)現(xiàn)代碼:

先定義接口:

public interface Sourceable {
    public void method();
}

分別定義兩個(gè)實(shí)現(xiàn)類:

public class SourceSub1 implements Sourceable {
 
    @Override
    public void method() {
        System.out.println("this is the first sub!");
    }
}
public class SourceSub2 implements Sourceable {
 
    @Override
    public void method() {
        System.out.println("this is the second sub!");
    }
}

定義一個(gè)橋刹悴,持有Sourceable的一個(gè)實(shí)例:

public abstract class Bridge {
    private Sourceable source;
 
    public void method(){
        source.method();
    }
    
    public Sourceable getSource() {
        return source;
    }
 
    public void setSource(Sourceable source) {
        this.source = source;
    }
}
public class MyBridge extends Bridge {
    public void method(){
        getSource().method();
    }
}

測試類:

public class BridgeTest {
    
    public static void main(String[] args) {
        
        Bridge bridge = new MyBridge();
        
        /*調(diào)用第一個(gè)對象*/
        Sourceable source1 = new SourceSub1();
        bridge.setSource(source1);
        bridge.method();
        
        /*調(diào)用第二個(gè)對象*/
        Sourceable source2 = new SourceSub2();
        bridge.setSource(source2);
        bridge.method();
    }
}

output:

this is the first sub!
this is the second sub!

這樣,就通過對Bridge類的調(diào)用攒暇,實(shí)現(xiàn)了對接口Sourceable的實(shí)現(xiàn)類SourceSub1和SourceSub2的調(diào)用土匀。接下來我再畫個(gè)圖,大家就應(yīng)該明白了形用,因?yàn)檫@個(gè)圖是我們JDBC連接的原理就轧,有數(shù)據(jù)庫學(xué)習(xí)基礎(chǔ)的,一結(jié)合就都懂了田度。


11妒御、組合模式(Composite)

組合模式有時(shí)又叫部分-整體模式在處理類似樹形結(jié)構(gòu)的問題時(shí)比較方便,看看關(guān)系圖:

組合模式

直接來看代碼:

public class TreeNode {
    
    private String name;
    private TreeNode parent;
    private Vector<TreeNode> children = new Vector<TreeNode>();
    
    public TreeNode(String name){
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public TreeNode getParent() {
        return parent;
    }
 
    public void setParent(TreeNode parent) {
        this.parent = parent;
    }
    
    //添加孩子節(jié)點(diǎn)
    public void add(TreeNode node){
        children.add(node);
    }
    
    //刪除孩子節(jié)點(diǎn)
    public void remove(TreeNode node){
        children.remove(node);
    }
    
    //取得孩子節(jié)點(diǎn)
    public Enumeration<TreeNode> getChildren(){
        return children.elements();
    }
}
public class Tree {
 
    TreeNode root = null;
 
    public Tree(String name) {
        root = new TreeNode(name);
    }
 
    public static void main(String[] args) {
        Tree tree = new Tree("A");
        TreeNode nodeB = new TreeNode("B");
        TreeNode nodeC = new TreeNode("C");
        
        nodeB.add(nodeC);
        tree.root.add(nodeB);
        System.out.println("build the tree finished!");
    }
}

使用場景:將多個(gè)對象組合在一起進(jìn)行操作镇饺,常用于表示樹形結(jié)構(gòu)中乎莉,例如二叉樹等。

12、享元模式(Flyweight)

享元模式的主要目的是實(shí)現(xiàn)對象的共享惋啃,即共享池哼鬓,當(dāng)系統(tǒng)中對象多的時(shí)候可以減少內(nèi)存的開銷,通常與工廠模式一起使用边灭。


享元模式

FlyWeightFactory負(fù)責(zé)創(chuàng)建和管理享元單元异希,當(dāng)一個(gè)客戶端請求時(shí),工廠需要檢查當(dāng)前對象池中是否有符合條件的對象绒瘦,如果有称簿,就返回已經(jīng)存在的對象,如果沒有椭坚,則創(chuàng)建一個(gè)新對象予跌,F(xiàn)lyWeight是超類。一提到共享池善茎,我們很容易聯(lián)想到Java里面的JDBC連接池券册,想想每個(gè)連接的特點(diǎn),我們不難總結(jié)出:適用于作共享的一些個(gè)對象垂涯,他們有一些共有的屬性烁焙,就拿數(shù)據(jù)庫連接池來說,url耕赘、driverClassName骄蝇、username、password及dbname操骡,這些屬性對于每個(gè)連接來說都是一樣的九火,所以就適合用享元模式來處理,建一個(gè)工廠類册招,將上述類似屬性作為內(nèi)部數(shù)據(jù)岔激,其它的作為外部數(shù)據(jù),在方法調(diào)用時(shí)是掰,當(dāng)做參數(shù)傳進(jìn)來虑鼎,這樣就節(jié)省了空間,減少了實(shí)例的數(shù)量键痛。
看個(gè)例子:



看下數(shù)據(jù)庫連接池的代碼:
public class ConnectionPool {
    
    private Vector<Connection> pool;
    
    /*公有屬性*/
    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root";
    private String driverClassName = "com.mysql.jdbc.Driver";
 
    private int poolSize = 100;
    private static ConnectionPool instance = null;
    Connection conn = null;
 
    /*構(gòu)造方法炫彩,做一些初始化工作*/
    private ConnectionPool() {
        pool = new Vector<Connection>(poolSize);
 
        for (int i = 0; i < poolSize; i++) {
            try {
                Class.forName(driverClassName);
                conn = DriverManager.getConnection(url, username, password);
                pool.add(conn);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
 
    /* 返回連接到連接池 */
    public synchronized void release() {
        pool.add(conn);
    }
 
    /* 返回連接池中的一個(gè)數(shù)據(jù)庫連接 */
    public synchronized Connection getConnection() {
        if (pool.size() > 0) {
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        } else {
            return null;
        }
    }
}

通過連接池的管理,實(shí)現(xiàn)了數(shù)據(jù)庫連接的共享絮短,不需要每一次都重新創(chuàng)建連接江兢,節(jié)省了數(shù)據(jù)庫重新創(chuàng)建的開銷,提升了系統(tǒng)的性能丁频!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末划址,一起剝皮案震驚了整個(gè)濱河市扔嵌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夺颤,老刑警劉巖痢缎,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異世澜,居然都是意外死亡独旷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門寥裂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嵌洼,“玉大人,你說我怎么就攤上這事封恰÷檠” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵诺舔,是天一觀的道長鳖昌。 經(jīng)常有香客問我,道長低飒,這世上最難降的妖魔是什么许昨? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮褥赊,結(jié)果婚禮上糕档,老公的妹妹穿的比我還像新娘。我一直安慰自己拌喉,他們只是感情好速那,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尿背,像睡著了一般端仰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上残家,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天榆俺,我揣著相機(jī)與錄音售躁,去河邊找鬼坞淮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛陪捷,可吹牛的內(nèi)容都是我干的回窘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼市袖,長吁一口氣:“原來是場噩夢啊……” “哼啡直!你這毒婦竟也來了烁涌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤酒觅,失蹤者是張志新(化名)和其女友劉穎撮执,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舷丹,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抒钱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颜凯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谋币。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖症概,靈堂內(nèi)的尸體忽然破棺而出蕾额,到底是詐尸還是另有隱情,我是刑警寧澤彼城,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布诅蝶,位于F島的核電站,受9級特大地震影響精肃,放射性物質(zhì)發(fā)生泄漏秤涩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一司抱、第九天 我趴在偏房一處隱蔽的房頂上張望筐眷。 院中可真熱鬧,春花似錦习柠、人聲如沸匀谣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽武翎。三九已至,卻和暖如春溶锭,著一層夾襖步出監(jiān)牢的瞬間宝恶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工趴捅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垫毙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓拱绑,卻偏偏與公主長得像综芥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子猎拨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容