設計模式學習總結(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());
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章