久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

技術(shù)圖文:03 結(jié)構(gòu)型設(shè)計(jì)模式(上)

 老馬的程序人生 2020-11-14

結(jié)構(gòu)型設(shè)計(jì)模式(上)

本教程主要介紹一系列用于如何將現(xiàn)有類或?qū)ο蠼M合在一起形成更加強(qiáng)大結(jié)構(gòu)的經(jīng)驗(yàn)總結(jié),。

知識(shí)結(jié)構(gòu):

圖1 知識(shí)結(jié)構(gòu)

享元模式 -- 實(shí)現(xiàn)對(duì)象的復(fù)用

Sunny 軟件公司欲開發(fā)一個(gè)圍棋軟件,,其界面效果如下圖所示:

圖2 圍棋軟件界面效果圖

Sunny 軟件公司開發(fā)人員通過對(duì)圍棋軟件進(jìn)行分析,發(fā)現(xiàn)在圍棋棋盤中包含大量的黑子和白子,,它們的形狀,、大小都一模一樣,只是出現(xiàn)的位置不同而已,。

如果將每一個(gè)棋子都作為一個(gè)獨(dú)立的對(duì)象存儲(chǔ)在內(nèi)存中,將導(dǎo)致圍棋軟件在運(yùn)行時(shí)所需內(nèi)存空間較大,,如何降低運(yùn)行代價(jià),、提高系統(tǒng)性能是 Sunny 公司開發(fā)人員需要解決的一個(gè)問題

為了節(jié)約存儲(chǔ)空間,,提高系統(tǒng)性能,Sunny 公司開發(fā)人員使用共享技術(shù)來設(shè)計(jì)圍棋軟件中的棋子,,其基本結(jié)構(gòu)如下圖所示:


圖3 圍棋棋子結(jié)構(gòu)圖

Chessman充當(dāng)抽象享元類,,WhiteChessmanBlackChessman是具體享元類,,ChessmanFactory是享元工廠類,。

在實(shí)現(xiàn)該類時(shí),,使用了 單列模式簡(jiǎn)單工廠模式,,確保了工廠對(duì)象的唯一性,并提供工廠方法來向客戶端返回共享對(duì)象,。

將棋子的位置Coordinates定義為棋子的一個(gè)外部狀態(tài),在需要時(shí)再進(jìn)行設(shè)置,,這樣即解決了黑子和白子的共享,又解決了顯示不同位置的問題,。

完整代碼如下:

(1)棋子位置類(坐標(biāo)類):

public class Coordinates
{

    public int X { get; set; }
    public int Y { get; set; }
    public Coordinates(int x, int y)
    
{
        X = x;
        Y = y;
    }
    public override string ToString()
    
{
        return X + "," + Y;
    }
}

(2)棋子類:

//棋子抽象類
public abstract class Chessman
{

    public abstract string Color { get; }
    public void Display(Coordinates coord)
    
{
        Console.WriteLine("棋子顏色:{0},位置:{1}", Color, coord);
    }
}

//白色棋子實(shí)體類
public class WhiteChessman : Chessman
{
    public override string Color
    {
        get { return "白色"; }
    }
}

//黑色棋子實(shí)體類
public class BlackChessman : Chessman
{
    public override string Color
    {
        get { return "黑色"; }
    }
}

(3)圍棋棋子工廠類:

public class ChessmanFactory
{

    private static readonly ChessmanFactory Instance = new ChessmanFactory();
    private static Hashtable _ht; //使用HashTable來存儲(chǔ)共享對(duì)象
    private ChessmanFactory()
    
{
        _ht = new Hashtable();
        Chessman black = new BlackChessman();
        Chessman white = new WhiteChessman();
        _ht.Add("b", black);
        _ht.Add("w", white);
    }
    //返回共享工廠的唯一實(shí)例
    public static ChessmanFactory GetInstance()
    
{
        return Instance;
    }
    //通過key獲取存儲(chǔ)在HashTable中的共享對(duì)象
    public Chessman GetChessman(string color)
    
{
        return _ht[color] as Chessman;
    }
}

(4)客戶端:

static void Main(string[] args)
{
    ChessmanFactory factory = ChessmanFactory.GetInstance();
    Chessman black1 = factory.GetChessman("b");
    Chessman black2 = factory.GetChessman("b");
    Chessman black3 = factory.GetChessman("b");
    Console.WriteLine("判斷兩顆黑子是否相同:{0}", object.ReferenceEquals(black1, black2));
    Chessman white1 = factory.GetChessman("w");
    Chessman white2 = factory.GetChessman("w");
    Console.WriteLine("判斷兩顆白子是否相同:{0}", object.ReferenceEquals(white1, white2));
    black1.Display(new Coordinates(12));
    black2.Display(new Coordinates(34));
    black3.Display(new Coordinates(13));
    white1.Display(new Coordinates(25));
    white2.Display(new Coordinates(24));
}

輸出結(jié)果如下圖所示:

圖4 運(yùn)行結(jié)果

從輸出結(jié)果可以看出,,雖然我們獲取了三個(gè)黑子對(duì)象和兩個(gè)白子對(duì)象,,但是它們的內(nèi)存地址相同,,也就是說,它們實(shí)際上是同一個(gè)對(duì)象,。

在每次調(diào)用display()方法時(shí),,由于設(shè)置了不同的外部狀態(tài),所以顯示在棋盤的不同位置,。

若一個(gè)軟件系統(tǒng)在運(yùn)行時(shí)產(chǎn)生的對(duì)象數(shù)量太多,,將導(dǎo)致運(yùn)行代價(jià)過高,帶來系統(tǒng)性能下降等問題,。為了避免系統(tǒng)中出現(xiàn)大量相同或相似的對(duì)象,同時(shí)又不影響客戶端程序通過面向?qū)ο蟮姆绞綄?duì)這些對(duì)象進(jìn)行操作,,享元模式 因此誕生,。

享元模式Flyweight Pattern):通過共享技術(shù)實(shí)現(xiàn)相同或相似對(duì)象的重用,存儲(chǔ)這些對(duì)象的地方稱為享元池(Flyweight Pool),。

