天天看點

Java 版設計模式代碼案例 (一):建立型設計模式

作者:馬士兵老師

1. 工廠模式(Factory)

工廠模式提供了一種将對象的執行個體化過程封裝在工廠類中的方式。通過使用工廠模式,可以将對象的建立與使用代碼分離,提供一種統一的接口來建立不同類型的對象。

在工廠模式中,我們在建立對象時不會對用戶端暴露建立邏輯,并且是通過使用一個共同的接口來指向新建立的對象。

工廠模式包含以下幾個核心角色:

  • 抽象産品(Abstract Product):定義了産品的共同接口或抽象類。它可以是具體産品類的父類或接口,規定了産品對象的共同方法。
  • 具體産品(Concrete Product):實作了抽象産品接口,定義了具體産品的特定行為和屬性。
  • 抽象工廠(Abstract Factory):聲明了建立産品的抽象方法,可以是接口或抽象類。它可以有多個方法用于建立不同類型的産品。
  • 具體工廠(Concrete Factory):實作了抽象工廠接口,負責實際建立具體産品的對象。

我們将建立一個 Shape 接口和實作 Shape 接口的實體類。

java複制代碼public interface Shape {  
  
    void draw();  
  
}
           
java複制代碼public class Rectangle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Rectangle::draw() method.");  
    }  
  
}
           
java複制代碼public class Square implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Square::draw() method.");  
    }  
  
}
           
java複制代碼public class Circle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Circle::draw() method.");  
    }  
  
}
           

下一步是定義工廠類 ShapeFactory。

java複制代碼public enum ShapeEnum {  
  
    RECTANGLE(Rectangle.class),  
    SQUARE(Square.class),  
    CIRCLE(Circle.class);  

    Class<? extends Shape> clazz;  

    ShapeEnum(Class<? extends Shape> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<? extends Shape> getClazz() {  
        return clazz;  
    }  
  
}
           
java複制代碼public class ShapeFactory {  
  
    public Shape getShape(ShapeEnum shapeType) throws Exception {  
        return shapeType.getClazz().newInstance();  
    }  
  
}
           

執行程式,輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        ShapeFactory factory = new ShapeFactory();  
        factory.getShape(ShapeEnum.RECTANGLE).draw();  
        factory.getShape(ShapeEnum.SQUARE).draw();  
        factory.getShape(ShapeEnum.CIRCLE).draw();  
    }  
  
}
           
java複制代碼Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Circle::draw() method.
           

工廠模式的優點:1、一個調用者想建立一個對象,隻要知道其名稱就可以了。 2、擴充性高,如果想增加一個産品,隻要擴充一個工廠類就可以。 3、屏蔽産品的具體實作,調用者隻關心産品的接口。

工廠模式的缺點:每次增加一個産品時,都需要增加一個具體類和對象實作工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這并不是什麼好事。

2. 抽象工廠模式(Abstract Factory)

抽象工廠模式是圍繞一個超級工廠建立其他工廠,它提供了一種建立對象的最佳方式。 在抽象工廠模式中,接口是負責建立一個相關對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。

抽象工廠模式提供了一種建立一系列相關或互相依賴對象的接口,而無需指定具體實作類。通過使用抽象工廠模式,可以将用戶端與具體産品的建立過程解耦,使得用戶端可以通過工廠接口來建立一族産品。

抽象工廠模式包含以下幾個核心角色:

  • 抽象工廠(Abstract Factory):聲明了一組用于建立産品對象的方法,每個方法對應一種産品類型。抽象工廠可以是接口或抽象類。
  • 具體工廠(Concrete Factory):實作了抽象工廠接口,負責建立具體産品對象的執行個體。
  • 抽象産品(Abstract Product):定義了一組産品對象的共同接口或抽象類,描述了産品對象的公共方法。
  • 具體産品(Concrete Product):實作了抽象産品接口,定義了具體産品的特定行為和屬性。

我們沿用 工廠模式 的代碼例子,下一步是建立抽象工廠類 AbstractFactory。

java複制代碼public abstract class AbstractFactory {  
  
    protected abstract Shape getShape(ShapeEnum shape);  
  
}
           

接着定義工廠類 ShapeFactory,這兩個工廠類都是擴充了 AbstractFactory。

