四種設計模式詳解

暴君熊123 發表於 2020-11-21


一、單例模式

1、單例模式的定義:

單例模式是設計模式中最簡單的形式之一。這一模式的目的是使得類的一個物件成為系統中的唯一例項。要實現這一點,可以從客戶端對其進行例項化開始。因此需要用一種只允許生成物件類的唯一例項的機制,“阻止”所有想要生成物件的訪問。
在這裡插入圖片描述

2、單例模式實現方法:

  1. 第一種實現:
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

這種寫法lazy loading很明顯,但是致命的是在多執行緒不能正常工作。

  1. 第二種實現:
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

這種寫法能夠在多執行緒中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。

  1. 第三種實現:
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}  

這種方式基於classloder機制避免了多執行緒的同步問題,不過,instance在類裝載時就例項化。

  1. 第四種實現:
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}   

二、抽象工廠

1、抽象工廠定義

抽象工廠模式是所有形態的工廠模式中最為抽象和最具一般性的一種形態。
為了方便引進抽象工廠模式,引進一個新概念:產品族(Product Family)。所謂產品族,是指位於不同產品等級結構,功能相關聯的產品組成的家族。如圖:

在這裡插入圖片描述
圖中一共有四個產品族,分佈於三個不同的產品等級結構中。只要指明一個產品所處的產品族以及它所屬的等級結構,就可以唯一的確定這個產品。
引進抽象工廠模式
所謂的抽象工廠是指一個工廠等級結構可以建立出分屬於不同產品等級結構的一個產品族中的所有物件。如果用圖來描述的話,如下圖:
在這裡插入圖片描述

程式碼如下(示例):

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2、Abstract Factory模式的結構:

在這裡插入圖片描述
圖中描述的東西用產品族描述如下:
在這裡插入圖片描述
抽象工廠(Abstract Factory)角色:擔任這個角色的是工廠方法模式的核心,它是與應用系統商業邏輯無關的。
具體工廠(Concrete Factory)角色:這個角色直接在客戶端的呼叫下建立產品的例項。這個角色含有選擇合適的產品物件的邏輯,而這個邏輯是與應用系統的商業邏輯緊密相關的。
抽象產品(Abstract Product)角色:擔任這個角色的類是工廠方法模式所建立的物件的父類,或它們共同擁有的介面。
具體產品(Concrete Product)角色:抽象工廠模式所建立的任何產品物件都是某一個具體產品類的例項。這是客戶端最終需要的東西,其內部一定充滿了應用系統的商業邏輯。

3、程式舉例:

using System;

// "AbstractFactory"
abstract class AbstractFactory
{
  // Methods
  abstract public AbstractProductA CreateProductA();
  abstract public AbstractProductB CreateProductB();
}

// "ConcreteFactory1"
class ConcreteFactory1 : AbstractFactory
{
  // Methods
  override public AbstractProductA CreateProductA()
  {
    return new ProductA1();
  }
  override public AbstractProductB CreateProductB()
  {
    return new ProductB1();
  }
}

// "ConcreteFactory2"
class ConcreteFactory2 : AbstractFactory
{
  // Methods
  override public AbstractProductA CreateProductA()
  {
    return new ProductA2();
  }

  override public AbstractProductB CreateProductB()
  {
    return new ProductB2();
  }
}

// "AbstractProductA"
abstract class AbstractProductA
{
}

// "AbstractProductB"
abstract class AbstractProductB
{
  // Methods
  abstract public void Interact( AbstractProductA a );
}

// "ProductA1"
class ProductA1 : AbstractProductA
{
}

// "ProductB1"
class ProductB1 : AbstractProductB
{
  // Methods
  override public void Interact( AbstractProductA a )
  {
    Console.WriteLine( this + " interacts with " + a );
  }
}

// "ProductA2"
class ProductA2 : AbstractProductA
{
}

// "ProductB2"
class ProductB2 : AbstractProductB
{
  // Methods
  override public void Interact( AbstractProductA a )
  {
    Console.WriteLine( this + " interacts with " + a );
  }
}