享元模式 以共享的方式高效地支持大量細(xì)粒度對(duì)象的重用,,享元對(duì)象能做到共享的關(guān)鍵是區(qū)分了內(nèi)部狀態(tài)Intrinsic State)和外部狀態(tài)Extrinsic State)。

  • 內(nèi)部狀態(tài)是存儲(chǔ)在享元對(duì)象內(nèi)部并且不會(huì)隨著環(huán)境改變而改變的狀態(tài),,內(nèi)部狀態(tài)可以共享,。
  • 外部狀態(tài)是隨環(huán)境改變而改變的、不可以共享的狀態(tài),。通常由客戶端保存,,并在創(chuàng)建享元對(duì)象之后,需要使用時(shí)傳入到享元對(duì)象內(nèi)部,。每個(gè)外部狀態(tài)之間都是相互獨(dú)立的,。

我們可以將具有相同內(nèi)部狀態(tài)的對(duì)象存儲(chǔ)在享元池中,,享元池中的對(duì)象是可以共享的,需要的時(shí)候就將對(duì)象從享元池中取出,,實(shí)現(xiàn)對(duì)象的復(fù)用,。通過向取出對(duì)象注入不同的外部狀態(tài),可以得到一系列相似的對(duì)象,,而這些對(duì)象在內(nèi)存中實(shí)際上只存儲(chǔ)一份,。

享元模式 結(jié)構(gòu)較為復(fù)雜,一般與 簡(jiǎn)單工廠模式 一起使用,,其結(jié)構(gòu)如下圖所示:

圖5 享元模式類圖

  • Flyweight(抽象享元類):通常是一個(gè)接口或抽象類,,在抽象享元類中要將內(nèi)部狀態(tài)和外部狀態(tài)分開處理,通常將內(nèi)部狀態(tài)作為享元類的屬性,,而外部狀態(tài)通過注入的方式添加到享元類中,。
  • ConcreteFlyweight(實(shí)體享元類):它實(shí)現(xiàn)/繼承了抽象享元類,,其實(shí)例稱為享元對(duì)象,。
  • UnsharedConcreteFlyweight(非共享實(shí)體享元類):并不是所有的抽象享元類的子類都需要被共享,不能被共享的子類可設(shè)計(jì)為非共享實(shí)體享元類,,當(dāng)需要一個(gè)非共享實(shí)體享元類的對(duì)象時(shí)可以直接通過實(shí)例化創(chuàng)建,。
  • FlyweightFactory(享元工廠類):用于創(chuàng)建并管理享元對(duì)象,它針對(duì)抽象享元類編程,,將各種類型的實(shí)體享元對(duì)象存儲(chǔ)在一個(gè)享元池中,,享元池一般設(shè)計(jì)為一個(gè)存儲(chǔ)“鍵值對(duì)”的集合,可以結(jié)合 簡(jiǎn)單工廠模式 進(jìn)行設(shè)計(jì),。當(dāng)用戶請(qǐng)求一個(gè)實(shí)體享元對(duì)象時(shí),,享元工廠提供一個(gè)存儲(chǔ)在享元池中已經(jīng)創(chuàng)建的實(shí)例或者創(chuàng)建一個(gè)新的實(shí)例(如果不存在的話),返回新創(chuàng)建的實(shí)例并將其存儲(chǔ)在享元池中,。在一個(gè)系統(tǒng)中,,通常只有唯一一個(gè)享元工廠,因此可以使用 單例模式 進(jìn)行享元工廠類的設(shè)計(jì),。

注意:享元模式 需要維護(hù)一個(gè)記錄了系統(tǒng)已有的所有享元對(duì)象的列表,,而這本身需要耗費(fèi)資源。另外,,為了使享元對(duì)象可以共享,,需要將一些狀態(tài)外部化,使得程序邏輯復(fù)雜化,。因此,,應(yīng)當(dāng)在有足夠多的實(shí)例對(duì)象可供共享時(shí)才值得使用享元模式。

享元模式與字符串

C# 語(yǔ)言中,,如果每次執(zhí)行類似string str1 = “abcd”的操作時(shí)都創(chuàng)建一個(gè)新的字符串對(duì)象將導(dǎo)致內(nèi)存開銷很大,,因此如果第一次創(chuàng)建了內(nèi)容為“abcd”的字符串對(duì)象str1,,下一次再創(chuàng)建相同的字符串對(duì)象str2時(shí)會(huì)將它的引用指向str1,不會(huì)重新分配內(nèi)存空間,,從而實(shí)現(xiàn)了“abcd”在內(nèi)存中的共享,。

見以下程序代碼:

class Program
{

    static void Main(string[] args)
    
{
       string str1 = "abcd";
       string str2 = "abcd";
       string str3 = "ab" + "cd";
       string str4 = "ab";
       str4 += "cd";

       Console.WriteLine(object.ReferenceEquals(str1, str2));
       Console.WriteLine(object.ReferenceEquals(str1, str3));
       Console.WriteLine(object.ReferenceEquals(str1, str4));
       Console.WriteLine(str1 == str4);
       Console.WriteLine(str1.Equals(str4));
       str2 += "e";
       Console.WriteLine(object.ReferenceEquals(str1, str2));
    }
}

圖6 程序運(yùn)行結(jié)果

  • 前兩個(gè)輸出語(yǔ)句均為True,說明str1,,str2,,str3在內(nèi)存中引用了相同的對(duì)象。
  • 字符串str4的初值為“ab”,,再對(duì)它進(jìn)行操作str4 += “cd”,,此時(shí)雖然str4的內(nèi)容與str1相同,但是由于str4的初始值不同,,在創(chuàng)建str4時(shí)重新分配了內(nèi)存,,所以第三個(gè)輸出語(yǔ)句結(jié)果為false
  • 最后一個(gè)輸出語(yǔ)句也為false,,說明當(dāng)對(duì)str2進(jìn)行修改時(shí)將創(chuàng)建一個(gè)新的對(duì)象,,修改工作在新對(duì)象上完成,而原來引用的對(duì)象并沒有發(fā)生任何改變,,str1仍然引用原有對(duì)象,,而str2引用新對(duì)象,str1str2引用了兩個(gè)完全不同的對(duì)象(Copy On Write),。