java複制代碼public class ShapesFactory extends AbstractFactory {  
  
    @Override  
    public Shape getShape(ShapeEnum shapeType) {  
        try {  
            return shapeType.getClazz().newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
}
           
java複制代碼public enum FactoryEnum {  
  
    SHAPE(ShapesFactory.class);  

    Class<? extends AbstractFactory> clazz;  

    FactoryEnum(Class<? extends AbstractFactory> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<? extends AbstractFactory> getClazz() {  
        return clazz;  
    }  
  
}
           

執行程式,輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        AbstractFactory factory = FactoryEnum.SHAPE.getClazz().newInstance();  
        factory.getShape(ShapeEnum.RECTANGLE).draw();  
        factory.getShape(ShapeEnum.SQUARE).draw();  
        factory.getShape(ShapeEnum.CIRCLE).draw();  
    }  

}
           
java複制代碼Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Circle::draw() method.
           

抽象工廠的優點:當一個産品族中的多個對象被設計成一起工作時,它能保證用戶端始終隻使用同一個産品族中的對象。

抽象工廠的缺點:産品族擴充非常困難,要增加一個系列的某一産品,既要在抽象的 Creator 裡加代碼,又要在具體的裡面加代碼。

3. 單例模式(Singleton)

單例模式涉及到一個單一的類,該類負責建立自己的對象,同時確定隻有單個對象被建立。這個類提供了一種通路其唯一的對象的方式,可以直接通路,不需要執行個體化該類的對象。

注意:

  • 1、單例類隻能有一個執行個體。
  • 2、單例類必須自己建立自己的唯一執行個體。
  • 3、單例類必須給所有其他對象提供這一執行個體。

(1)懶漢式,線程不安全

這種方式是最基本的實作方式,這種實作最大的問題就是不支援多線程。因為沒有加鎖 synchronized,是以嚴格意義上它并不算單例模式。

這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。

java複制代碼public class Singleton1 {  
  
    private static Singleton1 instance;  

    private Singleton1() {  
    }  

    public static Singleton1 getInstance() {  
        if (instance == null) {  
            instance = new Singleton1();  
        }  
        return instance;  
    }  
  
}
           

(2)懶漢式,線程安全

這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。

優點:第一次調用才初始化,避免記憶體浪費。

缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。

getInstance() 的性能對應用程式不是很關鍵(該方法使用不太頻繁)。

java複制代碼public class Singleton2 {  
  
    private static Singleton2 instance;  

    private Singleton2() {  
    }  

    public static synchronized Singleton2 getInstance() {  
        if (instance == null) {  
            instance = new Singleton2();  
        }  
        return instance;  
    }  
  
}
           

(3)餓漢式,線程安全

這種方式比較常用,但容易産生垃圾對象。

優點:沒有加鎖,執行效率會提高。

缺點:類加載時就初始化,浪費記憶體。

它基于 classloader 機制避免了多線程的同步問題,不過,instance 在類裝載時就執行個體化,雖然導緻類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的靜态方法)導緻類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

java複制代碼public class Singleton3 {  
  
    private static Singleton3 instance = new Singleton3();  

    private Singleton3() {  
    }  

    public static Singleton3 getInstance() {  
        return instance;  
    }  
  
}
           

(4)登記式 / 靜态内部類

這種方式能達到雙檢鎖方式一樣的功效,但實作更簡單。對靜态域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式隻适用于靜态域的情況,雙檢鎖方式可在執行個體域需要延遲初始化時使用。

這種方式同樣利用了 classloader 機制來保證初始化 instance 時隻有一個線程,它跟第 3 種方式不同的是:第 3 種方式隻要 Singleton 類被裝載了,那麼 instance 就會被執行個體化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,隻有通過顯式調用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,進而執行個體化 instance。

想象一下,如果執行個體化 instance 很消耗資源,是以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就執行個體化,因為不能確定 Singleton 類還可能在其他的地方被主動使用進而被加載,那麼這個時候執行個體化 instance 顯然是不合适的。這個時候,這種方式相比第 3 種方式就顯得很合理。

java複制代碼public class Singleton5 {  
  
    private static class SingletonHolder {  

        private static final Singleton5 INSTANCE = new Singleton5();  

    }  

    private Singleton5() {  
    }  

    public static final Singleton5 getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
  
}
           

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

這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。

getInstance() 的性能對應用程式很關鍵。

java複制代碼public class Singleton4 {  
  
    private volatile static Singleton4 singleton;  

    private Singleton4() {  
    }  

    public static Singleton4 getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton4.class) {  
                if (singleton == null) {  
                    singleton = new Singleton4();  
                }  
            }  
        }  
        return singleton;  
    }  
  
}
           

4. 建造者模式(Builder)

建造者模式使用多個簡單的對象一步一步建構成一個複雜的對象。這種類型的設計模式屬于建立型模式,它提供了一種建立對象的最佳方式。 一個 Builder 類會一步一步構造最終的對象。該 Builder 類是獨立于其他對象的。

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

我們假設一個快餐店的商業案例,其中,一個典型的套餐可以是一個漢堡(Burger)和一杯冷飲(Cold drink)。

  • 漢堡(Burger)可以是素食漢堡(Veg Burger)或雞肉漢堡(Chicken Burger),它們是包在紙盒中。
  • 冷飲(Cold drink)可以是可口可樂(coke)或百事可樂(pepsi),它們是裝在瓶子中。