// "Client" - the interaction environment of the products
class Environment
{
  // Fields
  private AbstractProductA AbstractProductA;
  private AbstractProductB AbstractProductB;

  // Constructors
  public Environment( AbstractFactory factory )
  {
    AbstractProductB = factory.CreateProductB();
    AbstractProductA = factory.CreateProductA();
  }
 
  // Methods
  public void Run()
  {
    AbstractProductB.Interact( AbstractProductA );
  }
}

/**//// <summary>
/// ClientApp test environment
/// </summary>
class ClientApp
{
  public static void Main(string[] args)
  {
    AbstractFactory factory1 = new ConcreteFactory1();
    Environment e1 = new Environment( factory1 );
    e1.Run();

    AbstractFactory factory2 = new ConcreteFactory2();
    Environment e2 = new Environment( factory2 );
    e2.Run();
  }
}

4、在什麼情形下使用抽象工廠模式:

在以下情況下應當考慮使用抽象工廠模式:
一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有形態的工廠模式都是重要的。
這個系統有多於一個的產品族,而系統只消費其中某一產品族。
同屬於同一個產品族的產品是在一起使用的,這一約束必須在系統的設計中體現出來。
系統提供一個產品類的庫,所有的產品以同樣的介面出現,從而使客戶端不依賴於實現。

5、抽象工廠的起源

據說最早的應用是用來建立在不同作業系統的視窗環境下都能夠執行的系統。比如在Windows與Unix系統下都有視窗環境的構件,在每一個作業系統中,都有一個視窗構件組成的構件家族。我們可以通過一個抽象角色給出功能描述,而由具體子類給出不同作業系統下的具體實現,如圖:
在這裡插入圖片描述
可以發現上面產品類圖有兩個產品等級結構,分別是Button與Text;同時有兩個產品族:Unix產品族與Windows產品族。
在這裡插入圖片描述
系統對產品物件的建立要求由一個工廠的等級結構滿足。其中有兩個具體工廠角色,即UnixFactory和WinFactory。UnixFactory物件負責建立Unix產品族中的產品,而WinFactory負責建立Windows產品族中的產品。
在這裡插入圖片描述
顯然一個系統只能夠在某一個作業系統的視窗環境下執行,而不能同時在不同的作業系統上執行。所以,系統實際上只能消費屬於同一個產品族的產品。
在現代的應用中,抽象工廠模式的使用範圍已經大大擴大了,不再要求系統只能消費某一個產品族了。

6、Abstract Factory模式在實際系統中的實現

// "AbstractFactory"
abstract class ControlFactory
{
  // Methods
  abstract public Button CreateButton();
  abstract public Text CreateText();
}

// "ConcreteFactory1"
class WinFactory : ControlFactory
{
  // Methods
  override public Button CreateButton()
  { return new WinButton(); }

  override public Text CreateText()
  { return new Wintext(); }
}

// "ConcreteFactory2"
class UnixFactory : ControlFactory
{
  // Methods
  override public Button CreateButton()
  { return new UnixButton(); }

  override public Text CreateText()
  { return new UnixText(); }
}

// "AbstractProductA"
abstract class Button
{
}

// "AbstractProductB"
abstract class Text
{
}

// "ProductA1"
class WinButton : Button
{
}

// "ProductB1"
class WinText : Text
{
}

// "ProductA2"
class UnixButton : Button
{
}

// "ProductB2"
class UnixText : Text
{
  
}

// "Client"
class Form1
{
  // Fields
  private Button b1;
  private Text t1;

  // Constructors
  public AnimalWorld( ControlFactory factory )
  {
    b1 = factory.CreateButton();
    t1 = factory.CreateText();
  }

}

class GameApp
{
  public static void Main( string[] args )
  {
    ControlFactory Win= new WinFactory();
    Form1 f1 = new Form1(Win );

ControlFactory Unix= new UnixFactory();
    Form1 f2 = new Form1(Unix );
  }
}

抽象工廠的另外一個例子:
在這裡插入圖片描述

7、"開放-封閉"原則

