設(shè)計模式之創(chuàng)建型模式

下面總結(jié)設(shè)計模式中的創(chuàng)建型模式:

1.簡單工廠模式

簡單工廠不是設(shè)計模式,更像是一種編程習(xí)慣。它把實(shí)例化的操作單獨(dú)放到一個類中,這個類就成為簡單工廠類,讓簡單工廠類來決定應(yīng)該用哪個具體子類來實(shí)例化。
在簡單工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象,從而將客戶類和具體子類的實(shí)現(xiàn)解耦,客戶類不再需要知道有哪些子類以及應(yīng)當(dāng)實(shí)例化哪個子類。客戶類往往有多個,如果不使用簡單工廠,那么所有的客戶類都要知道所有子類的細(xì)節(jié)。而且一旦子類發(fā)生改變,例如增加子類,那么所有的客戶類都要進(jìn)行修改。

介紹

意圖:在創(chuàng)建一個對象時不向客戶暴露內(nèi)部細(xì)節(jié),并提供一個創(chuàng)建對象的通用接口。

主要解決:主要解決接口選擇的問題。

何時使用:我們明確地計劃不同條件下創(chuàng)建不同實(shí)例時。

如何解決:讓其子類實(shí)現(xiàn)工廠接口,返回的也是一個抽象的產(chǎn)品。

關(guān)鍵代碼:創(chuàng)建過程在其子類執(zhí)行。

應(yīng)用實(shí)例: 1、您需要一輛汽車,可以直接從工廠里面提貨,而不用去管這輛汽車是怎么做出來的,以及這個汽車?yán)锩娴木唧w實(shí)現(xiàn)。 2、Hibernate 換數(shù)據(jù)庫只需換方言和驅(qū)動就可以。

優(yōu)點(diǎn): 1、一個調(diào)用者想創(chuàng)建一個對象,只要知道其名稱就可以了。 2、擴(kuò)展性高,如果想增加一個產(chǎn)品,只要擴(kuò)展一個工廠類就可以。 3、屏蔽產(chǎn)品的具體實(shí)現(xiàn),調(diào)用者只關(guān)心產(chǎn)品的接口。

缺點(diǎn):每次增加一個產(chǎn)品時,都需要增加一個具體類和對象實(shí)現(xiàn)工廠,使得系統(tǒng)中類的個數(shù)成倍增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,同時也增加了系統(tǒng)具體類的依賴。這并不是什么好事。

使用場景: 1、日志記錄器:記錄可能記錄到本地硬盤、系統(tǒng)事件、遠(yuǎn)程服務(wù)器等,用戶可以選擇記錄日志到什么地方。 2、數(shù)據(jù)庫訪問,當(dāng)用戶不知道最后系統(tǒng)采用哪一類數(shù)據(jù)庫,以及數(shù)據(jù)庫可能有變化時。 3、設(shè)計一個連接服務(wù)器的框架,需要三個協(xié)議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產(chǎn)品類,共同實(shí)現(xiàn)一個接口。

注意事項:作為一種創(chuàng)建類模式,在任何需要生成復(fù)雜對象的地方,都可以使用工廠模式。有一點(diǎn)需要注意的地方就是復(fù)雜對象適合使用工廠模式,而簡單對象,特別是只需要通過 new 就可以完成創(chuàng)建的對象,無需使用工廠模式。如果使用工廠模式,就需要引入一個工廠類,會增加系統(tǒng)的復(fù)雜度。

實(shí)現(xiàn)

我們將創(chuàng)建一個 Shape 接口和實(shí)現(xiàn) Shape 接口的實(shí)體類。下一步是定義工廠類 ShapeFactory。
FactoryPatternDemo,我們的演示類使用 ShapeFactory 來獲取 Shape 對象。它將向 ShapeFactory 傳遞信息(CIRCLE / RECTANGLE / SQUARE),以便獲取它所需對象的類型。

簡單工廠模式.jpg

步驟 1
創(chuàng)建一個接口。
Shape.java

public interface Shape {
   void draw();
}

步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類。
Rectangle.java

public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

Square.java

public class Square implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

Circle.java

public class Circle implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步驟 3
創(chuàng)建一個工廠,生成基于給定信息的實(shí)體類的對象。
ShapeFactory.java

public class ShapeFactory {
    
   /*使用 getShape 方法獲取形狀類型的對象,
   在實(shí)際操作中建議把CIRCLE等定義為常量*/
   public static Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

步驟 4
使用該工廠,通過傳遞類型信息來獲取實(shí)體類的對象。
FactoryPatternDemo.java

public class FactoryPatternDemo {