我們将建立一個表示食物包裝的 Packing 接口和實作 Packing 接口的實體類,漢堡是包在紙盒中,冷飲是裝在瓶子中。

java複制代碼public interface Packing {  
  
    String pack();  
  
}
           
java複制代碼public class Wrapper implements Packing {  
  
    @Override  
    public String pack() {  
        return this.getClass().getSimpleName();  
    }  
  
}
           
java複制代碼public class Bottle implements Packing {  
  
    @Override  
    public String pack() {  
        return this.getClass().getSimpleName();  
    }  
  
}
           

一個表示食物條目(比如漢堡和冷飲)的 Item 接口和實作 Item 接口的實體類。

java複制代碼public interface Item {  
  
    String name();  

    Packing packing();  

    float price();  
  
}
           

漢堡 Burger:

java複制代碼public abstract class Burger implements Item {  
  
    @Override  
    public Packing packing() {  
        return new Wrapper();  
    }  

    @Override  
    public abstract float price();  

}
           
java複制代碼public class VegBurger extends Burger {  
  
    @Override  
    public float price() {  
        return 25.0f;  
    }  

    @Override  
    public String name() {  
        return "Veg Burger";  
    }  
  
}
           
java複制代碼public class ChickenBurger extends Burger {  
  
    @Override  
    public float price() {  
        return 50.5f;  
    }  

    @Override  
    public String name() {  
        return "Chicken Burger";  
    }  
  
}
           

冷飲 ColdDrink:

java複制代碼public abstract class ColdDrink implements Item {  
  
    @Override  
    public Packing packing() {  
        return new Bottle();  
    }  

    @Override  
    public abstract float price();  
  
}
           
java複制代碼public class Coke extends ColdDrink {  
  
    @Override  
    public float price() {  
        return 30.0f;  
    }  

    @Override  
    public String name() {  
        return "Coke";  
    }  
  
}
           
java複制代碼public class Pepsi extends ColdDrink {  
  
    @Override  
    public float price() {  
        return 35.0f;  
    }  

    @Override  
    public String name() {  
        return "Pepsi";  
    }  
  
}
           

然後我們建立一個 Meal 類,帶有 Item 的 ArrayList 和一個通過結合 Item 來建立不同類型的 Meal 對象的 MealBuilder。

java複制代碼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());  
        }  
    }  
  
}
           
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;  
    }  
  
}
           

執行程式,輸出結果:

java複制代碼public class MainTest {  
  
public static void main(String[] args) throws Exception {  
    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("\nNon-Veg Meal");  
    nonVegMeal.showItems();  
    System.out.println("Total Cost: " + nonVegMeal.getCost());  
}  
  
}
           
java複制代碼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
           

建造者模式打的優點:

  1. 分離建構過程和表示,使得建構過程更加靈活,可以建構不同的表示。
  2. 可以更好地控制建構過程,隐藏具體建構細節。
  3. 代碼複用性高,可以在不同的建構過程中重複使用相同的建造者。

建造者模式打的缺點:

  1. 如果産品的屬性較少,建造者模式可能會導緻代碼備援。
  2. 建造者模式增加了系統的類和對象數量。

5. 原型模式(Prototype)

原型模式是用于建立重複的對象,同時又能保證性能。 這種模式是實作了一個原型接口,該接口用于建立目前對象的克隆。當直接建立對象的代價比較大時,則采用這種模式。

與通過對一個類進行執行個體化來構造新對象不同的是,原型模式是通過拷貝一個現有對象生成新對象的。淺拷貝實作 Cloneable,重寫,深拷貝是通過實作 Serializable 讀取二進制流。

我們将建立一個抽象類 Shape 和擴充了 Shape 類的實體類。

java複制代碼public interface Shape {  
  
    void draw();  
  
}
           
java複制代碼public class Rectangle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Rectangle::draw() method.");  
    }  
  
}
           
java複制代碼public class Square implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Square::draw() method.");  
    }  
  
}
           
java複制代碼public class Circle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Circle::draw() method.");  
    }  
  
}
           

下一步是定義類 ShapeCache,該類把 shape 對象存儲在一個 Hashtable 中,并在請求的時候傳回它們的克隆。

java複制代碼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();  
    }  

    // 對每種形狀都運作資料庫查詢,并建立該形狀  
    // 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);  
    }  
  
}
           

執行程式,輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        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());  
    }  
  
}
           
java複制代碼Shape : Circle
Shape : Square
Shape : Rectangle
           

原型模式的優點:1、性能提高。 2、逃避構造函數的限制。

原型模式的缺點:1、配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特别當一個類引用不支援串行化的間接對象,或者引用含有循環結構的時候。 2、必須實作 Cloneable 接口。

作者:白菜說技術

連結:https://juejin.cn/post/7259216643305816101

繼續閱讀