注:string類型是一個(gè)特殊的引用類型,,它的判斷不同于其它引用類型去比較對(duì)象引用是否指向堆中同一實(shí)例,而是和值類型判斷一致,,比較對(duì)象內(nèi)容是否一一相等,。


外觀模式 -- 為外部調(diào)用提供統(tǒng)一入口

Sunny 軟件公司欲開發(fā)一個(gè)可應(yīng)用于多個(gè)系統(tǒng)的文件加密模塊,該模塊可以對(duì)文件中的數(shù)據(jù)進(jìn)行加密并將加密之后的數(shù)據(jù)存儲(chǔ)在一個(gè)新文件中,,具體的流程包括三個(gè)部分,,分別是讀取源文件、加密,、保存加密之后的文件,,其中,讀取文件和保存文件使用流來實(shí)現(xiàn),,加密操作通過求模運(yùn)算實(shí)現(xiàn),。

這三個(gè)操作相對(duì)獨(dú)立,為了實(shí)現(xiàn)代碼的獨(dú)立重用,,讓設(shè)計(jì)更符合 單一職責(zé)原則,,這三個(gè)操作的業(yè)務(wù)代碼封裝在三個(gè)不同的類中。

通過分析,得到軟件結(jié)構(gòu)如下圖所示:

圖7 文件加密模塊結(jié)構(gòu)圖

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

(1)FileReader:充當(dāng)文件讀取的子系統(tǒng)類,。

internal class FileReader
{

    public string Read(string fileNameSrc)
    
{
        StringBuilder sb = new StringBuilder();
        try
        {
            FileStream fs = new FileStream(fileNameSrc, FileMode.Open);
            int data;
            while ((data = fs.ReadByte()) != -1)
            {
                sb.Append((char) data);
            }
            fs.Close();
        }
        catch (Exception e)
        {
            Console.WriteLine("讀取文件錯(cuò)誤:" + e.Message);
            return null;
        }
        Console.WriteLine("讀取文件,,獲取明文:" + sb);
        return sb.ToString();
    }
}

(2)FileWriter:充當(dāng)文件保存的子系統(tǒng)類。

internal class FileWriter
{

    public void Write(string encryptStr, string fileNameDes)
    
{
       try
       {
          FileStream fs = new FileStream(fileNameDes, FileMode.Create);
          byte[] str = Encoding.Default.GetBytes(encryptStr);
          fs.Write(str, 0, str.Length);
          fs.Flush();
          fs.Close();
       }
       catch (Exception e)
       {
          string info = "保存文件錯(cuò)誤:" + e.Message;
          Console.WriteLine(info);
          return;
       }
       Console.WriteLine("保存密文,,寫入文件,。");
    }
}

(3)CipherMachine:充當(dāng)數(shù)據(jù)加密的子系統(tǒng)類。

internal class CipherMachine
{

    public string Encrypt(string plainText)
    
{
        Console.Write("數(shù)據(jù)加密,,將明文轉(zhuǎn)換為密文:");
        StringBuilder sb = new StringBuilder();
        char[] chars = plainText.ToCharArray();
        foreach (char ch in chars)
        {
            sb.Append((ch%7));
        }
        Console.WriteLine(sb);
        return sb.ToString();
    }
}

(4)EncryptFacade:充當(dāng)加密外觀類,,為外部調(diào)用提供統(tǒng)一入口。

public class EncryptFacade
{
     
    private readonly FileReader _reader;
    private readonly CipherMachine _cipher;
    private readonly FileWriter _writer;
    
    public EncryptFacade()
    
{
        _reader = new FileReader();
        _cipher = new CipherMachine();
        _writer = new FileWriter();
    }    
    
    public void FileEncrypt(string fileNameSrc, string fileNameDes)
    
{
        string plainStr = _reader.Read(fileNameSrc);
        if (string.IsNullOrEmpty(plainStr))
        {
            return;
        }
        string encryptStr = _cipher.Encrypt(plainStr);
        _writer.Write(encryptStr, fileNameDes);
    }
}

(5)客戶端代碼:

class Program
{

    static void Main(string[] args)
    
{
        EncryptFacade ef = new EncryptFacade();
        ef.FileEncrypt("src.txt""des.txt");
    }
}

輸出結(jié)果:

圖8 輸出結(jié)果

在本案例中,,對(duì)文件 src.txt 中的數(shù)據(jù)進(jìn)行加密,,該文件內(nèi)容為 “Hello world!”,加密之后將密文保存到另一個(gè)文件 des.txt 中,,程序運(yùn)行后保存在文件中的密文為“233364062325”,。

在軟件開發(fā)中,有時(shí)候?yàn)榱送瓿梢豁?xiàng)較為復(fù)雜的功能,,一個(gè)客戶類需要和多個(gè)業(yè)務(wù)類交互,,由于涉及到的類比較多,導(dǎo)致使用時(shí)代碼較為復(fù)雜,,此時(shí),,特別需要一個(gè)角色,由它來負(fù)責(zé)和多個(gè)業(yè)務(wù)類進(jìn)行交互,,而客戶類只需與該類交互。

外觀模式 通過引入一個(gè)外觀類(Facade)來充當(dāng)這個(gè)角色,,為多個(gè)業(yè)務(wù)類的調(diào)用提供統(tǒng)一的入口,,簡(jiǎn)化了類與類之間的交互。在外觀模式中,,那些需要交互的業(yè)務(wù)類被稱為子系統(tǒng)(Subsystem),。

如果沒有外觀類,那么每個(gè)客戶類需要和多個(gè)子系統(tǒng)之間進(jìn)行復(fù)雜的交互,,系統(tǒng)的耦合度將很大,,如圖 9(A)所示;而引入外觀類之后,,客戶類只需要直接與外觀類交互,,客戶類與子系統(tǒng)之間原有的復(fù)雜關(guān)系由外觀類來實(shí)現(xiàn),從而降低了系統(tǒng)的耦合度,,如圖 9(B)所示,。

