設(shè)計模式學(xué)習(xí)筆記1 - 創(chuàng)建型模式

入鄉(xiāng)隨俗的配圖

前段時間,在自己糊里糊涂地寫了一年多的代碼之后,接手了一坨一個同事的代碼。身邊很多人包括我自己都在痛罵那些亂糟糟毫無設(shè)計可言的代碼,我不禁開始深思:自己真的比他高明很多嗎?我可以很自信地承認(rèn),在代碼風(fēng)格和單元測試上可以完勝,可是設(shè)計模式呢?自己平時開始一個project的時候有認(rèn)真考慮過設(shè)計模式嗎?答案是并沒有,我甚至都數(shù)不出有哪些設(shè)計模式。于是,我就拿起了這本設(shè)計模式黑皮書。

中文版《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》,譯自英文版《Design Patterns: Elements of Reusable Object-Oriented Software》。原書由Erich Gamma, Richard Helm, Ralph Johnson 和John Vlissides合著。這幾位作者常被稱為“Gang of Four”,即GoF。該書列舉了23種主要的設(shè)計模式,因此,其他地方經(jīng)常提到的23種GoF設(shè)計模式,就是指本書中提到的這23種設(shè)計模式。

把書看完很容易,但是要理解透徹,融匯貫通很難,能夠在實際中靈活地選擇合適的設(shè)計模式運(yùn)用起來就更是難上加難了。所以,我打算按照本書的組織結(jié)構(gòu)(把23種設(shè)計模式分成三大類)寫三篇讀書筆記,一來自我總結(jié),二來備忘供以后自己翻閱。與此同時,如果能讓讀者有一定的收獲就更棒了。我覺得本書的前言有句話很對,“第一次閱讀此書時你可能不會完全理解它,但不必著急,我們在起初編寫這本書時也沒有完全理解它們!請記住,這不是一本讀完一遍就可以束之高閣的書。我們希望你在軟件設(shè)計過程中反復(fù)參閱此書,以獲取設(shè)計靈感”。


本節(jié)介紹創(chuàng)建型模式,它包括抽象工廠模式、生成器模式、工廠方法模式、原型模式和單例模式。創(chuàng)建型模式抽象了實例化過程,它們幫助一個系統(tǒng)獨(dú)立于如何創(chuàng)建、組合和表示它的那些對象。創(chuàng)建型模式在“什么被創(chuàng)建”、“誰創(chuàng)建它”、“它是怎樣被創(chuàng)建的”和“何時創(chuàng)建”這些方面有很大的靈活性。

1. 工廠模式(Simple Factory, Abstract Factory, Factory Method)

其實并沒有一個設(shè)計模式叫工廠模式,這里我把三種與工廠相關(guān)的設(shè)計模式放在一起來講,所以小標(biāo)題就叫工廠模式了。之所以把它們放在一起,是因為它們有一個共性,就是工廠,都是用來封裝對象的創(chuàng)建的。

(1)簡單工廠模式

簡單工廠模式并不屬于GoF的23種設(shè)計模式,由于它過于直接,甚至有的地方認(rèn)為它不是一種真正的設(shè)計模式,而是一種編程習(xí)慣。
例如,我們要生產(chǎn)一種產(chǎn)品Product,具體有ProductAProductB。簡單工廠就是將“根據(jù)用戶指定的類型去生成對應(yīng)的產(chǎn)品對象的過程”封裝起來。一般使用靜態(tài)方法,這樣就不需要創(chuàng)建一個具體的對象去調(diào)用生產(chǎn)的工廠方法。
下面以Head First所采用的pizza的例子來說明幾種不同的工廠模式。我們首先需要新建Pizza基類,并在其基礎(chǔ)上生成繼承的相關(guān)的PizzaAPizzaB子類,這里就不提供它們的代碼了。而簡單工廠模式就是,(如下代碼所示)把根據(jù)用戶指定的類型來看是創(chuàng)建PizzaA還是PizzaB子類的過程,封裝在SimplePizzaFactory類中。

 public class SimplePizzaFactory
{
    public Pizza CreatePizza(string type)
    {
        Pizza pizza = null;
        if(type == "A")
        {
            pizza = new PizzaA();
        } else if(type == "B"){
            pizza = new PizzaB();
        } else {
            throw new ArgumentException($"{type} is not support");
        }
        return pizza;
    }
}