   public static void main(String[] args) {
      

      //獲取 Circle 的對象,并調(diào)用它的 draw 方法
      Shape shape1 = ShapeFactory.getShape("CIRCLE");

      //調(diào)用 Circle 的 draw 方法
      shape1.draw();

      //獲取 Rectangle 的對象,并調(diào)用它的 draw 方法
      Shape shape2 = ShapeFactory.getShape("RECTANGLE");

      //調(diào)用 Rectangle 的 draw 方法
      shape2.draw();

      //獲取 Square 的對象,并調(diào)用它的 draw 方法
      Shape shape3 = ShapeFactory.getShape("SQUARE");

      //調(diào)用 Square 的 draw 方法
      shape3.draw();
   }
}

步驟 5
驗證輸出。
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

使用反射機(jī)制可以解決每次增加一個產(chǎn)品時,都需要增加一個對象實(shí)現(xiàn)工廠接口的缺點(diǎn)。注意要使用完整包名,也可以使用Properties文件做映射。

public class ShapeFactory {
    public static Shape getShapeByClassName(String className) {
    Shape obj = null;

        try {
            obj = (Shape)Class.forName(clazz.getName()).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return obj;
        
    }
}

2.工廠方法模式

介紹

意圖:定義了一個創(chuàng)建對象的接口,但由子類決定要實(shí)例化哪個類。工廠方法把實(shí)例化操作推遲到子類。

工廠方法和簡單工廠的區(qū)別:簡單工廠模式的實(shí)質(zhì)是由一個工廠類根據(jù)傳入的參數(shù),動態(tài)決定應(yīng)該創(chuàng)建哪一個產(chǎn)品類(這些產(chǎn)品類繼承自一個父類或接口)的實(shí)例。相當(dāng)于是一個工廠中有各種產(chǎn)品,這樣工廠的職責(zé)較重,而且當(dāng)產(chǎn)品類型過多時不利于系統(tǒng)的擴(kuò)展維護(hù)。如果增加一類產(chǎn)品,必須修改工廠類的方法。而工廠方法提供了一個工廠接口,定義了許多工廠實(shí)現(xiàn)類來負(fù)責(zé)生產(chǎn)不同的產(chǎn)品,這樣如果想要增加一類產(chǎn)品只需定義一個新的工廠實(shí)現(xiàn)類即可。且簡單工廠返回產(chǎn)品的方法為static的,因為簡單工廠不需要創(chuàng)建工廠實(shí)例,而工廠方法要創(chuàng)建工廠實(shí)例。

工廠模式的實(shí)際應(yīng)用:

JDBC中Connection的創(chuàng)建
特定的Driver創(chuàng)建對應(yīng)的connection。

Spring的Bean管理

實(shí)現(xiàn)

在簡單工廠中,創(chuàng)建對象的是另一個類,而在工廠方法中,是由子類來創(chuàng)建對象。
下圖中,F(xiàn)actory 有一個 doSomething() 方法,這個方法需要用到一個產(chǎn)品對象,這個產(chǎn)品對象由 factoryMethod() 方法創(chuàng)建。該方法是抽象的,需要由子類去實(shí)現(xiàn)。


Factory.java

public abstract class Factory {
    abstract public Product factoryMethod();
    public void doSomething() {
        Product product = factoryMethod();
        // do something with the product
    }
}

ConcreteFactory.java

public class ConcreteFactory extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}

BrickFactory.java

public class BrickFactory extends Factory {
    public Product factoryMethod() {
        return new Brick();
    }
}

SteelFactory.java

public class SteelFactory extends Factory {
    public Product factoryMethod() {
        return new Steel();
    }
}

3.抽象工廠模式

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創(chuàng)建其他工廠。該超級工廠又稱為其他工廠的工廠。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

在抽象工廠模式中,接口是負(fù)責(zé)創(chuàng)建一個相關(guān)對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。

抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和最具一般性的一種形態(tài)。

抽象工廠模式創(chuàng)建的是對象家族,也就是很多對象而不是一個對象,并且這些對象是相關(guān)的,也就是說必須一起創(chuàng)建出來。而工廠方法模式只是用于創(chuàng)建一個對象,這和抽象工廠模式有很大不同。

為了方便引進(jìn)抽象工廠模式,引進(jìn)一個新概念:產(chǎn)品族(Product Family)。所謂產(chǎn)品族,是指位于不同產(chǎn)品等級結(jié)構(gòu),功能相關(guān)聯(lián)的產(chǎn)品組成的家族。如圖:

產(chǎn)品族1.gif

圖中一共有四個產(chǎn)品族,分布于三個不同的產(chǎn)品等級結(jié)構(gòu)中。同一個產(chǎn)品族是同一個工廠生產(chǎn)的,而不同等級結(jié)構(gòu)來自不同的工廠。只要指明一個產(chǎn)品所處的產(chǎn)品族以及它所屬的等級結(jié)構(gòu),就可以唯一的確定這個產(chǎn)品。
所謂的抽象工廠是指一個工廠等級結(jié)構(gòu)可以創(chuàng)建出分屬于不同產(chǎn)品等級結(jié)構(gòu)的一個產(chǎn)品族中的所有對象。如果用圖來描述的話,如下圖:

產(chǎn)品族2.gif

介紹

意圖:提供一個接口,用于創(chuàng)建相關(guān)的對象家族

主要解決:主要解決接口選擇的問題。

何時使用:系統(tǒng)的產(chǎn)品有多于一個的產(chǎn)品族,而系統(tǒng)只消費(fèi)其中某一族的產(chǎn)品。

如何解決:在一個產(chǎn)品族里面,定義多個產(chǎn)品。

關(guān)鍵代碼:在一個工廠里聚合多個同類產(chǎn)品。

應(yīng)用實(shí)例:工作了,為了參加一些聚會,肯定有兩套或多套衣服吧,比如說有商務(wù)裝(成套,一系列具體產(chǎn)品)、時尚裝(成套,一系列具體產(chǎn)品),甚至對于一個家庭來說,可能有商務(wù)女裝、商務(wù)男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產(chǎn)品。假設(shè)一種情況(現(xiàn)實(shí)中是不存在的,要不然,沒法進(jìn)入共產(chǎn)主義了,但有利于說明抽象工廠模式),在您的家中,某一個衣柜(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產(chǎn)品),每次拿這種成套的衣服時也自然要從這個衣柜中取出了。用 OO 的思想去理解,所有的衣柜(具體工廠)都是衣柜類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產(chǎn)品),褲子(某一具體產(chǎn)品),這些具體的上衣其實(shí)也都是上衣(抽象產(chǎn)品),具體的褲子也都是褲子(另一個抽象產(chǎn)品)。

