1. 程式人生 > >設計模式學習總結(2)單例模式、建造者模式、原型模式

設計模式學習總結(2)單例模式、建造者模式、原型模式

單例模式(Singleton Pattern)

這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

單例模式有以下三點注意:

  • 1、單例類只能有一個例項。
  • 2、單例類必須自己建立自己的唯一例項。
  • 3、單例類必須給所有其他物件提供這一例項。

單例的好處:

  1. 某些類建立比較頻繁,對於一些大型的物件,這是一筆很大的系統開銷。
  2. 省去了new操作符,降低了系統記憶體的使用頻率,減輕GC壓力。
  3. 有些類如交易所的核心交易引擎,控制著交易流程,如果該類可以建立多個的話,系統完全亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,才能保證核心交易伺服器獨立控制整個流程。

缺點:

沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

注意:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多執行緒同時進入造成 instance 被多次例項化。

單例模式的實現方式一共有6種,一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現 lazy loading 效果時,才會使用第 5 種登記方式(建議使用這個方法)。如果涉及到反序列化建立物件時,可以嘗試使用第 6 種列舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。

1、懶漢式,執行緒不安全

是否 Lazy 初始化:

是否多執行緒安全:

總結:最大的問題就是不支援多執行緒,

/**
 * @ClassName: Singleton1
 * @Description: 懶漢式,執行緒不安全
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:18
 */
public class Singleton1 {
    private static Singleton1 instance;
    private Singleton1(){}
    public static Singleton1 getInstance(){
        if(instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
    public void showMessage(){
        System.out.println("hello singleton1");
    }
}
/**
 * @ClassName: SingletonPatternDemo
 * @Description: 測試demo
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:19
 */
public class SingletonPatternDemo {
    public static void main(String[] args){
        Singleton1 singleton1 = Singleton1.getInstance();
        singleton1.showMessage();
    }
}

2、懶漢式,執行緒安全

是否 Lazy 初始化:

是否多執行緒安全:

總結:能進行多執行緒,但是加鎖會導致效率很低,優點就是避免了記憶體的浪費。這個還是有一個問題的,就是看到別的大神指出,如果A執行緒進入getInstance函式建立instance例項,不過並沒有進行賦值操作,但是B執行緒這時也要使用這個類建立的唯一例項,但是他發現instance已經建立,但是訪問卻什麼也沒有,因而出現問題。

/**
 * @ClassName: Singleton2
 * @Description: 懶漢式,執行緒安全
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:28
 */
public class Singleton2 {
    private static Singleton2 instance;
    private Singleton2(){}
    public static synchronized Singleton2 getInstance(){
        if(instance == null){
            instance = new Singleton2();
        }
        return instance;
    }
    public void showMessage(){
        System.out.println("hello singleton2");
    }
}

3、餓漢式

是否 Lazy 初始化:

是否多執行緒安全:

總結:它基於 classloader 機制避免了多執行緒的同步問題,但是缺點就是造成了記憶體的浪費,沒有達到lazy loading的效果。我認為相當於把instance的宣告和獲取分開了。

/**
 * @ClassName: Singleton3
 * @Description: 餓漢式
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:37
 */
public class Singleton3 {
    private static Singleton3 instance = new Singleton3();
    private Singleton3(){}
    public static Singleton3 getInstance(){
        return instance;
    }
    public void showMessage(){
        System.out.println("hello singleton3");
    }
}

4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)

是否 Lazy 初始化:

是否多執行緒安全:

總結:這種方式採用雙鎖機制,安全且在多執行緒情況下能保持高效能。(雖然程式碼也模仿的寫出來了,但是感覺還是有點不明白啊)

/**
 * @ClassName: Singleton4
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:47
 */
public class Singleton4 {
    private volatile static Singleton4 instance;
    private Singleton4(){}
    public static Singleton4 getInstance(){
        if(instance == null){
            synchronized (Singleton4.class){
                if(instance == null){
                    instance = new Singleton4();
                }
            }
        }
        return instance;
    }
    public void showMessage(){
        System.out.println("hello singleton4");
    }
}

5、登記式/靜態內部類

是否 Lazy 初始化:

是否多執行緒安全:

總結:也是利用了classloader機制保證初始化instance只有一個執行緒,採用了類裡面再單獨使用一個類去初始化的方法,可以有效地去避免記憶體的浪費,還可以保證效率,是一個常用的辦法

/**
 * @ClassName: Singleton5
 * @Description: 登記式/靜態內部類
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 11:54
 */
public class Singleton5 {
    private static class SingletonHolder{
        private static final Singleton5 instance = new Singleton5();
    }
    private Singleton5(){}
    public static Singleton5 getInstance(){
        return  SingletonHolder.instance;
    }
    public void showMessage(){
        System.out.println("hello singleton5");
    }
}

6、列舉

是否 Lazy 初始化:

是否多執行緒安全:

總結:這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還自動支援序列化機制,防止反序列化重新建立新的物件,絕對防止多次例項化。不過,由於 JDK1.5 之後才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。
不能通過 reflection attack 來呼叫私有構造方法。(這個方法還需要再研究啊。。。)

/**
 * @ClassName: Singleton6
 * @Description: 列舉
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:06
 */
public enum Singleton6 {
    INSTANCE;
    public void showMessage(){
        System.out.println("hello singleton6");
    }
}
        //方法6:列舉
        Singleton6 singleton6 = Singleton6.INSTANCE;
        singleton6.showMessage();

建造者模式(Builder Pattern)

一個 Builder 類會一步一步構造最終的物件。該 Builder 類是獨立於其他物件的。

主要解決:主要解決在軟體系統中,有時候面臨著"一個複雜物件"的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定。

優點: 1、建造者獨立,易擴充套件。 2、便於控制細節風險。

缺點: 1、產品必須有共同點,範圍有限制。 2、如內部變化複雜,會有很多的建造類。

/**
 * @ClassName: Packing
 * @Description: 包裝類介面
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:30
 */
public interface Packing {
    public String pack();
}
/**
 * @ClassName: Bottle
 * @Description: 包裝類介面實體類
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:32
 */
public class Bottle implements Packing{
    @Override
    public String pack() {
        return "bottle";
    }
}
/**
 * @ClassName: Wrapper
 * @Description: 紙質包裝實體類
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:31
 */
public class Wrapper implements Packing{
    @Override
    public String pack(){
        return "wrapper";
    }
}
/**
 * @ClassName: Item
 * @Description: 食物條目介面
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:29
 */
public interface Item {
    public String name();
    public Packing packing();
    public float price();
}
/**
 * @ClassName: Burger
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 12:33
 */
public abstract class Burger implements Item{
    @Override
    public Packing packing() {
        return new Wrapper();
    }

//    @Override
//    public abstract float price();
}
/**
 * @ClassName: ColdDrink
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:11
 */
public abstract class ColdDrink implements Item{
    @Override
    public Packing packing() {
        return new Bottle();
    }
}
/**
 * @ClassName: VerBurger
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:12
 */
public class VerBurger extends Burger{
    @Override
    public float price() {
        return 25.0f;
    }

