在現實生活中,常常存在辦事較復雜的例子,如辦房產證或注冊一家公司,有時要同多個部門聯系,這時要是有一個綜合部門能解決一切手續問題就好了。
軟件設計也是這樣,當一個系統的功能越來越強,子系統會越來越多,客戶對系統的訪問也變得越來越復雜。這時如果系統內部發生改變,客戶端也要跟着改變,這違背了“開閉原則”,也違背了“迪米特法則”,所以有必要為多個子系統提供一個統一的接口,從而降低系統的耦合度,這就是外觀模式的目標。
圖 1 給出了客戶去當地房產局辦理房產證過戶要遇到的相關部門。
外觀模式的定義與特點
外觀(Facade)模式又叫作門面模式,是一種通過為多個復雜的子系統提供一個一致的接口,而使這些子系統更加容易被訪問的模式。該模式對外有一個統一接口,外部應用程序不用關心內部子系統的具體細節,這樣會大大降低應用程序的復雜度,提高了程序的可維護性。
在日常編碼工作中,我們都在有意無意的大量使用外觀模式。只要是高層模塊需要調度多個子系統(2個以上的類對象),我們都會自覺地創建一個新的類封裝這些子系統,提供精簡的接口,讓高層模塊可以更加容易地間接調用這些子系統的功能。尤其是現階段各種第三方SDK、開源類庫,很大概率都會使用外觀模式。
外觀(Facade)模式是“迪米特法則”的典型應用,它有以下主要優點。
- 降低了子系統與客戶端之間的耦合度,使得子系統的變化不會影響調用它的客戶類。
- 對客戶屏蔽了子系統組件,減少了客戶處理的對象數目,並使得子系統使用起來更加容易。
- 降低了大型軟件系統中的編譯依賴性,簡化了系統在不同平台之間的移植過程,因為編譯一個子系統不會影響其他的子系統,也不會影響外觀對象。
外觀(Facade)模式的主要缺點如下。
- 不能很好地限制客戶使用子系統類,很容易帶來未知風險。
- 增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
外觀模式的結構與實現
外觀(Facade)模式的結構比較簡單,主要是定義了一個高層接口。它包含了對各個子系統的引用,客戶端可以通過它訪問各個子系統的功能。現在來分析其基本結構和實現方法。
1. 模式的結構
外觀(Facade)模式包含以下主要角色。
- 外觀(Facade)角色:為多個子系統對外提供一個共同的接口。
- 子系統(Sub System)角色:實現系統的部分功能,客戶可以通過外觀角色訪問它。
- 客戶(Client)角色:通過一個外觀角色訪問各個子系統的功能。
其結構圖如圖 2 所示。
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 所示。
程序代碼如下:
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.setText(sele); label.setHorizontalTextPosition(JLabel.CENTER); 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 所示。
外觀模式的應用場景
通常在以下情況下可以考慮使用外觀模式。
- 對分層結構系統構建時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關系。
- 當一個復雜系統的子系統很多時,外觀模式可以為系統設計一個簡單的接口供外界訪問。
- 當客戶端與多個子系統之間存在很大的聯系時,引入外觀模式可將它們分離,從而提高子系統的獨立性和可移植性。
外觀模式的擴展
在外觀模式中,當增加或移除子系統時需要修改外觀類,這違背了“開閉原則”。如果引入抽象外觀類,則在一定程度上解決了該問題,其結構圖如圖 5 所示。