iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Software Development

軟體架構備忘錄系列 第 14

Day 14 程式架構 - 創建型設計模式 (知識點072~075)

  • 分享至 

  • xImage
  •  

思考的問題

在建立物件時,有各種特殊的需求,有甚麼經典設計模式可以參考?

在設計模式中,定義了許多關於建立物件的經典設計模式,在此處並不會詳細說明如何實作這些設計模式。
主要著重於幫助讀者了解這些設計模式的用法、使用情境 以及常見套件中,如何使用這些設計模式。
作為未來設計程式時的參考。


工廠模式

描述

工廠模式 (Factory Pattern) 中定義需要傳入工廠方法的參數及傳回的介面。由工廠方法依據傳入的參數以及目前環境,回傳對應類別物件。這可避免程式通過new直接建立物件,而產生對特定類別的耦合。

使用情境

  • 避免耦合:某底層元件回傳的物件可能有多種格式,因此希望避免程式與特定類別耦合。
  • 簡化物件建立:如果物件建立需要多個複雜步驟,可使用工廠模式增加程式可讀性。

案例1

SLF4J套件中,固定通過LoggerFactory建立Logger物件。對使用者來說都是接到Logger進行使用。

但是實際上可能依照設定回傳:EventRecordingLogger, JDK14LoggerAdapter或是LocLogger等具體類別。

Logger logger = LoggerFactory.getLogger(LoggingExample.class);

案例2

JDBC中依據connectionUrl的設定,動態載入不同種類資料庫的Connection物件。

Connection con = DriverManager.getConnection(connectionUrl);

建造者模式

描述

建造者模式 (Builder Pattern) 針對複雜的物件,使用Builder方法,依據需求一步步呼叫不同的設定方法,以彈性的建立物件。這可避免物件建立的方法過多或難以維護。

通常依照下列模式建立目標物件

  1. 使用Builder方法建立暫時物件,
  2. 通過一系列的方法接續修改此暫時物件,每個修改方法都回傳同一個暫時物件,以達到方法的鏈接 (method chaining)
  3. 當設定完成後,才通過build方法,建立真正的目標物件。

使用情境

  • 建立物件的參數彈性:有多個傳入值,而且每個傳入值都可以自由選擇是否傳入。
  • 優化複雜物件建立流程:若建立物件的變化過多,使用Builder Pattern可讓物件的建立更有彈性也有更好的閱讀性。
  • 建立不可變物件:如果物件建立後,希望避免此物件內容變更。可使用Builder Pattern建立物件,並封裝其他修改物件的方法,避免外部更改物件內容。

案例1

在Spring Security中建立資安設定物件的可能變化過多。由於可能參數過多,不適合使用建構子。

如果使用傳統多個set的模式進行設定,可能會讓程式難以閱讀與維護。

public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((requests) -> requests
				.requestMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
			)
			.formLogin((form) -> form
				.loginPage("/login")
				.permitAll()
			)
			.logout((logout) -> logout.permitAll());

		return http.build();
	}

案例2

由於String建立後,在變更字串內容會耗費大量記憶體資源。因此通過StringBuilder進行多次暫存文字資料的變更,最後才呼叫toString轉為結果字串。

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Hello").append(" World");

if (isHolidayToday()) {
    stringBuilder.append("Happy Holiday!");
}

String result = stringBuilder.toString();

單例模式

描述

單例模式 (Singleton Pattern) 是確保某類別只會有一個物件被實例化。

使用情境

  • 物件建立費時:此物件建立需要較高成本,例如需讀取資料才能建立。
  • 各實例的內容皆一致:此物件的實例不會有狀態的變化,或狀態變化應該同步於所有使用程式。

案例1

Java核心套件中,Runtime與Desktop物件無論何時呼叫,指的都是同一個Runtime與Desktop,因此可以使用單例模式。

//java.lang.Runtime
Runtime runtime = Runtime.getRuntime();