    @Override
    public String name() {
        return "VerBurger";
    }
}
/**
 * @ClassName: ChickenBurger
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:14
 */
public class ChickenBurger extends Burger{
    @Override
    public float price() {
        return 50.5f;
    }

    @Override
    public String name() {
        return "chickenbuger";
    }
}
/**
 * @ClassName: Coke
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:15
 */
public class Coke extends ColdDrink{
    @Override
    public float price() {
        return 10.0f;
    }

    @Override
    public String name() {
        return "coke";
    }
}
/**
 * @ClassName: Pepsi
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:16
 */
public class Pepsi extends ColdDrink{
    @Override
    public float price() {
        return 9.0f;
    }

    @Override
    public String name() {
        return "pepsi";
    }
}
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: Meal
 * @Description: 建立一個 Meal 類,帶有上面定義的 Item 物件
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:21
 */
public class Meal {
    public 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.println("Item:"+item.name());
            System.out.println("Packing:"+item.packing().pack());
            System.out.println("price:"+item.price());
        }
    }
}
/**
 * @ClassName: MealBuilder
 * @Description: 建立一個 MealBuilder 類,實際的 builder 類負責建立 Meal 物件。
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:27
 */
public class MealBuilder {
    public Meal prepareVegMeal(){
        Meal meal = new Meal();
        meal.addItem(new VerBurger());
        meal.addItem(new Coke());
        return meal;
    }

    public Meal perpareChickenMeal(){
        Meal meal = new Meal();
        meal.addItem(new ChickenBurger());
        meal.addItem(new Pepsi());
        return meal;
    }
}
/**
 * @ClassName: BuilderPatternDemo
 * @Description: BuiderPatternDemo 使用 MealBuider 來演示建造者模式(Builder Pattern)。
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 13:31
 */
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 chickMeal = mealBuilder.perpareChickenMeal();
        System.out.println("chick meal");
        chickMeal.showItems();
        System.out.println("total cost:" + chickMeal.getCost());
    }
}

原型模式(Prototype Pattern)

這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。

注意事項:與通過對一個類進行例項化來構造新物件不同的是,原型模式是通過拷貝一個現有物件生成新物件的。淺拷貝實現 Cloneable,重寫,深拷貝是通過實現 Serializable 讀取二進位制流。

/**
 * @ClassName: Shape
 * @Description: 建立一個實現了 Clonable 介面的抽象類。
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 14:16
 */
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;
    }
}
/**
 * @ClassName: Square
 * @Description: java類作用描述
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 14:23
 */
public class Square extends Shape{
    public Square(){
        type = "square";
    }

    public void draw(){
        System.out.println("draw square");
    }
}
/**
 * @ClassName: Rectangle
 * @Description: 拓展抽象類的實體類
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 14:21
 */
public class Rectangle extends Shape{
    public Rectangle(){
        type = "Rectangle";
    }

    public void draw(){
        System.out.println("draw rectangle");
    }
}
import java.util.Hashtable;

/**
 * @ClassName: ShapeCache
 * @Description: 建立一個類,從資料庫獲取實體類,並把它們儲存在一個 Hashtable 中。
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 14:55
 */
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();
    }

    public static void loadCache(){
        Square square = new Square();
        square.setId("1");
        shapeMap.put(square.getId(),square);

        Rectangle rectangle = new Rectangle();
        rectangle.setId("2");
        shapeMap.put(rectangle.getId(),rectangle);
    }
}
/**
 * @ClassName: PrototypePatternDemo
 * @Description: PrototypePatternDemo 使用 ShapeCache 類來獲取儲存在 Hashtable 中的形狀的克隆。
 * @Author: xinyuan
 * @CreateDate: 2018/9/24 15:03
 */
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());
    }
}