優(yōu)點(diǎn):當(dāng)一個產(chǎn)品族中的多個對象被設(shè)計成一起工作時,它能保證客戶端始終只使用同一個產(chǎn)品族中的對象。

缺點(diǎn):產(chǎn)品族擴(kuò)展非常困難,要增加一個系列的某一產(chǎn)品,既要在抽象的 Creator 里加代碼,又要在具體的里面加代碼。

使用場景: 1、QQ 換皮膚,一整套一起換。 2、生成不同操作系統(tǒng)的程序。

注意事項:產(chǎn)品族難擴(kuò)展,產(chǎn)品等級易擴(kuò)展。

抽象工廠模式.gif

上圖的描述用產(chǎn)品族描述如下:

產(chǎn)品族3.gif

實(shí)現(xiàn)

我們來舉這樣一個例子,QQ秀有不同的裝扮,分為男孩和女孩,而男孩又分為圣誕男孩和新年男孩,女孩分為圣誕女孩和新年女孩。那么就可以有一個抽象工廠生產(chǎn)男孩和女孩。兩個具體的工廠分別生產(chǎn)圣誕系列的男孩和女孩、新年系列的男孩和女孩。同一系列的男孩和女孩是一個產(chǎn)品族,而不同系列構(gòu)成不同的產(chǎn)品等級。

步驟 1
為男孩創(chuàng)建一個接口。
Boy.java

public interface Boy {
    public void drawMan();
}

步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類。
MCBoy.java

public class MCBoy implements Boy {

    @Override
    public void drawMan() {
        System.out.println("-----------------圣誕系列的男孩子--------------------");
    }

}

HNBoy.java

public class HNBoy implements Boy {

    @Override
    public void drawMan() {
        System.out.println("-----------------新年系列的男孩子--------------------");
    }

}

步驟 3
為女孩創(chuàng)建一個接口
Girl.java

public interface Girl {
    public void drawWomen();
}

步驟 4
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類。
MCGirl.java

public class MCGirl implements Girl {

    @Override
    public void drawWomen() {
        System.out.println("-----------------圣誕系列的女孩子--------------------");
    }

}

HNGirl.java

public class HNGirl implements Girl {

    @Override
    public void drawWomen() {
        // TODO Auto-generated method stub
        System.out.println("-----------------新年系列的女孩子--------------------");
    }

}

步驟 5
創(chuàng)建生產(chǎn)男孩女孩的抽象工廠接口
PersonFactory.java

public interface PersonFactory {
    //男孩接口
    public Boy getBoy();
    //女孩接口
    public Girl getGirl();  
}

步驟 6
創(chuàng)建生產(chǎn)圣誕和新年系列的具體工廠
MCFactory.java

public class MCFctory implements PersonFactory {

    @Override
    public Boy getBoy() {
        return new MCBoy();
    }

    @Override
    public Girl getGirl() {
        return new MCGirl();
    }

}

HNFactory.java

public class HNFactory implements PersonFactory {

    @Override
    public Boy getBoy() {
        return new HNBoy();
    }

    @Override
    public Girl getGirl() {
        return new HNGirl();
    }

}

步驟 7
使用工廠生產(chǎn)
AbstractFactoryPatternDemo.java

public class AbstractFactoryPatternDemo{
    public static void main(String[] args){
        MCFactory mcFactory = new MCFactory();
        HNFactory hnFactory = new HNFactory();
        Boy mcBoy = mcFactory.getBoy();
        Girl mcGirl = mcFactory.getGirl();
        Boy hnBoy = hnFactory.getBoy();
        Girl hnGirl = hnFactory.getGirl();
        mcBoy.drawMan();
        mcGirl.drawWomen();
        hnBoy.drawMan();
        hnGirl.drawWomen();
    }
}

步驟 8
驗證輸出
-----------------圣誕系列的男孩子--------------------
-----------------圣誕系列的女孩子--------------------
-----------------新年系列的男孩子--------------------
-----------------新年系列的女孩子--------------------

