設計模式彙總:結構型模型(下)
總體來說設計模式分為三大類:
建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。
其實還有兩類:併發型模式和執行緒池模式。
前面已經介紹了幾個結構型模型:《設計模式彙總:結構型模型(上)》
四、外觀模式(Fasade)
外觀模式:為子系統中的一組介面提供一個一致的介面, Facade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
引入外觀角色之後,使用者只需要直接與外觀角色互動,使用者與子系統之間的複雜關係由外觀角色來實現,從而降低了系統的耦合度。
即提供一個Facade類,來統一操作子系統;適用於子系統比較複雜的情況。
主要解決問題:元件的客戶和元件中各種複雜的子系統有了過多的耦合,隨著外部客戶程式和各子系統的演化,這種過多的耦合面臨很多變化的挑戰。
舉個例子:
比如,現在有一輛汽車,我們(客戶程式)要啟動它,那我們就要發動引擎(子系統1),使四個車輪(子系統2)轉動。但是實際中我們並不需要用手推動車輪使其轉動,我們踩下油門,此時汽車再根據一些其他的操作使車輪轉動。油門就好比系統給我們留下的介面,不論汽車是以何種方式轉動車輪,車輪變化成什麼牌子的,我們要開走汽車所要做的還是踩下油門。
使用外觀模式之後的效果:
類圖:
其中涉及到的角色有:
外觀角色(Facade):是模式的核心,他被客戶client角色呼叫,知道各個子系統的功能。同時根據客戶角色已有的需求預訂了幾種功能組合。
子系統角色(Subsystem classes)
程式碼:
// 子系統類
class SubClass1 {
void operation1() {
System.out.println("SubClass1 operation1");
}
}
class SubClass2 {
void operation2() {
System.out.println("SubClass2 operation2");
}
}
class SubClass3 {
void operation3() {
System.out.println("SubClass3 operation3");
}
}
// 外觀類
class Facade {
SubClass1 subClass1;
SubClass2 subClass2;
SubClass3 subClass3;
public Facade() {
subClass1 = new SubClass1();
subClass2 = new SubClass2();
subClass3 = new SubClass3();
}
public void method() {
subClass1.operation1();
subClass2.operation2();
subClass3.operation3();
}
}
// 使用者類
class Client {
public void doSth() {
Facade facade = new Facade();
facade.method();
}
}
適用場合:
1)當需要為複雜子系統提供一個外部簡單介面來供使用者呼叫使用時,子系統往往會因為不斷演化而變得越來越複雜;當一些使用者不需要定製子系統時,facade可以為使用者提供一個預設預設的檢視來供使用,當有其他需要定製的使用者,可以越過facade進行自行定製;這樣的模式在開源專案中比較常見,比如Retrofit這些。
2)為了避免客戶程式和抽象類存在很大的依賴性,引入facade來使得子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移植性。
3)減少子系統之間的耦合;當需要構建一個層次結構的子系統時,使用 facade模式定義子系統中每層的入口點。如果子系統之間是相互依賴的,可以讓子系統僅通過facade進行通訊,從而簡化了它們之間的依賴關係。
Facade模式的優缺點:
優點:
1)鬆散耦合:門面模式鬆散了客戶端與子系統的耦合關係,讓子系統內部的模組能更容易擴充套件和維護
簡單易用:門面模式讓子系統更加易用,客戶端不再需要了解子系統內部的實現,也不需要跟蹤多個子系統內部的模組進行互動,只需要跟門面類互動就可以了
更好的劃分訪問層次:通過合理的使用Facade,可以幫助我們更好的劃分訪問的層次。有些方法是對系統外的,有些方法是系統內部使用的。把需要暴露給外部的功能集中到門面中,這樣既方便客戶端使用,也很好地掩藏了內部的細節。
缺點:
1) 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
2) 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的原始碼,違背了“開閉原則”。
五、橋接模式(Bridge)
意圖:將抽象部分與實現部分分離,使它們都可以獨立的變化。
適用場景:將抽象與實現分離
設想如果要繪製矩形、圓形、橢圓、正方形,我們至少需要4個形狀類,但是如果繪製的圖形需要具有不同的顏色,如紅色、綠色、藍色等,此時至少有如下兩種設計方案:
第一種設計方案是為每一種形狀都提供一套各種顏色的版本。
第二種設計方案是根據實際需要對形狀和顏色進行組合
對於有兩個變化維度(即兩個變化的原因)的系統,採用方案二來進行設計系統中類的個數更少,且系統擴充套件更為方便。設計方案二即是橋接模式的應用。橋接模式將繼承關係轉換為關聯關係,從而降低了類與類之間的耦合,減少了程式碼編寫量。
類圖:
實現上面的案例得:
// Abstraction抽象類
abstract class IShape {
protected IColor color;
public IShape(IColor color) {
this.color = color;
}
abstract void operation();
}
// Implementor實現類介面
abstract class IColor {
abstract void operationImpl();
}
// ConcreteImplementor具體實現類
class ColorRed extends IColor{
@Override
void operationImpl() {
System.out.print("Color is Red");
}
}
class ColorBlue extends IColor{
@Override
void operationImpl() {
System.out.print("Color is Blue");
}
}
// RefinedAbstraction擴充抽象類
class Rectangle extends IShape {
public Rectangle(IColor color) {
super(color);
}
@Override
void operation() {
System.out.print("It is a rectangle");
color.operationImpl();
}
}
class Oval extends IShape {
public Oval(IColor color) {
super(color);
}
@Override
void operation() {
System.out.print("It is a Oval");
color.operationImpl();
}
}
// Cient使用類
class Client {
public void doTest() {
IShape shape1 = new Rectangle(new ColorRed());
IShape shape2 = new Oval(new ColorBlue());
shape1.operation();
shape2.operation();
}
}
優缺點:
1)優點:
分離抽象介面及其實現部分。
橋接模式有時類似於多繼承方案,但是多繼承方案違背了類的單一職責原則(即一個類只有一個變化的原因),複用性比較差,而且多繼承結構中類的個數非常龐大,橋接模式是比多繼承方案更好的解決方法。
橋接模式提高了系統的可擴充性,在兩個變化維度中任意擴充套件一個維度,都不需要修改原有系統。
實現細節對客戶透明,可以對使用者隱藏實現細節。
2)缺點 :
橋接模式的引入會增加系統的理解與設計難度,由於聚合關聯關係建立在抽象層,要求開發者針對抽象進 行設計與程式設計。
橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性。
適用環境:
如果你不希望在抽象和實現部分採用固定的繫結關係,可以採用橋接模式,來把抽象和實現部分分開,然後在程式執行期間來動態的設定抽象部分需要用到的具體的實現,還可以動態切換具體的實現。
如果出現抽象部分和實現部分都應該可以擴充套件的情況,可以採用橋接模式,讓抽象部分和實現部分可以獨立的變化,從而可以靈活的進行單獨擴充套件,而不是攪在一起,擴充套件一邊會影響到另一邊。
如果希望實現部分的修改,不會對客戶產生影響,可以採用橋接模式,客戶是面向抽象的介面在執行,實現部分的修改,可以獨立於抽象部分,也就不會對客戶產生影響了,也可以說對客戶是透明的。
如果採用繼承的實現方案,會導致產生很多子類,對於這種情況,可以考慮採用橋接模式,分析功能變化的原因,看看是否能分離成不同的緯度,然後通過橋接模式來分離它們,從而減少子類的數目。
六、組合模式(Composite)
概念:將物件組合成樹形結構以表示“部分-整體”的層次結構。 組合模式使得使用者對單個物件和組合物件的使用具有唯一性。
應用:組合模式比較典型的應用就是
1)檔案目錄結構
2)Android中的ViewGroup和View
適用性:組合模式解耦了客戶程式與複雜元素內部結構,從而使客戶程式可以向處理簡單元素一樣來處理複雜元素。
如果你想要建立層次結構,並可以在其中以相同的方式對待所有元素,那麼組合模式就是最理想的選擇。
1)表示物件的部分-整體層次結構,如樹形選單、資料夾選單、部門組織架構等。
2)使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。
類圖:
涉及角色:
1) Component:是組合中的物件宣告介面,在適當的情況下,實現所有類共有介面的預設行為。宣告一個介面用於訪問和管理Component.
2) Leaf:在組合中表示葉子節點物件,葉子節點沒有子節點。
3) Composite:定義樹枝節點行為,用來儲存子部件,在Component介面中實現與子部件有關操作,如增加和刪除等。
程式碼:
abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
abstract void operation();
abstract void add(Component component);
abstract void remove(Component component);
abstract Component getChild(int i);
}
class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
void operation() {
System.out.println("Leaf " + name);
}
@Deprecated
void add(Component component) {}
@Deprecated
void remove(Component component) {}
@Deprecated
Component getChild(int i) {return null;}
}
class Composite extends Component {
ArrayList<Component> childs;
public Composite(String name) {
super(name);
childs = new ArrayList<>();
}
@Override
void operation() {
for (Component child : childs)
child.operation();
}
@Override
void add(Component component) {
childs.add(component);
}
@Override
void remove(Component component) {
childs.remove(component);
}
@Override
Component getChild(int i) {
return childs.get(i);
}
}
class Client {
public void doTest() {
Component leaf1 = new Leaf("leaf1");
Composite com = new Composite("Composite");
com.add(leaf1);
com.add(new Leaf("leaf2"));
Composite com2 = new Composite("Composite2");
com2.add(new Leaf("l3"));
com2.add(com);
}
}
優缺點:
優點:
1)可以清楚地定義分層次的複雜物件,表示物件的全部或部分層次,使得增加新構件也更容易。
2)客戶端呼叫簡單,客戶端可以一致的使用組合結構或其中單個物件。
3)定義了包含葉子物件和容器物件的類層次結構,葉子物件可以被組合成更復雜的容器物件,而這個容器物件又可以被組合,這樣不斷遞迴下去,可以形成複雜的樹形結構。
4)更容易在組合體內加入物件構件,客戶端不必因為加入了新的物件構件而更改原有程式碼。
缺點 :使設計變得更加抽象,物件的業務規則如果很複雜,則實現組合模式具有很大挑戰性,而且不是所有的方法都與葉子物件子類都有關聯
七、享元模式(FlyWeight)
背景:面向物件可以非常方便的解決一些擴充套件性的問題,但是在這個過程中系統務必會產生一些類或者物件,如果系統中存在物件的個數過多時,將會導致系統的效能下降。對於這樣的問題解決最簡單直接的辦法就是減少系統中物件的個數。
享元模式提供了一種解決方案,使用共享技術實現相同或者相似物件的重用。也就是說實現相同或者相似物件的程式碼共享。
Java中最典型的應用就是String:
String s1 = "a";
String s2 = "a";
System.out.println(s1 == s2);// String採用享元模式 儲存在常量池
定義:運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用。由於享元模式要求能夠共享的物件必須是細粒度物件,因此它又稱為輕量級模式,它是一種物件結構型模式。
概念:在瞭解享元模式之前我們先要了解兩個概念:內部狀態、外部狀態。
內部狀態:在享元物件內部不隨外界環境改變而改變的共享部分。
外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態。
由於享元模式區分了內部狀態和外部狀態,所以我們可以通過設定不同的外部狀態使得相同的物件可以具備一些不同的特性,而內部狀態設定為相同部分。在我們的程式設計過程中,我們可能會需要大量的細粒度物件來表示物件,如果這些物件除了幾個引數不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程式當中的物件。如何利用享元模式呢?這裡我們只需要將他們少部分的不同的部分當做引數移動到類例項的外部去,然後再方法呼叫的時候將他們傳遞過來就可以了。這裡也就說明了一點:內部狀態儲存於享元物件內部,而外部狀態則應該由客戶端來考慮。
享元模式分為兩種:單純享元模式和複合享元模式兩種形式;
(一)單純享元模式:
在單純的享元模式中,所有的享元物件都是可以共享的。
類圖:
涉及角色:
Flyweight: 抽象享元類。所有具體享元類的超類或者介面,通過這個介面,Flyweight可以接受並作用於外部專題
ConcreteFlyweight: 具體享元類。指定內部狀態,為內部狀態增加儲存空間。
FlyweightFactory: 享元工廠類。用來建立並管理Flyweight物件,它主要用來確保合理地共享Flyweight,當用戶請求一個Flyweight時,FlyweightFactory就會提供一個已經建立的Flyweight物件或者新建一個(如果不存在)。
享元模式的核心在於享元工廠類,享元工廠類的作用在於提供一個用於儲存享元物件的享元池,使用者需要物件時,首先從享元池中獲取,如果享元池中不存在,則建立一個新的享元物件返回給使用者,並在享元池中儲存該新增物件。
程式碼:
abstract class Flywight {
abstract void operation(String externalState);
}
class ConcreteFlyweight extends Flywight {
private String internalState;
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
@Override
void operation(String externalState) {
System.out.println("內部狀態:" + internalState);
System.out.println("外部狀態:" + externalState);
}
}
class FlyweightFactory {
HashMap<String, Flywight> map = new HashMap<>();
public Flywight getFlyweight(String state) {
Flywight flywight = map.get(state);
if (flywight == null) {
flywight = new ConcreteFlyweight(state);
map.put(state, flywight);
}
return flywight;
}
}
(二)複合享元模式:
在單純享元模式中,所有的享元物件都是單純享元物件,也就是說都是可以直接共享的。還有一種較為複雜的情況,將一些單純享元使用合成模式加以複合,形成複合享元物件。這樣的複合享元物件本身不能共享,但是它們可以分解成單純享元物件,而後者則可以共享。
類圖:
角色:
抽象享元(Flyweight) :給出一個抽象介面,以規定出所有具體享元角色需要實現的方法。
具體享元(ConcreteFlyweight):實現抽象享元角色所規定出的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。
複合享元(ConcreteCompositeFlyweight) :複合享元角色所代表的物件是不可以共享的,但是一個複合享元物件可以分解成為多個本身是單純享元物件的組合。複合享元角色又稱作不可共享的享元物件。
享元工廠(FlyweightFactory) :負責建立和管理享元角色。
程式碼:
abstract class Flywight {
abstract void operation(String externalState);
}
class ConcreteFlyweight extends Flywight {
private String internalState;
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
@Override
void operation(String externalState) {
System.out.println("內部狀態:" + internalState);
System.out.println("外部狀態:" + externalState);
}
}
class ConcreteCompositeFlyweight extends Flywight {
Map<String, Flywight> list = new HashMap<>();
public void add(String state, Flywight flywight) {
list.put(state, flywight);
}
@Override
void operation(String externalState) {
for (Map.Entry<String, Flywight> data : list.entrySet()) {
data.getValue().operation(externalState);
}
}
}
class FlyweightFactory {
HashMap<String, Flywight> map = new HashMap<>();
public Flywight getFlyweight(List<String> states) {
ConcreteCompositeFlyweight ccFlyweight = new ConcreteCompositeFlyweight();
for (String state : states)
ccFlyweight.add(state, getFlyweight(state));
return ccFlyweight;
}
public Flywight getFlyweight(String state) {
Flywight flywight = map.get(state);
if (flywight == null) {
flywight = new ConcreteFlyweight(state);
map.put(state, flywight);
}
return flywight;
}
}
優缺點:
享元模式的優點在於它大幅度地降低記憶體中物件的數量。但是,它做到這一點所付出的代價也是很高的:
1)享元模式使得系統更加複雜。為了使物件可以共享,需要將一些狀態外部化,這使得程式的邏輯複雜化。
2)享元模式將享元物件的狀態外部化,而讀取外部狀態使得執行時間稍微變長。