"開放-封閉"原則要求系統對擴充套件開放,對修改封閉。通過擴充套件達到增強其功能的目的。對於涉及到多個產品族與多個產品等級結構的系統,其功能增強包括兩方面:
增加產品族:Abstract Factory很好的支援了"開放-封閉"原則。
增加新產品的等級結構:需要修改所有的工廠角色,沒有很好支援"開放-封閉"原則。
綜合起來,抽象工廠模式以一種傾斜的方式支援增加新的產品,它為新產品族的增加提供方便,而不能為新的產品等級結構的增加提供這樣的方便。

三、觀察者模式

1.觀察者(Observer)模式定義:
觀察者模式又叫做釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。
一個軟體系統常常要求在某一個物件的狀態發生變化的時候,某些其它的物件做出相應的改變。做到這一點的設計方案有很多,但是為了使系統能夠易於複用,應該選擇低耦合度的設計方案。減少物件之間的耦合有利於系統的複用,但是同時設計師需要使這些低耦合度的物件之間能夠維持行動的協調一致,保證高度的協作(Collaboration)。觀察者模式是滿足這一要求的各種設計方案中最重要的一種。
2.觀察者模式的結構
觀察者模式的類圖如下:
在這裡插入圖片描述
可以看出,在這個觀察者模式的實現裡有下面這些角色:
 抽象主題(Subject)角色:主題角色把所有對觀察考物件的引用儲存在一個聚集裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,主題角色又叫做抽象被觀察者(Observable)角色,一般用一個抽象類或者一個介面實現。
 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己。這個介面叫做更新介面。抽象觀察者角色一般用一個抽象類或者一個介面實現。在這個示意性的實現中,更新介面只包含一個方法(即Update()方法),這個方法叫做更新方法。
 具體主題(ConcreteSubject)角色:將有關狀態存入具體現察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實現。
 具體觀察者(ConcreteObserver)角色:儲存與主題的狀態自恰的狀態。具體現察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態相協調。如果需要,具體現察者角色可以儲存一個指向具體主題物件的引用。具體觀察者角色通常用一個具體子類實現。
從具體主題角色指向抽象觀察者角色的合成關係,代表具體主題物件可以有任意多個對抽象觀察者物件的引用。之所以使用抽象觀察者而不是具體觀察者,意味著主題物件不需要知道引用了哪些ConcreteObserver型別,而只知道抽象Observer型別。這就使得具體主題物件可以動態地維護一系列的對觀察者物件的引用,並在需要的時候呼叫每一個觀察者共有的Update()方法。這種做法叫做"針對抽象程式設計"。
3、 觀察者模式的示意性原始碼

// "Subject"
abstract class Subject
{
  // Fields
  private ArrayList observers = new ArrayList();

  // Methods
  public void Attach( Observer observer )
  {
    observers.Add( observer );
  }

  public void Detach( Observer observer )
  {
    observers.Remove( observer );
  }

  public void Notify()
  {
    foreach( Observer o in observers )
      o.Update();
  }
}

// "ConcreteSubject"
class ConcreteSubject : Subject
{
  // Fields
  private string subjectState;

  // Properties
  public string SubjectState
  {
    get{ return subjectState; }
    set{ subjectState = value; }
  }
}

// "Observer"
abstract class Observer
{
  // Methods
  abstract public void Update();
}

// "ConcreteObserver"
class ConcreteObserver : Observer
{
  // Fields
  private string observerState;
  private ConcreteSubject subject;

  // Constructors
  public ConcreteObserver( ConcreteSubject subject,  
    string name )
  {
    this.subject = subject;
    this.name = name;
  }

  // Methods
  override public void Update()
  {
    observerState = subject.SubjectState;
    Console.WriteLine( "Observer {0}'s new state is {1}",
      name, observerState );
  }

  // Properties
  public ConcreteSubject Subject
  {
    get { return subject; }
    set { subject = value; }
  }
}

/**//// <summary>
/// Client test
/// </summary>
public class Client
{
  public static void Main( string[] args )
  {
    // Configure Observer structure
    ConcreteSubject s = new ConcreteSubject();
    s.Attach( new ConcreteObserver( s, "1" ) );
    s.Attach( new ConcreteObserver( s, "2" ) );
    s.Attach( new ConcreteObserver( s, "3" ) );

    // Change subject and notify observers
    s.SubjectState = "ABC";
    s.Notify();
  }
}