public class PizzaStore
{
    SimplePizzaFactory factory;
    public PizzaStore(SimplePizzaFactory factory)
    {
        this.factory = factory;
    }
    
    public Pizza OrderPizza(string type)
    {
        Pizza pizza = this.factory.CreatePizza(type);
        
        pizza.Prepare();
        pizza.Bake();
        pizza.Cut();
        pizza.Box();
        return pizza;
    }
}
(2)工廠方法模式

工廠方法模式,通過讓子類來決定該創(chuàng)建的對象是什么,來達(dá)到將對象創(chuàng)建的過程封裝的目的。工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類。
工廠方法的結(jié)構(gòu)如下圖所示:

工廠方法模式

上面的簡單工廠方法,通過用戶輸入的type來判斷該生成什么樣的產(chǎn)品。如果有多個PizzaStore,而它們對應(yīng)的工廠和所需的產(chǎn)品可能不太一樣,要同時支持不同Store的需求,我們可以把實例化Pizza的過程推遲到具體的工廠子類去。
例如,現(xiàn)在有PizzaStore1PizzaStore2兩個商店,它們對用戶輸入的用戶類型A和B時可能會創(chuàng)建出適合這兩個商店自身的兩種產(chǎn)品。而如果生成這兩種產(chǎn)品由這兩個商店自己決定。我們新建一個抽象類PizzaStore,并為它定義了一個抽象的工廠方法CreatePizza,這樣任何store在定義的時候都會實現(xiàn)這個抽象工廠。

 public abstract class PizzaStore
{   
    public Pizza OrderPizza(string type)
    {
        Pizza pizza = CreatePizza(type);
        
        pizza.Prepare();
        pizza.Bake();
        pizza.Cut();
        pizza.Box();
        return pizza;
    }
    
    protected abstract Pizza CreatePizza(string type);
}
(3)抽象工廠模式

抽象工廠模式,提供一個接口,用于創(chuàng)建相關(guān)或者依賴的對象家族,而不需要指明具體的類。其結(jié)構(gòu)如下圖所示:

抽象工廠模式

上一節(jié)中的共產(chǎn)方法模式讓不同的store可以用不同的方式實現(xiàn)不同的工廠方法,這樣會過于靈活。如果我們想對不同的工廠進(jìn)行一定的控制,則上述工廠方法則很難做到。所以我們可以新建一個抽象工廠模式,讓工廠方法基于這個抽象工廠模式進(jìn)行生產(chǎn),則可以實現(xiàn)統(tǒng)一的控制。抽象工廠模式一般用于用于創(chuàng)建相關(guān)或相互依賴的對象家族,例如生產(chǎn)的產(chǎn)品一定要由a, b, c三個部分組成,不同的工廠可以選擇這三個部分的不同子類,但是一定要按照這種模式進(jìn)行組合。這個時候就是抽象工廠模式就上場了。
例如,我們要生產(chǎn)的產(chǎn)品X需要由Xa, Xb, Xc組成,而產(chǎn)品Y則需要由Ya, Yb, Yc組成,所以我們可以定義一個抽象的接口,即生產(chǎn)產(chǎn)品的時候,先生產(chǎn)a, b, c,再把它們組裝起來。然后再分別用工廠X和工廠Y去實現(xiàn)這個抽象的接口來生產(chǎn)產(chǎn)品X和產(chǎn)品Y。

// abstract factory
public interface IngredientFactory
{   
    public PartA CreatePartA();
    public PartB CreatePartB();
    public PartC CreatePartC();
}

// implement factory X to produce product X
public class ProductXFactory : IngredientFactory
{
    public PartA CreatePartA()
    {
        return new PartXA();
    }
    
