在建立物件時,有各種特殊的需求,有甚麼經典設計模式可以參考?
在設計模式中,定義了許多關於建立物件的經典設計模式,在此處並不會詳細說明如何實作這些設計模式。
主要著重於幫助讀者了解這些設計模式的用法、使用情境 以及常見套件中,如何使用這些設計模式。
作為未來設計程式時的參考。
工廠模式 (Factory Pattern) 中定義需要傳入工廠方法的參數及傳回的介面。由工廠方法依據傳入的參數以及目前環境,回傳對應類別物件。這可避免程式通過new直接建立物件,而產生對特定類別的耦合。
SLF4J套件中,固定通過LoggerFactory建立Logger物件。對使用者來說都是接到Logger進行使用。
但是實際上可能依照設定回傳:EventRecordingLogger, JDK14LoggerAdapter或是LocLogger等具體類別。
Logger logger = LoggerFactory.getLogger(LoggingExample.class);
JDBC中依據connectionUrl的設定,動態載入不同種類資料庫的Connection物件。
Connection con = DriverManager.getConnection(connectionUrl);
建造者模式 (Builder Pattern) 針對複雜的物件,使用Builder方法,依據需求一步步呼叫不同的設定方法,以彈性的建立物件。這可避免物件建立的方法過多或難以維護。
通常依照下列模式建立目標物件
在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();
}
由於String建立後,在變更字串內容會耗費大量記憶體資源。因此通過StringBuilder進行多次暫存文字資料的變更,最後才呼叫toString轉為結果字串。
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Hello").append(" World");
if (isHolidayToday()) {
stringBuilder.append("Happy Holiday!");
}
String result = stringBuilder.toString();
單例模式 (Singleton Pattern) 是確保某類別只會有一個物件被實例化。
Java核心套件中,Runtime與Desktop物件無論何時呼叫,指的都是同一個Runtime與Desktop,因此可以使用單例模式。
//java.lang.Runtime
Runtime runtime = Runtime.getRuntime();
//java.awt.Desktop
Desktop desktop = Desktop.getDesktop();
程式中的設定檔資料應該只會有一份,而且需要內容應保持一致,載入也需要較多資源。
因此是很常見的單例模式使用情境。
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);
}
// 其他設定相關方法...
}
除了經典使用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;
除了透過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的方式,在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();