抽象工廠模式用到了工廠方法模式來創(chuàng)建單一對象,PersonFactory 中的 getBoy() 和 getGirl() 方法都是讓子類來實(shí)現(xiàn),這兩個方法單獨(dú)來看就是在創(chuàng)建一個對象,這符合工廠方法模式的定義。

至于創(chuàng)建對象的家族這一概念是在 AbstractFactoryPatternDemo 體現(xiàn), AbstractFactoryPatternDemo 要通過 PersonFactory 同時調(diào)用兩個方法來創(chuàng)建出兩個對象,在這里這兩個對象就有很大的相關(guān)性, AbstractFactoryPatternDemo 需要同時創(chuàng)建出這兩個對象。

從高層次來看,抽象工廠使用了組合,即 AbstractFactoryPatternDemo 組合了 PersonFactory,而工廠方法模式使用了繼承。

注意:抽象工廠也可以運(yùn)用反射,只不過反射的不再是產(chǎn)品類,而是不同的具體工廠類。

對比.png

4.單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實(shí)例化該類的對象
注意:
1、單例類只能有一個實(shí)例
2、單例類必須自己創(chuàng)建自己的唯一實(shí)例,而不是在外部隨意地new對象。
3、單例類必須給所有其他對象提供這一實(shí)例。

介紹

意圖:保證一個類僅有一個實(shí)例,并提供一個訪問它的全局訪問點(diǎn)
主要解決:一個全局使用的類頻繁地創(chuàng)建與銷毀。
何時使用:當(dāng)想控制實(shí)例數(shù)目,節(jié)省系統(tǒng)資源的時候。
如何解決:判斷系統(tǒng)是否已經(jīng)有這個單例,如果有則返回,如果沒有則創(chuàng)建。
關(guān)鍵代碼:使用一個私有構(gòu)造函數(shù)、一個私有靜態(tài)變量以及一個公有靜態(tài)函數(shù)來實(shí)現(xiàn)。私有構(gòu)造函數(shù)保證了不能通過構(gòu)造函數(shù)來創(chuàng)建對象實(shí)例,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量。
應(yīng)用實(shí)例: 1、一個黨只能有一個主席。 2、Windows 是多進(jìn)程多線程的,在操作一個文件的時候,就不可避免地出現(xiàn)多個進(jìn)程或線程同時操作一個文件的現(xiàn)象,所以所有文件的處理必須通過唯一的實(shí)例來進(jìn)行。 3、一些設(shè)備管理器常常設(shè)計為單例模式,比如一個電腦有兩臺打印機(jī),在輸出的時候就要處理不能兩臺打印機(jī)打印同一個文件。
優(yōu)點(diǎn): 1、在內(nèi)存里只有一個實(shí)例,減少了內(nèi)存的開銷。2、避免頻繁的創(chuàng)建和銷毀實(shí)例,提高性能(比如管理學(xué)院首頁頁面緩存)。 2、避免對資源的多重占用(比如寫文件操作)。
缺點(diǎn):1、擴(kuò)展比較困難,沒有接口,不能繼承,與單一職責(zé)原則沖突,一個類應(yīng)該只關(guān)心內(nèi)部邏輯,而不關(guān)心外面怎么樣來實(shí)例化。2、如果實(shí)例化后的對象長期不利用,系統(tǒng)將默認(rèn)為垃圾進(jìn)行回收,造成對象狀態(tài)丟失。
使用場景: 1、當(dāng)多個實(shí)例存在可能引起程序邏輯錯誤,如要求生產(chǎn)唯一序列號。 2、對系統(tǒng)內(nèi)資源要求統(tǒng)一讀寫,如讀寫配置信息,又如WEB 中的計數(shù)器,不用每次刷新都在數(shù)據(jù)庫里加一次,用單例先緩存起來。 3、創(chuàng)建的一個對象需要消耗的資源過多,但同時又需要用到該對象,比如 I/O 與數(shù)據(jù)庫的連接等。
注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進(jìn)入造成 instance 被多次實(shí)例化。

實(shí)現(xiàn)

我們將創(chuàng)建一個 SingleObject 類。SingleObject 類有它的私有構(gòu)造函數(shù)和本身的一個靜態(tài)實(shí)例。
SingleObject 類提供了一個靜態(tài)方法,供外界獲取它的靜態(tài)實(shí)例。SingletonPatternDemo,我們的演示類使用 SingleObject 類來獲取 SingleObject 對象。

單例模式.jpg

步驟 1
創(chuàng)建一個 Singleton 類。

SingleObject.java
public class SingleObject {

   //創(chuàng)建 SingleObject 的一個對象
   private static SingleObject instance = new SingleObject();

   //讓構(gòu)造函數(shù)為 private,這樣該類就不會被實(shí)例化
   private SingleObject(){}

   //獲取唯一可用的對象
   public static SingleObject getInstance(){
      return instance;
   }

   public void showMessage(){
      System.out.println("Hello World!");
   }
}

步驟 2
從 singleton 類獲取唯一的對象。

SingletonPatternDemo.java
public class SingletonPatternDemo {
   public static void main(String[] args) {

      //不合法的構(gòu)造函數(shù)
      //編譯時錯誤:構(gòu)造函數(shù) SingleObject() 是不可見的
      //SingleObject object = new SingleObject();

      //獲取唯一可用的對象
      SingleObject object = SingleObject.getInstance();

      //顯示消息
      object.showMessage();
   }
}