4、 一個實際應用觀察者模式的例子

//該例子演示了註冊的投資者在股票市場發生變化時,可以自動得到通知。
// Observer pattern -- Real World example  
using System;
using System.Collections;

// "Subject"
abstract class Stock
{
  // Fields
  protected string symbol;
  protected double price;
  private ArrayList investors = new ArrayList();

  // Constructor
  public Stock( string symbol, double price )
  {
    this.symbol = symbol;
    this.price = price;
  }

  // Methods
  public void Attach( Investor investor )
  {
    investors.Add( investor );
  }

  public void Detach( Investor investor )
  {
    investors.Remove( investor );
  }

  public void Notify()
  {
    foreach( Investor i in investors )
      i.Update( this );
  }

  // Properties
  public double Price
  {
    get{ return price; }
    set
    {
      price = value;
      Notify(); 
    }
  }

  public string Symbol
  {
    get{ return symbol; }
    set{ symbol = value; }
  }
}

// "ConcreteSubject"
class IBM : Stock
{
  // Constructor
  public IBM( string symbol, double price )
    : base( symbol, price ) {}
}

// "Observer"
interface IInvestor
{
  // Methods
  void Update( Stock stock );
}

// "ConcreteObserver"
class Investor : IInvestor
{
  // Fields
  private string name;
  private string observerState;
  private Stock stock;

  // Constructors
  public Investor( string name )
  {
    this.name = name;
  }

  // Methods
  public void Update( Stock stock )
  {
    Console.WriteLine( "Notified investor {0} of {1}'s change to {2:C}", 
      name, stock.Symbol, stock.Price );
  }

  // Properties
  public Stock Stock
  {
    get{ return stock; }
    set{ stock = value; }
  }
}

/**//// <summary>
/// ObserverApp test
/// </summary>
public class ObserverApp
{
  public static void Main( string[] args )
  {
    // Create investors
    Investor s = new Investor( "Sorros" );
    Investor b = new Investor( "Berkshire" );

    // Create IBM stock and attach investors
    IBM ibm = new IBM( "IBM", 120.00 );
    ibm.Attach( s );
    ibm.Attach( b );

    // Change price, which notifies investors
    ibm.Price = 120.10;
    ibm.Price = 121.00;
    ibm.Price = 120.50;
    ibm.Price = 120.75;
  }
}
 

四、通用層次模式

這個模式出現在許多類圖中。你經常發現一組物件集有自然的層次關係。例如機構圖中經理及其下屬的關係,或檔案系統中的目錄(也叫資料夾)、子目錄和檔案之間的關係。在這樣一個層次中,每個物件可能沒有或有多個物件位於他們上層(上級),並且沒有或有多個物件在它們下層(下級)。但是某些物件不能有任何下級——例如機構圖中的工作人員(與經理相對),或檔案系統中的檔案(與日錄相對)。

安全通用層次模式類圖
在這裡插入圖片描述
透明通用層次模式類圖
在這裡插入圖片描述

透明模式的優點是所有的構件類都有相同的介面。在客戶端看來,樹葉類物件與合成類物件的區別起碼在介面層次上消失了,客戶端可以同等同的對待所有的物件。缺點是不夠安全,因為樹葉類物件和合成類物件在本質上是有區別的。樹葉類物件不可能有管理子類物件,但如果寫了這類方法在編譯時期不會出錯,而只會在執行時期才會出錯。
安全模式的優點時安全,因為樹葉型別的物件根本就沒有管理子類物件的方法,因此,如果客戶端對樹葉類物件使用這些方法時,程式會在編譯時期出錯。缺點是不夠透明,因為樹葉類和合成類將具有不同的介面。

abstract class Component
{
	public virtual void Operation();
}

class Composite : Component
{
  private ArrayList children = new ArrayList();
  public void Add( Component component ) 
  {
    children.Add( component );
  }
  public void Remove( Component component ) 
  {
    children.Remove( component );
  }
}

class Leaf : Component
{
public void Operation()
	{
}
}