    public PartB CreatePartB()
    {
        return new PartXB();
    }
    
    public PartC CreatePartC()
    {
        return new PartXC();
    }
}

// implement factory Y to produce product Y
public class ProductYFactory : IngredientFactory
{
    public PartA CreatePartA()
    {
        return new PartYA();
    }
    
    public PartB CreatePartB()
    {
        return new PartYB();
    }
    
    public PartC CreatePartC()
    {
        return new PartYC();
    }
}

//
public class Product
{
    IngredientFactory factory;
    public Product(IngredientFactory factory)
    {
        this.factory = factory;
    }
    
    void Produce()
    {
        var a = this.factory.CreatePartA();
        var b = this.factory.CreatePartB();
        var c = this.factory.CreatePartC();
        // todo: a+b+c and return the intact product
    }
}

下面簡單小結(jié)一下與工廠相關(guān)的設(shè)計模式。
簡單工廠模式很直觀,就是把生產(chǎn)產(chǎn)品對象的部分封裝起來。下面主要談?wù)劰S方法和抽象工廠之間的區(qū)別。實際上抽象工廠模式中也有工廠方法的影子,例如上述例子中我們定義了抽象工廠IngredientFactory,而在它內(nèi)部又定義了三個方法,這些方法就是工廠方法。如果你此時覺得“咦,有道理耶”,那么恭喜你,你應(yīng)該理解了這兩種模式的本質(zhì)。如果你覺得更困惑了,沒關(guān)系,下面我們再來細(xì)看一下他們的區(qū)別吧。

(a)工廠方法和抽象工廠的本質(zhì)都是負(fù)責(zé)創(chuàng)建對象,但是工廠方法使用的是類,而抽象工廠通過的是對象。我們回到上面創(chuàng)建不同PizzaStore的例子,為了讓不同的store可以生產(chǎn)不同的產(chǎn)品,工廠方法是在抽象的PizzaStore類中定義了一個CreatePizza()的抽象方法,這樣不同的store會有自己對應(yīng)的類,而這個類繼承自PizzaStore,然后在該類中需要實現(xiàn)它自己的CreatePizza()方法。而同樣為了解決這個問題,抽象工廠所用的方法則是在PizzaStore中添加了一個抽象工廠類的對象,這樣創(chuàng)建不同的store只需要在構(gòu)造PizzaStore類的時候傳入不同的抽象工廠類的對象就可以了。
(b)工廠方法被用來創(chuàng)建一個產(chǎn)品,而抽象工廠則被用來創(chuàng)建整個產(chǎn)品家族。所以抽象工廠需要一個很大的接口,一旦需要新增產(chǎn)品則需要修改接口。
(c)應(yīng)用場景不同。當(dāng)你要將客戶代碼從需要實例化的具體類中解耦時,或者你目前還不知道將來需要實例化哪些具體的類時,可以使用工廠方法。而當(dāng)你需要創(chuàng)建產(chǎn)品家族或者想讓制造的相關(guān)產(chǎn)品集合起來的時候,可以使用抽象工廠。

2. 單例模式(Singleton)

單例模式可能大家都比較熟悉,就是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點(diǎn)。單例模式看起來非常簡單(結(jié)構(gòu)圖如下所示),但是實現(xiàn)起來還是有很多注意事項需要考慮。

單例模式

首先簡單說說單例模式的優(yōu)點(diǎn)。知己知彼,才能更好地利用它。很多人包括我自己認(rèn)為它的作用就是保證唯一的一個實例,實際上它還有一些其他優(yōu)點(diǎn)。1)對唯一實例的受控訪問;2)縮小名空間,它是對全局變量的一種改進(jìn),從而避免了那些存儲唯一實例的全局變量污染名空間;3)允許對操作和表示的精化,Singleton類可以有子類,而且用這個擴(kuò)展類的實例來配置一個應(yīng)用是很容易的,你可以用你所需要的類的實例在運(yùn)行時刻配置應(yīng)用。4)允許可變數(shù)目的實例,Singleton模式是允許你修改為支持多個實例的,可以根據(jù)相同的方法來控制實例的數(shù)目。5)比類操作更靈活,除了單例模式,另一種封裝單件功能的方式是使用類操作(如C++中的靜態(tài)成員函數(shù)),但是單例模式控制實例的數(shù)目比類操作更靈活。