圖9 外觀模式示意圖

外觀模式 并不給系統(tǒng)增加任何新功能,它僅僅是簡(jiǎn)化調(diào)用接口。

外觀模式(Facade Pattern):又稱為門面模式,,為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的入口,。


外觀模式迪米特法則 的一種具體實(shí)現(xiàn),通過引入一個(gè)新的外觀角色可以降低原有系統(tǒng)的復(fù)雜度,,同時(shí)降低客戶類與子系統(tǒng)的耦合度,。

外觀模式 的結(jié)構(gòu)如下圖所示:

圖10 外觀模式結(jié)構(gòu)圖

  • Facade:在 Client 可以調(diào)用它的方法,Facade 知道相關(guān)的(一個(gè)或者多個(gè)) SubSystem 的功能和責(zé)任,;在正常情況下,,它將所有從 Client 發(fā)來的請(qǐng)求委派給相應(yīng)的 SubSystem 處理。

  • SubSystem:在軟件系統(tǒng)中可以有一個(gè)或者多個(gè) SubSystem,,每個(gè) SubSystem 可以不是一個(gè)單獨(dú)的類,,而是一個(gè)類的集合,它實(shí)現(xiàn) SubSystem 的功能,;每一個(gè) SubSystem 都可以被 Client 直接調(diào)用,,或者被 Facade 調(diào)用;SubSystem 并不知道 Facade 的存在,,對(duì)于 SubSystem 而言,,Facade 僅僅是另外一個(gè) Client 而已。

(1)子系統(tǒng)類

internal class SubSystemA
{

    public void MethodA()
    
{
        //業(yè)務(wù)實(shí)現(xiàn)代碼
    }
}

internal class SubSystemB
{

    public void MethodB()
    
{
        //業(yè)務(wù)實(shí)現(xiàn)代碼
    }
}

internal class SubSystemC
{

    public void MethodC()
    
{
        //業(yè)務(wù)實(shí)現(xiàn)代碼
    }
}

(2)外觀類

public class Facade
{

    private SubSystemA _obj1 = new SubSystemA();
    private SubSystemB _obj2 = new SubSystemB();
    private SubSystemC _obj3 = new SubSystemC();

    public void Method()
    
{
        _obj1.MethodA();
        _obj2.MethodB();
        _obj3.MethodC();
    }
}

(3)客戶端

class Program
{

    static void Main(string[] args)
    
{
        Facade facade = new Facade();
        facade.Method();
    }
}

抽象外觀類

如果在案例“文件加密模塊”中需要更換一個(gè)加密類,,不再使用原有的基于求模運(yùn)算的加密類CipherMachine,,而改為基于移位運(yùn)算的新加密類NewCipherMachine,其代碼如下:

internal string Encrypt(string plainText)
{
    Console.Write("數(shù)據(jù)加密,,將明文轉(zhuǎn)換為密文:");
    StringBuilder sb = new StringBuilder();
    int key = 10//設(shè)置密鑰,,移位數(shù)為10
    char[] chars = plainText.ToCharArray();

    foreach (char ch in chars)
    {
        int temp = Convert.ToInt32(ch);
        //小寫字母移位
        if (ch >= 'a' && ch <= 'z')
        {
            temp += key%26;
            if (temp > 122) temp -= 26;
            if (temp < 97) temp += 26;
        }
        //大寫字母移位
        if (ch >= 'A' && ch <= 'Z')
        {
            temp += key%26;
            if (temp > 90) temp -= 26;
            if (temp < 65) temp += 26;
        }
        sb.Append((char) temp));
    }
    Console.WriteLine(sb);
    return sb.ToString();
}

如果不增加新的外觀類,只能通過修改原有外觀類EncryptFacade的源代碼來實(shí)現(xiàn)加密類的更換,,將原有的對(duì)CipherMachine類型對(duì)象的引用改為對(duì)NewCipherMachine類型對(duì)象的引用,,這違背了 開閉原則,因此需要通過增加新的外觀類來實(shí)現(xiàn)對(duì)子系統(tǒng)對(duì)象引用的改變,。

如果增加一個(gè)新的外觀類NewEncryptFacade來與FileReader,、FileWriter以及新增加的NewCipherMachine類進(jìn)行交互,雖然原有系統(tǒng)類庫(kù)無須做任何修改,,但是因?yàn)榭蛻舳舜a中原來針對(duì)EncryptFacade類進(jìn)行編程,,現(xiàn)在需要改為NewEncryptFacade類,因此需要修改客戶端源代碼,。

如何在不修改客戶端代碼的前提下使用新的外觀類呢,?

圖11 引入抽象外觀類之后的文件加密模塊結(jié)構(gòu)圖

(1)抽象外觀類

public abstract class AbstractEncryptFacade
{

    public abstract void FileEncrypt(string fileNameSrc, string fileNameDes);
}

(2)新增加的實(shí)體外觀類

public class NewEncryptFacade : AbstractEncryptFacade
{
    private readonly FileReader _reader;
    private readonly NewCipherMachine _cipher;
    private readonly FileWriter _writer;

    public NewEncryptFacade()
    
{
        _reader = new FileReader();
        _cipher = new NewCipherMachine();
        _writer = new FileWriter();
    }

    public override void FileEncrypt(string fileNameSrc, string fileNameDes)
    
{
        string plainStr = _reader.Read(fileNameSrc);
        if (string.IsNullOrEmpty(plainStr))
        {
            return;
        }
        string encryptStr = _cipher.Encrypt(plainStr);
        _writer.Write(encryptStr, fileNameDes);
    }
}

配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="facade" value="SunnyFacade.NewEncryptFacade"/>
  </appSettings>
</configuration>

客戶端

using System.Configuration;
using System.Reflection;

class Program
{

    static void Main(string[] args)
    
{
        string facadeString = ConfigurationManager. AppSettings["facade"];
        AbstractEncryptFacade ef = Assembly.Load("SunnyFacade").CreateInstance(facadeString) 
            as AbstractEncryptFacade;

        if (ef != null)
            ef.FileEncrypt("src.txt""des.txt");
    }
}