//java.awt.Desktop
Desktop desktop = Desktop.getDesktop();

案例2

程式中的設定檔資料應該只會有一份,而且需要內容應保持一致,載入也需要較多資源。

因此是很常見的單例模式使用情境。

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class AppConfig {
    // 單例實例
    private static AppConfig instance;

    // 儲存設定的 Properties 物件
    private Properties properties;

    // 私有建構子,防止外部實例化
    private AppConfig() {
        properties = new Properties();
        loadConfigFromFile("app.properties");
    }

    // 全域訪問點,用於獲取唯一的實例
    public static synchronized AppConfig getInstance() {
        if (instance == null) {
            instance = new AppConfig();
        }
        return instance;
    }

    // 從檔案載入設定檔內容
    private void loadConfigFromFile(String filename) {
        try (FileInputStream fileInputStream = new FileInputStream(filename)) {
            properties.load(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 獲取設定檔中的設定值
    public String getSetting(String key) {
        return properties.getProperty(key);
    }

    // 其他設定相關方法...
}

案例 3:容器管理中的Singleton

除了經典使用static物件的singleton實作方式之外,其實也有很多種形式的Singleton。

例如:透過套件容器管理機制

下方Spring boots的範例中,就是一個常見例子

假設application.properties中有下方設定

myapp.name=My App
myapp.version=1.0

可通過Configuation物件,讓Spring Boots自動進行初始化與管理

@Configuration
@ConfigurationProperties(prefix = "myapp") // 指定配置项的前缀
public class AppConfig {
    private String name;
    private String version;

    public String getName() {
        return name;
    }

    public String getVersion() {
        return version;
    }
}

後續透過Autowired,讓Spring Boots自動進行物件管理。

@Autowired
private AppConfig appConfig;

案例4:執行序中的Singleton

除了透過Static達到全執行環境中的Singleton,也可以透過ThreadLocal達到執行序中唯一的Singleton

這主要使用在Web APP中,由於每個請求是由獨立的執行序進行處理,因此可使用ThreadLocal建立該請求的獨立物件。以用來存放使用者的資料庫連線、操作狀態等資料。

public class ThreadLocalSingleton {

    // 使用 ThreadLocal 來儲存單例實例
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            ThreadLocal.withInitial(ThreadLocalSingleton::new);

    // 私有建構函式,防止外部直接實例化
    private ThreadLocalSingleton() {
        // 進行初始化操作
    }

    // 獲取執行緒本地單例
    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

原型模式

描述

原型模式 (Prototype pattern) 先建立一個原型物件並備存此物件。後續不再new新的物件。而是透過clone原型的方式,取得新的物件的實例。

使用情境

  • 物件建立費時:如果需要讀取設定資料、網路資料、資料庫資料等費時動作才能建立物件。
  • 個別狀態差異:由於物件使用時,狀態可能會不一樣,因此不適合使用Singleton

案例

可通過類似singleton的方式,在copy時判斷原型物件如果不存在才會建立原型物件。

並私有化建構子,只能通過copy來建立物件。

public class PrototypeExample implement Cloneable {
    private static PrototypeExample prototpye = null;
    private String data;

    // 私有建構子
    private PrototypeSingleton( {
        //費時的物件建立步驟
    }
   
	// 靜態方法以建立物件的複本並返回現有實例
    public static PrototypeExample copy() {
        if (prototype == null) {
            prototype = new PrototypeExample();
        }
        try {
            return (PrototypeExample) prototype.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // 需要修改clone的細節
    }
}

//一律都使用copy來建立物件
PrototypeExample inst1 = PrototypeExample.copy();
PrototypeExample inst2 = PrototypeExample.copy();

上一篇
Day 13 程式架構 - SOLID原則 (知識點067~071)
下一篇
Day 15 程式架構 - 結構型設計模式 (知識點076~079)
系列文
軟體架構備忘錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言