步驟 3
驗證輸出。

Hello World!

單例模式的幾種實(shí)現(xiàn)方式

單例模式的實(shí)現(xiàn)有多種方式,如下所示:
1、懶漢式,線程不安全
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:
代碼實(shí)例:

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  /*懶漢式標(biāo)志:Lazy 初始化,
    在外部第一次請求使用該類對象時才實(shí)例化,是時間換空間的模式*/
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

描述:這種方式是最基本的實(shí)現(xiàn)方式,這種實(shí)現(xiàn)最大的問題就是不支持多線程。假設(shè)開始線程1進(jìn)入,判斷instance為空,在將要創(chuàng)建實(shí)例時,時間片切換,線程2又進(jìn)來了,同樣判斷instance為空,創(chuàng)建了實(shí)例,這是CPU調(diào)度回到線程1,繼續(xù)創(chuàng)建實(shí)例。因為沒有加鎖 synchronized,所以嚴(yán)格意義上它并不算單例模式。
這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。
接下來介紹的幾種實(shí)現(xiàn)方式都支持多線程,但是在性能上有所差異。

2、懶漢式,線程安全
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:
代碼實(shí)例:

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% 情況下不需要同步。
優(yōu)點(diǎn):第一次調(diào)用才初始化,避免內(nèi)存浪費(fèi)。
缺點(diǎn):必須加鎖 synchronized 才能保證單例,但加鎖會影響效率,下一個線程想要取得對象,必須等上一個線程釋放鎖之后,才可以繼續(xù)執(zhí)行。
getInstance() 的性能對應(yīng)用程序不是很關(guān)鍵(該方法使用不太頻繁)。

3、餓漢式
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:
代碼實(shí)例:

public class Singleton {  
    private static Singleton instance = new Singleton();  
                           /*餓漢式標(biāo)志:在類加載時直接初始化,
                           是空間換時間的模式*/
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}  

描述:這種方式比較常用,但容易產(chǎn)生垃圾對象。
優(yōu)點(diǎn):沒有加鎖,執(zhí)行效率會提高。
缺點(diǎn):類加載時就初始化,浪費(fèi)內(nèi)存。
它基于 classloder 機(jī)制避免了多線程的同步問題,不過,instance 在類裝載時就實(shí)例化,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時候初始化 instance 顯然沒有達(dá)到 lazy loading 的效果。

4、靜態(tài)代碼塊
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:
代碼實(shí)例:

public class Singleton {  
    private static Singleton instance = null;
    static{
        instance = new Singleton();  
    }
    private Singleton (){}  
    public static Singleton getInstance() {  
         return instance;  
    }  
}  

描述:類似于餓漢式。
優(yōu)點(diǎn):沒有加鎖,執(zhí)行效率會提高。
缺點(diǎn):類加載時就初始化,浪費(fèi)內(nèi)存。

5、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:較復(fù)雜
代碼實(shí)例:

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;  
    }  
}  

描述:這種方式采用雙鎖機(jī)制,安全且在多線程情況下能保持高性能。getInstance() 的性能對應(yīng)用程序很關(guān)鍵。這種方法既能保證線程安全又能提高了效率。
假設(shè)線程1進(jìn)入方法,instance為空,進(jìn)入同步代碼塊,時間片切換,線程2進(jìn)來,instance為空,在同步代碼塊外被阻塞,因為此時線程1正在里面。cup切換,線程1執(zhí)行創(chuàng)建實(shí)例,當(dāng)2再進(jìn)入代碼塊后,此時instace不為空,直接返回instance。當(dāng)再有線程進(jìn)來,instance不為空,不用執(zhí)行同步代碼塊,提高了效率。
外層 if 語句是為了保證 instance 不為空時不執(zhí)行同步代碼塊,直接返回對象,提高效率;里層 if 語句則是為了防止之前已經(jīng)進(jìn)入外層 if 語句的線程重復(fù)實(shí)例化對象,保證單例。

注意:singleton 采用 volatile 關(guān)鍵字修飾是很有必要的!
這里涉及到了JVM編譯器的指令重排
簡單的一句 singleton = new Singleton(); 會被編譯器編譯成如下JVM指令:

memory =allocate(); //1:分配對象的內(nèi)存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址

但是這些指令順序并非一成不變,有可能會經(jīng)過JVM和CPU的優(yōu)化,指令重排成下面的順序:
memory =allocate(); //1:分配對象的內(nèi)存空間
instance =memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
ctorInstance(memory); //2:初始化對象

當(dāng)線程A執(zhí)行完1,3,時,instance對象還未完成初始化,但已經(jīng)不再指向null。此時如果線程B搶占到CPU資源,執(zhí)行 if(instance == null)的結(jié)果會是false,從而返回一個沒有初始化完成的instance對象。如下圖所示:


使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。

6、登記式 / 靜態(tài)內(nèi)部類
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:一般
代碼實(shí)例:

public class Singleton {
    private Singleton (){}
    private static class LazyHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

描述:這里有幾個需要注意的點(diǎn):
1、從外部無法訪問靜態(tài)內(nèi)部類LazyHolder,只有當(dāng)調(diào)用Singleton.getInstance()方法的時候,才能得到單例對象INSTANCE。
2、靜態(tài)內(nèi)部類LazyHolder是懶加載的,它不隨外部類Singleton的加載而加載。所以INSTANCE對象不會在單例類Singleton被加載的時候就初始化,而是在調(diào)用getInstance()方法時,靜態(tài)內(nèi)部類LazyHolder被加載的時候被初始化。因此這種實(shí)現(xiàn)方式是利用classloader的加載機(jī)制來實(shí)現(xiàn)懶加載,并保證構(gòu)建單例的線程安全。

7、序列化與反序列化
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:一般
代碼實(shí)例:
MyObject.java


SaveAndRead.java


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

發(fā)現(xiàn)返回的不是同一個對象

去掉如下代碼的注釋


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

返回的是同一個對象

分析
靜態(tài)內(nèi)部類可以實(shí)現(xiàn)線程安全,但如果遇到序列化對象,使用默認(rèn)的方法運(yùn)行得到的結(jié)果還是多例的。如果既想要做到可序列化,又想要反序列化為同一對象,則必須實(shí)現(xiàn)readResolve方法。readResolve() 方法會緊挨著 readObject() 之后被調(diào)用,該方法的返回值將會代替原來反序列化的對象,而原來 readObject() 反序列化的對象將會被立即丟棄。

8、枚舉
JDK 版本:JDK1.5 起
是否 Lazy 初始化:
是否多線程安全:
實(shí)現(xiàn)難度:
代碼實(shí)例:

public enum Singleton {
    INSTANCE;
}

描述:單元素的枚舉類型是實(shí)現(xiàn)單例模式的最佳方法。它更簡潔,自動支持序列化機(jī)制,絕對防止多次實(shí)例化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機(jī)制,該實(shí)現(xiàn)在多次序列化再進(jìn)行反序列化之后,不會得到多個實(shí)例。而其它實(shí)現(xiàn),為了保證不會出現(xiàn)反序列化之后出現(xiàn)多個實(shí)例,需要使用 transient 修飾所有字段,并且實(shí)現(xiàn)序列化和反序列化的方法,來防止反序列化重新創(chuàng)建新的對象。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實(shí)際工作中,也很少用。

該實(shí)現(xiàn)可以防止反射攻擊。在其它實(shí)現(xiàn)中,通過 setAccessible() 方法可以將私有構(gòu)造函數(shù)的訪問級別設(shè)置為 public,然后調(diào)用構(gòu)造函數(shù)從而實(shí)例化對象,如果要防止這種攻擊,需要在構(gòu)造函數(shù)中添加防止實(shí)例化第二個對象的代碼。但是該實(shí)現(xiàn)是由 JVM 保證只會實(shí)例化一次,因此不會出現(xiàn)上述的反射攻擊。

經(jīng)驗之談:一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實(shí)現(xiàn) lazy loading 效果時,才會使用第 6 種登記方式。如果涉及到反序列化創(chuàng)建對象時,可以嘗試使用第 8 種枚舉方式。如果有其他特殊的需求,可以考慮使用第 5 種雙檢鎖方式。

5.建造者模式

建造者模式(Builder Pattern)使用多個簡單的對象一步一步構(gòu)建成一個復(fù)雜的對象。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
一個 Builder 類會一步一步構(gòu)造最終的對象。該 Builder 類是獨(dú)立于其他對象的。

介紹

意圖:將一個復(fù)雜的構(gòu)建與其表示相分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
主要解決:主要解決在軟件系統(tǒng)中,有時候面臨著"一個復(fù)雜對象"的創(chuàng)建工作,其通常由各個部分的子對象用一定的算法構(gòu)成;由于需求的變化,這個復(fù)雜對象的各個部分經(jīng)常面臨著劇烈的變化,但是將它們組合在一起的算法卻相對穩(wěn)定。
何時使用:一些基本部件不會變,而其組合經(jīng)常變化的時候。
如何解決:將變與不變分離開。
關(guān)鍵代碼:建造者:創(chuàng)建和提供實(shí)例,導(dǎo)演:管理建造出來的實(shí)例的依賴關(guān)系。
應(yīng)用實(shí)例:
1、去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經(jīng)常變化的,生成出所謂的"套餐"。
2、JAVA 中的 StringBuilder。
優(yōu)點(diǎn):1、建造者獨(dú)立,易擴(kuò)展。 2、便于控制細(xì)節(jié)風(fēng)險。
缺點(diǎn): 1、產(chǎn)品必須有共同點(diǎn),范圍有限制。 2、如內(nèi)部變化復(fù)雜,會有很多的建造類。
使用場景: 1、需要生成的對象具有復(fù)雜的內(nèi)部結(jié)構(gòu)。 2、需要生成的對象內(nèi)部屬性本身相互依賴。
注意事項:與工廠模式的區(qū)別是:建造者模式更加關(guān)注與零件裝配的順序。

實(shí)現(xiàn)

我們假設(shè)一個快餐店的商業(yè)案例,其中,一個典型的套餐可以是一個漢堡(Burger)和一杯冷飲(Cold drink)。漢堡(Burger)可以是素食漢堡(Veg Burger)或雞肉漢堡(Chicken Burger),它們是包在紙盒中。冷飲(Cold drink)可以是可口可樂(coke)或百事可樂(pepsi),它們是裝在瓶子中。
我們將創(chuàng)建一個表示食物條目(比如漢堡和冷飲)的 Item 接口和實(shí)現(xiàn) Item 接口的實(shí)體類,以及一個表示食物包裝的 Packing 接口和實(shí)現(xiàn) Packing 接口的實(shí)體類,漢堡是包在紙盒中,冷飲是裝在瓶子中。
然后我們創(chuàng)建一個 Meal 類,帶有 Item 的 ArrayList 和一個通過結(jié)合 Item 來創(chuàng)建不同類型的 Meal 對象的 MealBuilder。BuilderPatternDemo,我們的演示類使用 MealBuilder 來創(chuàng)建一個 Meal。


步驟 1
創(chuàng)建一個表示食物條目和食物包裝的接口。
Item.java

public interface Item {
   public String name();
   public Packing packing();
   public float price();    
}

Packing.java

public interface Packing {
   public String pack();
}

步驟 2
創(chuàng)建實(shí)現(xiàn) Packing 接口的實(shí)體類。
Wrapper.java

public class Wrapper implements Packing {
 