輸出結(jié)果如下:

圖12 輸出結(jié)果


原有外觀類EncryptFacade也需作為抽象外觀類AbstractEncryptFacade類的子類,更換具體外觀類時(shí)只需修改配置文件,,無須修改源代碼,,符合 開閉原則,。


適配器模式 -- 不兼容結(jié)構(gòu)的協(xié)調(diào)

Sunny 軟件公司在很久以前曾開發(fā)了一個(gè)算法庫(kù),里面包含了一些常用的算法,,例如排序算法和查找算法,,在進(jìn)行各類軟件開發(fā)時(shí)經(jīng)常需要重用該算法庫(kù)中的算法。

在為某學(xué)校開發(fā)教務(wù)管理系統(tǒng)時(shí),,開發(fā)人員發(fā)現(xiàn)需要對(duì)學(xué)生成績(jī)進(jìn)行排序和查找,,該系統(tǒng)的設(shè)計(jì)人員已經(jīng)開發(fā)了一個(gè)成績(jī)操作接口IScoreOperation,在該接口中聲明了排序方法Sort(int[])和查找方法Search(int[], int),,為了提高排序和查找的效率,,開發(fā)人員決定重用算法庫(kù)中的快速排序算法類QuickSort和二分查找算法類BinarySearch,其中QuickSortQuickExchangeSort(int[])方法實(shí)現(xiàn)了快速排序,,BinarySearchBinSearch (int[], int)方法實(shí)現(xiàn)了二分查找,。

由于某些原因,現(xiàn)在 Sunny 公司開發(fā)人員已經(jīng)找不到該算法庫(kù)的源代碼,,無法直接通過復(fù)制和粘貼操作來重用其中的代碼,;部分開發(fā)人員已經(jīng)針對(duì)IScoreOperation接口編程,如果再要求對(duì)該接口進(jìn)行修改或要求大家直接使用QuickSort類和BinarySearch類將導(dǎo)致大量代碼需要修改,。

Sunny 軟件公司開發(fā)人員面對(duì)這個(gè)沒有源碼的算法庫(kù),,遇到一個(gè)幸福而又煩惱的問題:如何在既不修改現(xiàn)有接口又不需要任何算法庫(kù)代碼的基礎(chǔ)上能夠?qū)崿F(xiàn)算法庫(kù)的重用

圖13 需協(xié)調(diào)的兩個(gè)系統(tǒng)的結(jié)構(gòu)示意圖

現(xiàn)在我們需要IScoreOperation接口能夠和已有算法庫(kù)一起工作,,讓它們?cè)谕粋€(gè)系統(tǒng)中能夠兼容,,最好的實(shí)現(xiàn)方法是增加一個(gè)適配器角色,通過適配器來協(xié)調(diào)這兩個(gè)原本不兼容的結(jié)構(gòu),。

圖14 算法庫(kù)重用結(jié)構(gòu)圖

IScoreOperation接口充當(dāng)抽象目標(biāo),,QuickSortBinarySearch類充當(dāng)適配者,OperationAdapter充當(dāng)適配器,。

(1)抽象成績(jī)操作類:目標(biāo)接口

public interface IScoreOperation
{
    void Sort(int[] array);
    int Search(int[] arrayint key);
}

(2)快速排序類:適配者

public class QuickSort
{

    public void QuickExchangeSort<T>(T[] array) where T : IComparable<T>
    {
        QuickExchangeSort(array0array.Length - 1);
    }

    private void QuickExchangeSort<T>(T[] arrayint left, int right) where T : IComparable<T>
    {
        if (left < right)
        {
            T current = array[left];
            int i = left;
            int j = right;
            while (i < j)
            {
                while (array[j].CompareTo(current) > 0 && i < j)
                    j--;
                while (array[i].CompareTo(current) <= 0  && i < j)
                    i++;
                if (i < j)
                {
                    T temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                    j--;
                    i++;
                }
            }
            array[left] = array[j];
            array[j] = current;
            
            if (left < j - 1
                QuickExchangeSort(array, left, j - 1);
            if (right > j + 1
                QuickExchangeSort(array, j + 1, right);
        }
    }
}

(3)二分查找類:適配者

public class BinarySearch
{

    public int BinSearch<T>(T[] array, T key)
        where T : IComparable<T>
    {
        int left = 0;
        int right = array.Length - 1;
        while (left <= right)
        {
            int mid = (left + right)/2;
            
            if (array[mid].CompareTo(key) < 0)
                left = mid + 1;
            else if (array[mid].CompareTo(key) == 0)
                return mid;
            else
                right = mid - 1;
        }
        return -1;
    }
}

(4)操作適配器:適配器

public class OperationAdapter : IScoreOperation
{

    private readonly QuickSort _sortObj;
    private readonly BinarySearch _searchObj;

    public OperationAdapter()
    
{
        _sortObj = new QuickSort();
        _searchObj = new BinarySearch();
    }

    public void Sort(int[] array)
    
{
        _sortObj.QuickExchangeSort<int>(array);
    }

    public int Search(int[] arrayint key)
    
{
        return _searchObj.BinSearch<int>(array, key);
    }
}

(5)配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
   <add key="Adapter" value="SunnyAdapter.OperationAdapter"/>
  </appSettings>
</configuration>

(6)客戶端代碼

using System.Configuration;
using System.Reflection;

class Program
{

    static void Main(string[] args)
    
{
        Assembly assembly = Assembly.Load("SunnyAdapter");
        IScoreOperation scoureOperation = assembly.CreateInstance(ConfigurationManager.AppSettings["Adapter"])
                as IScoreOperation;
                
        if (scoureOperation == null)
            return;

        int[] scores = { 8476506990918896 };
        
        Console.WriteLine("成績(jī)排序結(jié)果:");
        scoureOperation.Sort(scores);        
        for (int i = 0; i < scores.Length; i++)
        {
            Console.Write(scores[i]+",");
        }
        Console.WriteLine();
        
        Console.WriteLine("查找成績(jī)90:");
        int score = scoureOperation.Search(scores, 90);
        if (score == -1)
            Console.WriteLine("沒有找到成績(jī)90,。");
        else
            Console.WriteLine("找到成績(jī)90。");

        Console.WriteLine("查找成績(jī)92:");
        score = scoureOperation.Search(scores, 92);
        if (score == -1)
            Console.WriteLine("沒有找到成績(jī)92,。");
        else
            Console.WriteLine("找到成績(jī)92。");
    }
}

圖15 運(yùn)行結(jié)果

如果需要使用其它排序算法類和查找算法類,,可以增加一個(gè)新的適配器類,,使用新的適配器來適配新的算法,原有代碼無須修改,。

通過引入配置文件和反射機(jī)制,,可以在不修改客戶端代碼的情況下使用新的適配器,無須修改源代碼,,符合“開閉原則”,。

適配器模式Adpter Pattern) 將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper),。

