外觀模式(Facade模式)詳解——小馬同學@Tian

小馬同學@Tian發表於2020-11-30

外觀模式(Facade模式)詳解

在現實生活中,常常存在辦事較複雜的例子,如辦房產證或註冊一家公司,有時要同多個部門聯絡,這時要是有一個綜合部門能解決一切手續問題就好了。

軟體設計也是這樣,當一個系統的功能越來越強,子系統會越來越多,客戶對系統的訪問也變得越來越複雜。這時如果系統內部發生改變,客戶端也要跟著改變,這違背了“開閉原則”,也違背了“迪米特法則”,所以有必要為多個子系統提供一個統一的介面,從而降低系統的耦合度,這就是外觀模式的目標。

圖 1 給出了客戶去當地房產局辦理房產證過戶要遇到的相關部門。

辦理房產證過戶的相關部門
圖1 辦理房產證過戶的相關部門

外觀模式的定義與特點

外觀(Facade)模式又叫作門面模式,是一種通過為多個複雜的子系統提供一個一致的介面,而使這些子系統更加容易被訪問的模式。該模式對外有一個統一介面,外部應用程式不用關心內部子系統的具體細節,這樣會大大降低應用程式的複雜度,提高了程式的可維護性。

在日常編碼工作中,我們都在有意無意的大量使用外觀模式。只要是高層模組需要排程多個子系統(2個以上的類物件),我們都會自覺地建立一個新的類封裝這些子系統,提供精簡的介面,讓高層模組可以更加容易地間接呼叫這些子系統的功能。尤其是現階段各種第三方SDK、開源類庫,很大概率都會使用外觀模式。

外觀(Facade)模式是“迪米特法則”的典型應用,它有以下主要優點。

  1. 降低了子系統與客戶端之間的耦合度,使得子系統的變化不會影響呼叫它的客戶類。
  2. 對客戶遮蔽了子系統元件,減少了客戶處理的物件數目,並使得子系統使用起來更加容易。
  3. 降低了大型軟體系統中的編譯依賴性,簡化了系統在不同平臺之間的移植過程,因為編譯一個子系統不會影響其他的子系統,也不會影響外觀物件。

外觀(Facade)模式的主要缺點如下。

  1. 不能很好地限制客戶使用子系統類,很容易帶來未知風險。
  2. 增加新的子系統可能需要修改外觀類或客戶端的原始碼,違背了“開閉原則”。

外觀模式的結構與實現

外觀(Facade)模式的結構比較簡單,主要是定義了一個高層介面。它包含了對各個子系統的引用,客戶端可以通過它訪問各個子系統的功能。現在來分析其基本結構和實現方法。

1. 模式的結構

外觀(Facade)模式包含以下主要角色。

  1. 外觀(Facade)角色:為多個子系統對外提供一個共同的介面。
  2. 子系統(Sub System)角色:實現系統的部分功能,客戶可以通過外觀角色訪問它。
  3. 客戶(Client)角色:通過一個外觀角色訪問各個子系統的功能。

其結構圖如圖 2 所示。

外觀模式的結構圖
圖2 外觀(Facade)模式的結構圖

2. 模式的實現

外觀模式的實現程式碼如下:

package facade;
public class FacadePattern{   
    public static void main(String[] args)    {
        Facade f=new Facade();      
        f.method();   
    }
}

//外觀角色
class Facade{ 
    private SubSystem01 obj1=new SubSystem01();  
    private SubSystem02 obj2=new SubSystem02();   
    private SubSystem03 obj3=new SubSystem03();   
    public void method()    {     
        obj1.method1();     
        obj2.method2();     
        obj3.method3();   
    }
}

//子系統角色
class SubSystem01{  
    public  void method1()    {     
        System.out.println("子系統01的method1()被呼叫!");   
    } 
}

//子系統角色
class SubSystem02{  
    public  void method2()    {    
        System.out.println("子系統02的method2()被呼叫!");  
    } 
}

//子系統角色
class SubSystem03{   
    public  void method3()    {     
        System.out.println("子系統03的method3()被呼叫!");   
    }  
}

程式執行結果如下:

子系統01的method1()被呼叫!
子系統02的method2()被呼叫!
子系統03的method3()被呼叫!

外觀模式的應用例項

【例1】用“外觀模式”設計一個婺源特產的選購介面。

分析:本例項的外觀角色 WySpecialty 是 JPanel 的子類,它擁有 8 個子系統角色 Specialty1~Specialty8,它們是圖示類(ImageIcon)的子類物件,用來儲存該婺源特產的圖示。

外觀類(WySpecialty)用 JTree 元件來管理婺源特產的名稱,並定義一個事件處理方法 valueClianged(TreeSelectionEvent e),當使用者從樹中選擇特產時,該特產的圖示物件儲存在標籤(JLabd)物件中。

客戶窗體物件用分割皮膚來實現,左邊放外觀角色的目錄樹,右邊放顯示所選特產影像的標籤。其結構圖如圖 3 所示。

婺源特產管理介面的結構圖
圖3 婺源特產管理介面的結構圖

程式程式碼如下:

package facade;import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class WySpecialtyFacade {

    public static void main(String[] args) {
        JFrame f = new JFrame("外觀模式: 婺源特產選擇測試");
        Container cp = f.getContentPane();
        WySpecialty wys = new WySpecialty();
        JScrollPane treeView = new JScrollPane(wys.tree);
        JScrollPane scrollpane = new JScrollPane(wys.label);
        JSplitPane splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, treeView, scrollpane);
        //分割面版        
        splitpane.setDividerLocation(230);
        //設定splitpane的分隔線位置 
        splitpane.setOneTouchExpandable(true);
        //設定splitpane可以展開或收起                        
        cp.add(splitpane);
        f.setSize(650, 350);
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

class WySpecialty extends JPanel implements TreeSelectionListener {
    private static final long serialVersionUID = 1L;
    final JTree tree;
    JLabel label;
    private Specialty1 s1 = new Specialty1();
    private Specialty2 s2 = new Specialty2();
    private Specialty3 s3 = new Specialty3();
    private Specialty4 s4 = new Specialty4();
    private Specialty5 s5 = new Specialty5();
    private Specialty6 s6 = new Specialty6();
    private Specialty7 s7 = new Specialty7();
    private Specialty8 s8 = new Specialty8();

    WySpecialty() {
        DefaultMutableTreeNode top = new DefaultMutableTreeNode("婺源特產");
        DefaultMutableTreeNode node1 = null, node2 = null, tempNode = null;
        node1 = new DefaultMutableTreeNode("婺源四大特產(紅、綠、黑、白)");
        tempNode = new DefaultMutableTreeNode("婺源荷包紅鯉魚");        
        node1.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源綠茶");
        node1.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源龍尾硯");
        node1.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源江灣雪梨");
        node1.add(tempNode);
        top.add(node1);
        node2 = new DefaultMutableTreeNode("婺源其它土特產");
        tempNode = new DefaultMutableTreeNode("婺源酒糟魚");
        node2.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源糟米子糕");
        node2.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源清明果");
        node2.add(tempNode);
        tempNode = new DefaultMutableTreeNode("婺源油煎燈");
        node2.add(tempNode);
        top.add(node2);
        tree = new JTree(top);
        tree.addTreeSelectionListener(this);
        label = new JLabel();
    }

    public void valueChanged(TreeSelectionEvent e) {
        if (e.getSource() == tree) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
            if (node == null) return;
            if (node.isLeaf()) {
                Object object = node.getUserObject();
                String sele = object.toString();
                label.setVerticalTextPosition(JLabel.BOTTOM);
                sele = sele.substring(2, 4);
                if (sele.equalsIgnoreCase("荷包")) label.setIcon(s1);
                else if (sele.equalsIgnoreCase("綠茶")) label.setIcon(s2);
                else if (sele.equalsIgnoreCase("龍尾")) label.setIcon(s3);
                else if (sele.equalsIgnoreCase("江灣")) label.setIcon(s4);
                else if (sele.equalsIgnoreCase("酒糟")) label.setIcon(s5);
                else if (sele.equalsIgnoreCase("糟米")) label.setIcon(s6);
                else if (sele.equalsIgnoreCase("清明")) label.setIcon(s7);
                else if (sele.equalsIgnoreCase("油煎")) label.setIcon(s8);
                label.setHorizontalAlignment(JLabel.CENTER);
            }
        }
    }
}

class Specialty1 extends ImageIcon {
    private static final long serialVersionUID = 1L;

    Specialty1() {
        super("src/facade/WyImage/Specialty11.jpg");
    }
}

class Specialty2 extends ImageIcon {
    private static final long serialVersionUID = 1L;

    Specialty2() {
        super("src/facade/WyImage/Specialty12.jpg");
    }
}

class Specialty3 extends ImageIcon {
    private static final long serialVersionUID = 1L;

    Specialty3() {
        super("src/facade/WyImage/Specialty13.jpg");
    }
}

class Specialty4 extends ImageIcon {
    private static final long serialVersionUID = 1L;

    Specialty4() {
        super("src/facade/WyImage/Specialty14.jpg");
    }

    class Specialty5 extends ImageIcon {
        private static final long serialVersionUID = 1L;

        Specialty5() {
            super("src/facade/WyImage/Specialty21.jpg");
        }
    }

    class Specialty6 extends ImageIcon {
        private static final long serialVersionUID = 1L;

        Specialty6() {
            super("src/facade/WyImage/Specialty22.jpg");
        }
    }

    class Specialty7 extends ImageIcon {
        private static final long serialVersionUID = 1L;

        Specialty7() {
            super("src/facade/WyImage/Specialty23.jpg");
        }
    }

    class Specialty8 extends ImageIcon {
        private static final long serialVersionUID = 1L;

        Specialty8() {
            super("src/facade/WyImage/Specialty24.jpg");
        }
    }
}

程式執行結果如圖 4 所示。

婺源特產管理介面的執行結果
圖4 婺源特產管理介面的執行結果

外觀模式的應用場景

通常在以下情況下可以考慮使用外觀模式。

  1. 對分層結構系統構建時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關係。
  2. 當一個複雜系統的子系統很多時,外觀模式可以為系統設計一個簡單的介面供外界訪問。
  3. 當客戶端與多個子系統之間存在很大的聯絡時,引入外觀模式可將它們分離,從而提高子系統的獨立性和可移植性。

外觀模式的擴充套件

在外觀模式中,當增加或移除子系統時需要修改外觀類,這違背了“開閉原則”。如果引入抽象外觀類,則在一定程度上解決了該問題,其結構圖如圖 5 所示。

引入抽象外觀類的外觀模式的結構圖
情況下可以考慮使用外觀模式。

  1. 對分層結構系統構建時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關係。
  2. 當一個複雜系統的子系統很多時,外觀模式可以為系統設計一個簡單的介面供外界訪問。
  3. 當客戶端與多個子系統之間存在很大的聯絡時,引入外觀模式可將它們分離,從而提高子系統的獨立性和可移植性。

外觀模式的擴充套件

在外觀模式中,當增加或移除子系統時需要修改外觀類,這違背了“開閉原則”。如果引入抽象外觀類,則在一定程度上解決了該問題,其結構圖如圖 5 所示。

[外鏈圖片轉存中…(img-M9AiykMD-1606732635304)]
圖5 引入抽象外觀類的外觀模式的結構圖

相關文章