   @Override
   public String pack() {
      return "Wrapper";
   }
}

Bottle.java

public class Bottle implements Packing {
 
   @Override
   public String pack() {
      return "Bottle";
   }
}

步驟 3
創(chuàng)建實(shí)現(xiàn) Item 接口的抽象類,該類提供了默認(rèn)的功能。
Burger.java

public abstract class Burger implements Item {
 
   @Override
   public Packing packing() {
      return new Wrapper();
   }
 
   @Override
   public abstract float price();
}

ColdDrink.java

public abstract class ColdDrink implements Item {
 
    @Override
    public Packing packing() {
       return new Bottle();
    }
 
    @Override
    public abstract float price();
}

步驟 4
創(chuàng)建擴(kuò)展了 Burger 和 ColdDrink 的實(shí)體類。
VegBurger.java

public class VegBurger extends Burger {
 
   @Override
   public float price() {
      return 25.0f;
   }
 
   @Override
   public String name() {
      return "Veg Burger";
   }
}

ChickenBurger.java

public class ChickenBurger extends Burger {
 
   @Override
   public float price() {
      return 50.5f;
   }
 
   @Override
   public String name() {
      return "Chicken Burger";
   }
}

Coke.java

public class Coke extends ColdDrink {
 
   @Override
   public float price() {
      return 30.0f;
   }
 
   @Override
   public String name() {
      return "Coke";
   }
}

Pepsi.java

public class Pepsi extends ColdDrink {
 
   @Override
   public float price() {
      return 35.0f;
   }
 
   @Override
   public String name() {
      return "Pepsi";
   }
}

步驟 5
創(chuàng)建一個 Meal 類,帶有上面定義的 Item 對象。
Meal.java

import java.util.ArrayList;
import java.util.List;
 
public class Meal {
   private List<Item> items = new ArrayList<Item>();    
 
   public void addItem(Item item){
      items.add(item);
   }
 
   public float getCost(){
      float cost = 0.0f;
      for (Item item : items) {
         cost += item.price();
      }        
      return cost;
   }
 
   public void showItems(){
      for (Item item : items) {
         System.out.print("Item : "+item.name());
         System.out.print(", Packing : "+item.packing().pack());
         System.out.println(", Price : "+item.price());
      }        
   }    
}

步驟 6
創(chuàng)建一個 MealBuilder 類,實(shí)際的 builder 類負(fù)責(zé)創(chuàng)建 Meal 對象。

MealBuilder.java

public class MealBuilder {
 
   public Meal prepareVegMeal (){
      Meal meal = new Meal();
      meal.addItem(new VegBurger());
      meal.addItem(new Coke());
      return meal;
   }   
 