適配器模式 中引入了一個(gè)被稱為適配器(Adapter)的包裝類,,而它所包裝的對(duì)象稱為適配者(Adaptee),即被適配的類,。適配器的實(shí)現(xiàn)就是把客戶類的請(qǐng)求轉(zhuǎn)化為對(duì)適配者的相應(yīng)接口的調(diào)用,。從而解決了接口不兼容的問題,使得原本沒有任何關(guān)系的類可以協(xié)同工作,。

根據(jù)適配器類與適配者類的關(guān)系不同,,適配器模式可分為:

  • 對(duì)象適配器(適配器與適配者之間是關(guān)聯(lián)關(guān)系)
  • 類適配器(適配器與適配者之間是繼承或?qū)崿F(xiàn)關(guān)系)

圖16 對(duì)象適配器模式結(jié)構(gòu)圖

(1)目標(biāo)接口

public interface ITarget
{
    void Request();
}

(2)適配者類

public class Adaptee
{

    public void SpecificRequest()
    
{
        ;
    }
}

(3)適配器類

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    
{
        _adaptee = adaptee;
    }
    
    public void Request()
    
{
        _adaptee.SpecificRequest();
    }
}

在對(duì)象適配器中,客戶端需要調(diào)用Request()方法,,而適配者類Adaptee沒有該方法,,但是它所提供的SpecificRequest()方法卻是客戶端所需要的。為了使客戶端能夠使用適配者類,,需要提供一個(gè)包裝類Adapter,,即適配器類。這個(gè)包裝類包裝了一個(gè)適配者的實(shí)例,,從而將客戶端與適配者銜接起來,,在適配器的Request()方法中調(diào)用適配者的SpecificRequest()方法。

圖17 類適配器模式結(jié)構(gòu)圖

public class Adapter : Adaptee, ITarget
{
    public void Request()
    
{
        base.SpecificRequest();
    }
}

適配器模式包含以下 3 個(gè)角色:

  • ITarget(目標(biāo)接口):定義客戶所需接口,。
  • Adaptee(適配者類):即被適配的角色,,它定義了一個(gè)已經(jīng)存在的接口,這個(gè)接口需要適配,,適配者類一般是一個(gè)具體類,,包含了客戶希望使用的業(yè)務(wù)方法,在某些情況下可能沒有適配者類的源碼,。
  • Adapter(適配器類):適配器可以調(diào)用另一個(gè)接口,,作為一個(gè)轉(zhuǎn)換器,對(duì)AdapteeITarget進(jìn)行適配,,它通過實(shí)現(xiàn)ITarget并 關(guān)聯(lián)/繼承 一個(gè)Adaptee 對(duì)象/類 使二者產(chǎn)生聯(lián)系,。

由于Java、C#等語(yǔ)言不支持多重類繼承,,因此類適配器的使用受到很多限制,,例如如果目標(biāo)ITarget不是接口,,而是一個(gè)類,,就無法使用類適配器,;此外,如果適配者Adapteesealed類,,也無法使用類適配器,。在C#等面向?qū)ο缶幊陶Z(yǔ)言中,,大部分情況下我們使用的是對(duì)象適配器,類適配器較少使用,。


橋接模式 -- 處理多維度變化

Sunny 軟件公司欲開發(fā)一個(gè)跨平臺(tái)圖像瀏覽系統(tǒng),,要求該系統(tǒng)能夠顯示 BMPJPG,、GIF,、PNG 等多種格式的文件,并且能夠在 Windows,、Linux,、Unix 等多個(gè)操作系統(tǒng)上運(yùn)行。

系統(tǒng)首先將各種格式的文件解析為像素矩陣(Matrix),,然后將像素矩陣顯示在屏幕上,,在不同的操作系統(tǒng)中可以調(diào)用不同的繪制函數(shù)來繪制像素矩陣。

系統(tǒng)需具有較好的擴(kuò)展性以支持新的文件格式和操作系統(tǒng),。

Sunny 軟件公司的開發(fā)人員針對(duì)上述要求,,提出了一個(gè)初始設(shè)計(jì)方案,其基本結(jié)構(gòu)如下圖所示:

圖18 跨平臺(tái)圖像瀏覽器初始結(jié)構(gòu)圖

在上圖的初始設(shè)計(jì)方案中,,使用了一種多層繼承結(jié)構(gòu),。

  • Image是抽象父類,而每一種類型的圖像類作為其直接子類,,不同的圖像文件格式具有不同的解析方法,,可以得到不同的像素矩陣;
  • 由于每一種圖像又需要在不同的操作系統(tǒng)中顯示,,不同的操作系統(tǒng)在屏幕上顯示像素矩陣有所差異,,因此需要為不同的圖像類再提供一組在不同操作系統(tǒng)顯示的子類。

對(duì)該設(shè)計(jì)方案進(jìn)行分析,,發(fā)現(xiàn)存在如下兩個(gè)主要問題:

【1】由于采用了多層繼承結(jié)構(gòu),,導(dǎo)致系統(tǒng)中類的個(gè)數(shù)急劇增加,在各種圖像的操作系統(tǒng)實(shí)現(xiàn)層提供了12個(gè)實(shí)體類,,加上各級(jí)抽象層的類,,系統(tǒng)中類的總個(gè)數(shù)達(dá)到了17個(gè),在該設(shè)計(jì)方案中,,實(shí)體層的類的個(gè)數(shù) = 所支持的圖像文件格式數(shù) × 所支持的操作系統(tǒng)數(shù),。

【2】系統(tǒng)擴(kuò)展麻煩,由于每一個(gè)實(shí)體類既包含圖像文件格式信息,,又包含操作系統(tǒng)信息,因此無論是增加新的圖像文件格式還是增加新的操作系統(tǒng),,都需要增加大量的實(shí)體類,。

如何解決這兩個(gè)問題,?

談?wù)剝煞N常見文具 “毛筆” 和 “蠟筆” 的區(qū)別。

假如我們需要大中小 3 種型號(hào)的畫筆,,能夠繪制 12 種不同的顏色,。如果使用蠟筆,需要準(zhǔn)備 36支,,如果使用毛筆,,只需要提供 3 種型號(hào)的毛筆,外加 12 個(gè)顏料盒即可,,涉及到的對(duì)象個(gè)數(shù)為 15,,遠(yuǎn)小于36,卻能實(shí)現(xiàn)與36支蠟筆同樣的功能,。如果增加一種新型號(hào)的畫筆,,并且也需要具有 12 種顏色,對(duì)應(yīng)的蠟筆需增加 12支,,而毛筆只需增加 1支,。

為什么會(huì)這樣呢?

通過分析可知:

  • 蠟筆:顏色和型號(hào)兩個(gè)不同的變化維度(即兩個(gè)不同的變化原因)融合在一起,,無論是對(duì)顏色還是對(duì)型號(hào)進(jìn)行擴(kuò)展都勢(shì)必會(huì)影響另一個(gè)維度,;
  • 毛筆:顏色和型號(hào)實(shí)現(xiàn)了分離,增加新的顏色或者型號(hào)對(duì)另一方都沒有任何影響,。

如果使用軟件工程中的術(shù)語(yǔ),,我們可以認(rèn)為在蠟筆中顏色和型號(hào)之間存在較強(qiáng)的耦合性,而毛筆很好地將二者解耦,,使用起來非常靈活,,擴(kuò)展也更為方便。

我們通過分析得知,,該系統(tǒng)也存在兩個(gè)獨(dú)立變化的維度:

  • 圖像文件格式(對(duì)應(yīng)圖像格式的解析)
  • 操作系統(tǒng)(對(duì)應(yīng)像素矩陣的顯示)

圖19 跨平臺(tái)圖像瀏覽器中存在的兩個(gè)獨(dú)立變化維度示意圖

為了減少所需生成的子類數(shù)目,,將這兩個(gè)維度分離,使得它們可以獨(dú)立變化,,增加新的圖像文件格式或者操作系統(tǒng)時(shí)都對(duì)另一個(gè)維度不造成任何影響,。

Sunny 公司開發(fā)人員重構(gòu)了系統(tǒng)的設(shè)計(jì),如下圖所示:

圖20 跨平臺(tái)圖像瀏覽器重構(gòu)后的結(jié)構(gòu)圖

完整代碼如下:

//各種格式的文件最終都被轉(zhuǎn)化為像素矩陣,,
//不同的操作系統(tǒng)提供不同的方式顯示像素矩陣,。
public class Matrix
{

    //...
}

“實(shí)現(xiàn)類”層次結(jié)構(gòu):

public abstract class ImageImp
{

    public abstract void DoPaint(Matrix m);  
}

public class LinuxImp : ImageImp
{
    public override void DoPaint(Matrix m)
    
{
        Console.WriteLine("在Linux操作系統(tǒng)中顯示圖像。");
    }
}

public class UnixImp : ImageImp
{
    public override void DoPaint(Matrix m)
    
{
        Console.WriteLine("在Unix操作系統(tǒng)中顯示圖像,。");
    }
}

public class WindowsImp : ImageImp
{
    public override void DoPaint(Matrix m)
    
{
        Console.WriteLine("在Windows操作系統(tǒng)中顯示圖像,。");
    }
}

“抽象類”層次結(jié)構(gòu):

public abstract class Image
{

    protected ImageImp Imp;

    public void SetImageImp(ImageImp imp)
    
{
        this.Imp = imp;
    }

    public abstract void ParseFile(string fileName);
}

public class BmpImage : Image
{
    public override void ParseFile(string fileName)
    
{
        Matrix m = new Matrix();
        Imp.DoPaint(m);
        Console.WriteLine(fileName + ",格式為BMP,。");
    }
}

public class GifImage : Image
{
    public override void ParseFile(string fileName)
    
{
        Matrix m = new Matrix();
        Imp.DoPaint(m);
        Console.WriteLine(fileName + ",,格式為GIF,。");
    }
}

public class JpgImage:Image
{
    public override void ParseFile(string fileName)
    
{
        Matrix m = new Matrix();
        Imp.DoPaint(m);
        Console.WriteLine(fileName+ ",格式為JPG,。");
    }
}

public class PngImage : Image
{
    public override void ParseFile(string fileName)
    
{
        Matrix m = new Matrix();
        Imp.DoPaint(m);
        Console.WriteLine(fileName + ",,格式為PNG。");
    }
}

配置文件代碼:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="image" value="SunnyBridge.BmpImage"/>
    <add key="os" value="SunnyBridge.LinuxImp"/>
  </appSettings>
</configuration>

客戶端代碼:

using System.Reflection;
using System.Configuration;
class Program
{

  static void Main(string[] args)
  
{
    string image = ConfigurationManager.AppSettings["image"];
    string os =  ConfigurationManager.AppSettings["os"];
    
    Assembly assembly = Assembly.Load("SunnyBridge");
    Image img = assembly.CreateInstance(image) as Image;
    ImageImp imageImp = assembly.CreateInstance(os) as ImageImp;

    if (img != null)
    {
        img.SetImageImp(imageImp);
        img.ParseFile("光頭強(qiáng)");
    }
  }
}

輸出結(jié)果如下圖所示:

圖21 運(yùn)行結(jié)果

如果需要更換圖像文件格式或者更換操作系統(tǒng),,只需修改配置文件即可,,在實(shí)際使用時(shí),可以通過分析圖像文件格式后綴名來確定具體的文件格式,,在程序運(yùn)行時(shí)獲取操作系統(tǒng)信息來確定操作系統(tǒng)類型,,無須使用配置文件

當(dāng)增加新的圖像文件格式或者操作系統(tǒng)時(shí),,原有系統(tǒng)無需做任何修改,,只需增加一個(gè)對(duì)應(yīng)的實(shí)現(xiàn)類即可,系統(tǒng)具有較好的可擴(kuò)展性,,完全符合“開閉原則”,。

橋接模式與多層繼承方案不同,它將兩個(gè)獨(dú)立變化的維度設(shè)計(jì)為兩個(gè)獨(dú)立的繼承等級(jí)結(jié)構(gòu),,并且在抽象層建立一個(gè)抽象關(guān)聯(lián),,該關(guān)聯(lián)關(guān)系類似一條連接兩個(gè)獨(dú)立繼承結(jié)構(gòu)的橋,故名橋接模式,。


橋接模式Bridge Pattern):將抽象部分與它的實(shí)現(xiàn)部分分離,,使它們都可以獨(dú)立地變化。

  • 抽象關(guān)聯(lián) 取代了傳統(tǒng)的 多繼承,;
  • 將類之間的 靜態(tài)繼承關(guān)系 轉(zhuǎn)換為 動(dòng)態(tài)的對(duì)象組合關(guān)系,;

橋接模式 結(jié)構(gòu)如下圖所示:

圖22 橋接模式類圖

  • Abstraction(抽象類):定義了一個(gè)Implementor類型的對(duì)象并可以維護(hù)該對(duì)象,它與Implementor之間具有關(guān)聯(lián)關(guān)系,,它既可以包含抽象業(yè)務(wù)方法,,也可以包含具體業(yè)務(wù)方法。
  • RefinedAbstraction(擴(kuò)充抽象類):實(shí)現(xiàn)了在Abstraction中聲明的抽象業(yè)務(wù)方法,,在RefinedAbstraction中可以調(diào)用在Implementor中定義的業(yè)務(wù)方法,。
  • Implementor(實(shí)現(xiàn)類接口):通過關(guān)聯(lián)關(guān)系,在Abstraction中不僅擁有自己的方法,,還可以調(diào)用到Implementor中定義的方法,,使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系。
  • ConcreteImplementor(實(shí)體實(shí)現(xiàn)類):在程序運(yùn)行時(shí),,ConcreteImplementor對(duì)象將替換其父類對(duì)象,,提供給抽象類實(shí)體的業(yè)務(wù)操作方法。

通常情況下,我們將具有兩個(gè)獨(dú)立變化維度的類的一些普通業(yè)務(wù)方法和與之關(guān)系最密切的維度設(shè)計(jì)為“抽象類”層次結(jié)構(gòu)(抽象部分),,而將另一個(gè)維度設(shè)計(jì)為“實(shí)現(xiàn)類”層次結(jié)構(gòu)(實(shí)現(xiàn)部分),。

對(duì)于毛筆而言:

  • 型號(hào)是其固有的維度,因此可以設(shè)計(jì)一個(gè)抽象的毛筆類,,在該類中聲明并部分實(shí)現(xiàn)毛筆的業(yè)務(wù)方法,而將各種型號(hào)的毛筆作為其子類,;
  • 顏色是其另一個(gè)維度,,由于它與毛筆之間存在一種“設(shè)置”的關(guān)系,因此我們可以提供一個(gè)抽象的顏色接口,,而將具體的顏色作為實(shí)現(xiàn)該接口的子類,。

結(jié)構(gòu)示意圖如下圖所示:


圖23 橋接模式類圖


橋接模式 中體現(xiàn)了“單一職責(zé)原則”、“開閉原則”,、“合成復(fù)用原則”,、“里氏代換原則”、“依賴倒轉(zhuǎn)原則”等設(shè)計(jì)原則,。熟悉該模式有助于我們深入理解這些設(shè)計(jì)原則,,也有助于我們形成正確的設(shè)計(jì)思想和培養(yǎng)良好的設(shè)計(jì)風(fēng)格。

適配器模式與橋接模式的聯(lián)用

  • 橋接模式:用于系統(tǒng)的初步設(shè)計(jì),,對(duì)于存在兩個(gè)獨(dú)立變化維度的類可以將其分為抽象化實(shí)現(xiàn)化兩個(gè)角色,,使它們可以分別進(jìn)行變化;
  • 適配器模式:初步設(shè)計(jì)完成之后,,當(dāng)發(fā)現(xiàn)系統(tǒng)與已有類無法協(xié)同工作時(shí),,可以采用適配器模式,解決兩個(gè)已有接口間不兼容問題,;

某系統(tǒng)的報(bào)表處理模塊中,,需要將報(bào)表顯示和數(shù)據(jù)采集分開,系統(tǒng)可以有多種報(bào)表顯示方式也可以有多種數(shù)據(jù)采集方式,,如可以從文本文件中讀取數(shù)據(jù),,也可以從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù),還可以從Excel文件中獲取數(shù)據(jù),。如果需要從Excel文件中獲取數(shù)據(jù),,則需要調(diào)用與Excel相關(guān)的API,而這個(gè)API是現(xiàn)有系統(tǒng)所不具備的,,該API由廠商提供,。

在設(shè)計(jì)過程中,由于存在報(bào)表顯示和數(shù)據(jù)采集兩個(gè)獨(dú)立變化的維度,,因此可以使用 橋接模式 進(jìn)行初步設(shè)計(jì),;為了使用Excel相關(guān)的API來進(jìn)行數(shù)據(jù)采集則需要使用適配器模式。系統(tǒng)的完整設(shè)計(jì)中需要將兩個(gè)模式聯(lián)用,如下圖所示:

圖24 橋接模式與適配器模式聯(lián)用示意圖


    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多