單例模式的具體實現(xiàn):
(1)保證一個唯一的實例。如何讓一個類智能被實例化一次呢?首先,我們把這個類的構(gòu)造函數(shù)聲明為私有的,這樣就不能在外部用new關(guān)鍵字創(chuàng)建該類的實例的。然后,為了創(chuàng)建出這個類的實例,我們可以在該類的內(nèi)部定義一個GetInstance()的方法,這樣在GetInstance()方法內(nèi)部就可以訪問到該類的私有構(gòu)造函數(shù)了。最后,為了保證GetInstance()方法返回的實例只能是唯一的一個,我們需要再在類中定義個靜態(tài)的類對象成員,當(dāng)?shù)谝淮握{(diào)用GetInstance()時為創(chuàng)建一個實例,之后的調(diào)用就直接返回該實例即可,這樣就能保證唯一性了。
(2)如何處理多線程的情況。上面我們提到在第一次調(diào)用GetInstance()的時候創(chuàng)建實例,但是在多線程的情況下,這個第一次是很難判斷和保證的。下面有幾個方法來處理這個問題:
(a)將GetInstance()方法設(shè)置為同步方法,這樣多線程調(diào)用該方法的時候就會排隊了,所以就不會出現(xiàn)并發(fā)訪問的問題了。但是變成同步的方法可能會使得性能下降100倍,如果調(diào)用該方法不是很頻繁,這樣做還是無可厚非的。
(b)不采用延遲實例化的方法。我們直接對靜態(tài)的數(shù)據(jù)成員進(jìn)行初始化
private static Singleton uniqueInstance = new Singleton();
(c)采用雙重加鎖(double-checked locking)來減少使用同步。

public class Singleton
{
    private static volatile Singleton uniqueInstance;
    
    private static object syncRoot = new Object();
    
    private Singleton() {}
    
    public static Singleton Instance
    {
        get
        {
            if(uniqueInstance == null)
            {
                lock(syncRoot)
                {
                    if(uniqueInstance == null)
                    {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }
}

3. 原型模式(Prototype)

原型模式,是指用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這類原型創(chuàng)建新的對象。有時候我們要創(chuàng)建一系列實例,它們之間大部分成員都是相同的,所以我們希望能夠有一個類似于復(fù)制粘貼的功能來實現(xiàn)對象的拷貝,原型模式就是做這種事情的。在使用原型模式時,我們首先創(chuàng)建一個原型對象,然后再通過復(fù)制該對象創(chuàng)建出更多同類型的對象。

下面是它的結(jié)構(gòu)圖:


原型模式

適用情況:
(1)當(dāng)一個系統(tǒng)應(yīng)該獨(dú)立于它的產(chǎn)品創(chuàng)建、構(gòu)成和表示時,
(2)當(dāng)要實例化的類是在運(yùn)行時刻指定的,例如,通過動態(tài)加載,
(3)為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時,
(4)當(dāng)一個類的實例只能有幾個不同狀態(tài)組合中的一種時,建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手動實例化該類更方便一些。

原型模式的實現(xiàn)非常簡單,就是為原型對象提供一個Clone()方法。但是需要注意的是對于原型對象中的引用,我們需要非常謹(jǐn)慎地實現(xiàn)其拷貝(深拷貝還是淺拷貝)。

與抽象工廠一樣,原型模式對客戶隱藏了具體的產(chǎn)品類,因此減少了客戶需要知道的名字的數(shù)目,此外它的主要優(yōu)點(diǎn)如下:
(1)運(yùn)行時刻增加和刪除產(chǎn)品。
(2)改變值以指定新對象。由于在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進(jìn)行編程,而將具體原型類寫在配置文件中,增加或減少產(chǎn)品類對原有系統(tǒng)都沒有任何影響。
(3)改變結(jié)構(gòu)以指定新對象。原型模式提供了簡化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個與產(chǎn)品類等級結(jié)構(gòu)相同的工廠等級結(jié)構(gòu),而原型模式就不需要這樣,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實現(xiàn)的,無須專門的工廠類來創(chuàng)建產(chǎn)品。
(4)減少子類的構(gòu)造。
(5)用類動態(tài)配置應(yīng)用。
(6)輔助實現(xiàn)撤銷操作??梢允褂蒙羁寺〉姆绞奖4鎸ο蟮臓顟B(tài),使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來,以便在需要的時候使用,如恢復(fù)到某一歷史狀態(tài)。

下面是一個簡單的例子。我們需要創(chuàng)建一個迷宮Maze,它由Wall、Room和Door組成,我們可能會寫一個抽象工廠來組裝這些部分,而在工廠中創(chuàng)建一個迷宮的時候需要new很多的Wall和Room等實例。如果創(chuàng)建這些部件需要很復(fù)雜的操作則會使得創(chuàng)建一個Maze非常繁瑣,所以我們可以使用Prototype模式為Maze和這些部件提供克隆函數(shù),使得新建它們的實例的時候不再是new一個對象,而是克隆現(xiàn)有的prototype對象。

public class MazeFactory
{
    public MazeFactory();
    
    public virtual Maze MakeMaze()
    {
        return new Maze;
    }
    public virtual Wall MakeWall()
    {
        return new Wall;
    }
    public virtual Room MakeRoom()
    {
        return new Room;
    }
    public virtual Door MakeDoor()
    {
        return new Door;
    }
}

public class MazePrototypeFactory : MazeFactory
{
    public MazePrototypeFactory(Maze m, Wall w, Room r, Door d);
    
    public virtual Maze MakeMaze()
    {
        return this.prototypeMaze.Clone();
    }
    public virtual Wall MakeWall()
    {
        return this.prototypeWall.Clone();
    }
    public virtual Room MakeRoom()
    {
        return this.prototypeRoom.Clone();
    }
    public virtual Door MakeDoor()
    {
        return this.prototypeDoor.Clone();
    }
    
    private Maze prototypeMaze;
    private Wall prototypeWall;
    private Room prototypeRoom;
    private Door prototypeDoor;
}

4. 生成器模式(Builder)

生成器模式的意圖,是將一個復(fù)雜對象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。它一般用來創(chuàng)建包含多個組成部分的較復(fù)雜對象,它將客戶端與復(fù)雜對象的創(chuàng)建過程分離,使得客戶端只需知道所需建造者的類型。它關(guān)注如何一步一步創(chuàng)建一個的復(fù)雜對象,不同的具體建造者定義了不同的創(chuàng)建過程,且具體建造者相互獨(dú)立,增加新的建造者非常方便,無須修改已有代碼,系統(tǒng)具有較好的擴(kuò)展性。

生成器模式的結(jié)構(gòu):


生成器模式

適用情況:
(1)當(dāng)創(chuàng)建復(fù)雜對象的算法應(yīng)該獨(dú)立于該對象的組成部分以及它們的裝配方式時。
(2)當(dāng)構(gòu)造過程必須允許被構(gòu)造的對象有不同的表示時。

生成器和抽象工廠都可以用來創(chuàng)建復(fù)雜對象,它們之間的主要區(qū)別是,前者著重于一步步構(gòu)造一個復(fù)雜對象,而后者著重于多個系列的產(chǎn)品對象。
生成器的主要優(yōu)點(diǎn)有:
(1)它使你可以改變一個產(chǎn)品的內(nèi)部表示。Builder對象提供給Director一個構(gòu)造產(chǎn)品的抽象接口,該接口使得生成器可以隱藏這個產(chǎn)品的表示和內(nèi)部結(jié)構(gòu),以及具體的裝配方式。
(2)它將構(gòu)造代碼和表示代碼分開。
(3)它使你可對構(gòu)造過程進(jìn)行更精細(xì)的控制。

我們還是利用上一小節(jié)中構(gòu)建迷宮的例子來簡單介紹生成器模式。對應(yīng)著上面的結(jié)構(gòu)圖來看,迷宮就是我們最終的Product。接下來我們?yōu)樯蒑aze創(chuàng)建一個Builder接口MazeBuilder,然后可以在MazeBuilder的基礎(chǔ)上實現(xiàn)不同的創(chuàng)建Maze的子類。最后Director要做的事情就是調(diào)用MazeBuilder提供的接口利用用戶提供的具體參數(shù)來生成迷宮。

public interface MazeBuilder
{
    void BuildMaze();
    void BuildRoom(int roomNo);
    void BuildDoor(int roomFrom, int roomTo);
    
    Maze GetMaze();
}

public class StandardMazeBuilder : MazeBuilder
{
    private Maze currentMaze = new Maze();

    public override void BuildMaze()
    {
        currentMaze = new Maze();
    }

    public override void BuildRoom(int roomNo)
    {   
        if(!currentMaze.ContainsRoom(roomNo))
        {
            var room = new Room(roomNo);
            currentMaze.AddRoom(room);
        }
    }
    
    public override void BuildDoor(int roomFrom, int roomTo)
    {   
        if(currentMaze.ContainsRoom(roomFrom) && currentMaze.ContainsRoom(roomTo))
        {
            var door = new Door(roomFrom, roomTo);
            currentMaze.AddDoor(door);
        }
    }

    public override Maze GetMaze()
    {
        return currentMaze;
    }
}

5. 小結(jié)

用一個系統(tǒng)創(chuàng)建的那些對象的類對系統(tǒng)進(jìn)行參數(shù)化有兩種常用方法。一種時生成創(chuàng)建對象的類的子類,對應(yīng)的設(shè)計模式有工廠方法模式。另一種方法則更多的依賴于對象復(fù)合,對應(yīng)的設(shè)計模式有抽象工廠、生成器和原型模式。所有這三個模式都涉及到創(chuàng)建一個新的復(fù)雜創(chuàng)建產(chǎn)品對象的“工廠對象”。抽象工廠由這個工廠對象產(chǎn)生多個類的對象;生成器由這個工廠對象使用一個相對復(fù)雜的協(xié)議,逐步創(chuàng)建一個復(fù)雜產(chǎn)品。原型模式由該工廠對象通過拷貝原型對象來創(chuàng)建產(chǎn)品對象。

參考文獻(xiàn)

《設(shè)計模式 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》

《深入淺出設(shè)計模式》

創(chuàng)建型設(shè)計模式 by 極客學(xué)院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計模式匯總 一、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,093評論 1 15
  • 1 場景問題# 1.1 訂單處理系統(tǒng)## 考慮這樣一個實際應(yīng)用:訂單處理系統(tǒng)。 現(xiàn)在有一個訂單處理的系統(tǒng),里面有個...
    七寸知架構(gòu)閱讀 4,666評論 3 63
  • 設(shè)計模式基本原則 開放-封閉原則(OCP),是說軟件實體(類、模塊、函數(shù)等等)應(yīng)該可以拓展,但是不可修改。開-閉原...
    西山薄涼閱讀 4,081評論 3 14
  • 一個UML類圖 類之間的關(guān)系 類的繼承結(jié)構(gòu)表現(xiàn)在UML中為:泛化(generalize)與實現(xiàn)(realize) ...
    僚機(jī)KK閱讀 750評論 0 0
  • 我們?nèi)齻€合力使用靈力只能沖開封印的一小塊角落,并且是封印比較薄弱的一角,在三秒后鉆入了龍族的領(lǐng)域以石壁為背景的封...
    那片回憶閱讀 563評論 0 0

友情鏈接更多精彩內(nèi)容