   public Meal prepareNonVegMeal (){
      Meal meal = new Meal();
      meal.addItem(new ChickenBurger());
      meal.addItem(new Pepsi());
      return meal;
   }
}

步驟 7
BuiderPatternDemo 使用 MealBuider 來演示建造者模式(Builder Pattern)。

BuilderPatternDemo.java

public class BuilderPatternDemo {
   public static void main(String[] args) {
      MealBuilder mealBuilder = new MealBuilder();
 
      Meal vegMeal = mealBuilder.prepareVegMeal();
      System.out.println("Veg Meal");
      vegMeal.showItems();
      System.out.println("Total Cost: " +vegMeal.getCost());
 
      Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
      System.out.println("\n\nNon-Veg Meal");
      nonVegMeal.showItems();
      System.out.println("Total Cost: " +nonVegMeal.getCost());
   }
}

步驟 8
執(zhí)行程序,輸出結(jié)果:

Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0


Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5

6.原型模式

原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象,同時又能保證性能。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式是實(shí)現(xiàn)了一個原型接口,該接口用于創(chuàng)建當(dāng)前對象的克隆。當(dāng)直接創(chuàng)建對象的代價比較大時,則采用這種模式。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫,以此來減少數(shù)據(jù)庫調(diào)用。

介紹

意圖:用原型實(shí)例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。
主要解決:在運(yùn)行期建立和刪除原型。
何時使用: 1、當(dāng)一個系統(tǒng)應(yīng)該獨(dú)立于它的產(chǎn)品創(chuàng)建,構(gòu)成和表示時。 2、當(dāng)要實(shí)例化的類是在運(yùn)行時刻指定時,例如,通過動態(tài)裝載。 3、為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時。 4、當(dāng)一個類的實(shí)例只能有幾個不同狀態(tài)組合中的一種時。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實(shí)例化該類更方便一些。
如何解決:利用已有的一個原型對象,快速地生成和原型對象一樣的實(shí)例。
關(guān)鍵代碼: 1、實(shí)現(xiàn)克隆操作,繼承 Cloneable,重寫 clone() 2、原型模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關(guān)系,它同樣要求這些"易變類"擁有穩(wěn)定的接口。
應(yīng)用實(shí)例: 1、細(xì)胞分裂。 2、JAVA 中的 Object clone() 方法。
優(yōu)點(diǎn):1、性能提高。 2、逃避構(gòu)造函數(shù)的約束。
缺點(diǎn):1、配備克隆方法需要對類的功能進(jìn)行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特別當(dāng)一個類引用不支持串行化的間接對象,或者引用含有循環(huán)結(jié)構(gòu)的時候。 2、必須實(shí)現(xiàn) Cloneable 接口。
使用場景:1、資源優(yōu)化場景。 2、類初始化需要消化非常多的資源,這個資源包括數(shù)據(jù)、硬件資源等。 3、性能和安全要求的場景。 4、通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限,則可以使用原型模式。 5、一個對象多個修改者的場景。 6、一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用。 7、在實(shí)際項目中,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn),通過 clone 的方法創(chuàng)建一個對象,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體,大家可以隨手拿來使用。
注意事項:與通過對一個類進(jìn)行實(shí)例化來構(gòu)造新對象不同的是,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的。淺拷貝實(shí)現(xiàn) Cloneable,重寫,深拷貝是通過實(shí)現(xiàn) Serializable 讀取二進(jìn)制流。

實(shí)現(xiàn)

我們將創(chuàng)建一個抽象類 Shape 和擴(kuò)展了 Shape 類的實(shí)體類。下一步是定義類 ShapeCache,該類把 shape 對象存儲在一個 Hashtable 中,并在請求的時候返回它們的克隆。
PrototypePatternDemo,我們的演示類使用 ShapeCache 類來獲取 Shape 對象。


步驟 1
創(chuàng)建一個實(shí)現(xiàn)了 Clonable 接口的抽象類。
Shape.java

public abstract class Shape implements Cloneable {
   
   private String id;
   protected String type;
   
   abstract void draw();
   
   public String getType(){
      return type;
   }
   
   public String getId() {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
   
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

步驟 2
創(chuàng)建擴(kuò)展了上面抽象類的實(shí)體類。
Rectangle.java

public class Rectangle extends Shape {
 
   public Rectangle(){
     type = "Rectangle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

Square.java

public class Square extends Shape {
 
   public Square(){
     type = "Square";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

Circle.java

public class Circle extends Shape {
 
   public Circle(){
     type = "Circle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步驟 3
創(chuàng)建一個類,從數(shù)據(jù)庫獲取實(shí)體類,并把它們存儲在一個 Hashtable 中。
ShapeCache.java

import java.util.Hashtable;
 
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap 
      = new Hashtable<String, Shape>();
 
   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }
 
   // 對每種形狀都運(yùn)行數(shù)據(jù)庫查詢,并創(chuàng)建該形狀
   // shapeMap.put(shapeKey, shape);
   // 例如,我們要添加三種形狀
   public static void loadCache() {
      Circle circle = new Circle();
      circle.setId("1");
      shapeMap.put(circle.getId(),circle);
 
      Square square = new Square();
      square.setId("2");
      shapeMap.put(square.getId(),square);
 
      Rectangle rectangle = new Rectangle();
      rectangle.setId("3");
      shapeMap.put(rectangle.getId(),rectangle);
   }
}

步驟 4
PrototypePatternDemo 使用 ShapeCache 類來獲取存儲在 Hashtable 中的形狀的克隆。
PrototypePatternDemo.java

public class PrototypePatternDemo {
   public static void main(String[] args) {
      ShapeCache.loadCache();
 
      Shape clonedShape = (Shape) ShapeCache.getShape("1");
      System.out.println("Shape : " + clonedShape.getType());        
 
      Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
      System.out.println("Shape : " + clonedShape2.getType());        
 
      Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
      System.out.println("Shape : " + clonedShape3.getType());        
   }
}

步驟 5
執(zhí)行程序,輸出結(jié)果:

Shape : Circle
Shape : Square
Shape : Rectangle

參考資料:
菜鳥教程之設(shè)計模式
CyC2018/CS-Notes/設(shè)計模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內(nèi)容