<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
工廠設計模式可能是最常用的設計模式之一,我想大家在自己的專案中都用到過。可能你會不屑一顧,但這篇文章不僅僅是關於工廠模式的基本知識,更是討論如何在執行時動態選擇不同的方法進行執行,你們可以看看是不是和你們專案中用的一樣?
直接上例子說明,設計一個紀錄檔記錄的功能,但是支援記錄到不同的地方,例如:
面對這麼一個需求,你會怎麼做呢?我們先來看看小菜鳥的做法吧。
小菜鳥建立了一個Logger
類
class Logger { public void log(String message, String loggerMedium) {} }
小菜鳥想都不想,直接一通if else
。
class Logger { public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { logInMemory(message); } else if (loggerMedium.equals("FILE")) { logOnFile(message); } else if (loggerMedium.equals("DB")) { logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { logToRemote(message); } } private void logInMemory(String message) { // Implementation } private void logOnFile(String message) { // Implementation } private void logToDB(String message) { // Implementation } private void logToRemote(String message) { // Implementation } }
現在突然說要增加一種儲存媒介FLASH_DRIVE
,就要改了這個類?不拍改錯嗎?也不符合“開閉原則”,而且隨著儲存媒介變多,類也會變的很大,小菜鳥懵逼了,不知道怎麼辦?
這時候小菜鳥去找你幫忙,你一頓操作,改成了下面這樣:
class InMemoryLog { public void logToMemory(String message) { // Implementation } } class FileLog { public void logToFile(String message) { //Implementation } } class DBLog { public void logToDB(String message) { // Implementation } } class RemoteServiceLog { public void logToService(String message) { // Implementation } } class Logger { private InMemoryLog mLog; private FileLog fLog; private DBLog dbLog; private RemoteServiceLog sLog; public Logger() { mLog = new InMemoryLog(); fLog = new FileLog(); dbLog = new DBLog(); sLog = new RemoteServiceLog(); } public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { mLog.logToMemory(message); } else if (loggerMedium.equals("FILE")) { fLog.logToFile(message); } else if (loggerMedium.equals("DB")) { dbLog.logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { sLog.logToService(message); } } }
在這個實現中,你已經將單獨的程式碼分離到它們對應的檔案中,但是Logger
類與儲存媒介的具體實現緊密耦合,如FileLog
、DBLog
等。隨著儲存媒介的增加,類中將引入更多的範例Logger
。
你想了想,上面的實現都是直接寫具體的實現類,是面向實現程式設計,更合理的做法是面向介面程式設計,介面意味著協定,契約,是一種更加穩定的方式。
定義一個紀錄檔操作的介面
public interface LoggingOperation { void log(String message); }
實現這個介面
class InMemoryLog implements LoggingOperation { public void log(String message) { // Implementation } } class FileLog implements LoggingOperation { public void log(String message) { //Implementation } } class DBLog implements LoggingOperation { public void log(String message) { // Implementation } } class RemoteServiceLog implements LoggingOperation { public void log(String message) { // Implementation } }
你定義了一個類,據傳遞的引數,在執行時動態選擇具體實現,這就是所謂的工廠類,不過是基礎版。
class LoggerFactory { public static LoggingOperation getInstance(String loggerMedium) { LoggingOperation op = null; switch (loggerMedium) { case "MEMORY": op = new InMemoryLog(); break; case "FILE": op = new FileLog(); break; case "DB": op = new DBLog(); break; case "REMOTE_SERVICE": op = new RemoteServiceLog(); break; } return op; } }
現在你的 Logger
類的實現就是下面這個樣子了。
class Logger { public void log(String message, String loggerMedium) { LoggingOperation instance = LoggerFactory.getInstance(loggerMedium); instance.log(message); } }
這裡的程式碼變得非常統一,建立實際儲存範例的責任已經轉移到LoggerFactory
,各個儲存類只實現它們如何將訊息記錄到它們的特定媒介,最後該類Logger
只關心通過LoggerFactory
將實際的紀錄檔記錄委託給具體的實現。這樣,程式碼就很鬆耦合了。你想要新增一個新的儲存媒介,例如FLASH_DRIVE
,只需建立一個實現LoggingOperation
介面的新類並將其註冊到LoggerFactory
中就好了。這就是工廠模式可以幫助您動態選擇實現的方式。
你已經完成了一個鬆耦合的設計,但是想象一下假如有數百個儲存媒介的場景,所以我們最終會在工廠類LoggerFactory
中的switch case
部分case
數百個。這看起來還是很糟糕,如果管理不當,它有可能成為技術債務,這該怎麼辦呢?
擺脫不斷增長的if else
或者 switch case
的一種方法是維護類中所有實現類的列表,LoggerFactory
程式碼如下所示:
class LoggerFactory { private static final List<LoggingOperation> instances = new ArrayList<>(); static { instances.addAll(Arrays.asList( new InMemoryLog(), new FileLog(), new DBLog(), new RemoteServiceLog() )); } public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) { for(LoggingOperation op : instances) { // 比如判斷StrUtil.equals(loggerMedium, op.getType()) op本身新增一個type } return null; } }
但是請注意,還不夠,在所有上述實現中,無論if else、switch case
還是上面的做法,都是讓儲存實現與LoggerFactory
緊密耦合的。你新增一種實現,就要修改LoggerFactory
,有什麼更好的做法嗎?
逆向思維一下,我們是不是讓具體的實現主動註冊上來呢?通過這種方式,工廠不需要知道系統中有哪些範例可用,而是範例本身會註冊並且如果它們在系統中可用,工廠就會為它們提供服務。具體程式碼如下:
class LoggerFactory { private static final Map<String, LoggingOperation> instances = new HashMap<>(); public static void register(String loggerMedium, LoggingOperation instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(String loggerMedium) { if (instances.containsKey(loggerMedium)) { return instances.get(loggerMedium); } return null; } }
在這裡,LoggerFactory
提供了一個register
註冊的方法,具體的儲存實現可以呼叫該方法註冊上來,儲存在工廠的instances
map物件中。
我們來看看具體的儲存實現註冊的程式碼如下:
class RemoteServiceLog implements LoggingOperation { static { LoggerFactory.register("REMOTE", new RemoteServiceLog()); } public void log(String message) { // Implementation } }
由於註冊應該只發生一次,所以它發生在static
類載入器載入儲存類時的塊中。
但是又有一個問題,預設情況下JVM不載入類RemoteServiceLog
,除非它由應用程式在外部範例化或呼叫。因此,儘管儲存類有註冊的程式碼,但實際上註冊並不會發生,因為沒有被JVM載入,不會呼叫static程式碼塊中的程式碼, 你又犯難了。
你靈機一動,LoggerFactory
是獲取儲存範例的入口點,能否在這個類上做點文章,就寫下了下面的程式碼:
class LoggerFactory { private static final Map<String, LoggingOperation> instances = new HashMap<>(); static { try { loadClasses(LoggerFactory.class.getClassLoader(), "com.alvin.storage.impl"); } catch (Exception e) { // log or throw exception. } } public static void register(String loggerMedium, LoggingOperation instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(String loggerMedium) { if (instances.containsKey(loggerMedium)) { return instances.get(loggerMedium); } return null; } private static void loadClasses(ClassLoader cl, String packagePath) throws Exception { String dottedPackage = packagePath.replaceAll("[/]", "."); URL upackage = cl.getResource(packagePath); URLConnection conn = upackage.openConnection(); String rr = IOUtils.toString(conn.getInputStream(), "UTF-8"); if (rr != null) { String[] paths = rr.split("n"); for (String p : paths) { if (p.endsWith(".class")) { Class.forName(dottedPackage + "." + p.substring(0, p.lastIndexOf('.'))); } } } } }
在上面的實現中,你使用了一個名為loadClasses
的方法,該方法掃描提供的包名稱com.alvin.storage.impl
並將駐留在該目錄中的所有類載入到類載入器。以這種方式,當類載入時,它們的static
塊被初始化並且它們將自己註冊到LoggerFactory
中。
你突然發現你的應用是springboot應用,突然想到有更方便的解決方案。
因為你的儲存實現類都被標記上註解@Component
,這樣 Spring
會在應用程式啟動時自動載入類,它們會自行註冊,在這種情況下你不需要使用loadClasses
功能,Spring
會負責載入類。具體的程式碼實現如下:
class LoggerFactory { private static final Map<String, Class<? extends LoggingOperation>> instances = new HashMap<>(); public static void register(String loggerMedium, Class<? extends LoggingOperation> instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) { if (instances.containsKey(loggerMedium)) { return context.getBean(instances.get(loggerMedium)); } return null; } }
getInstance
需要傳入ApplicationContext
物件,這樣就可以根據型別獲取具體的實現了。
修改所有儲存實現類,如下所示:
import org.springframework.stereotype.Component; @Component class RemoteServiceLog implements LoggingOperation { static { LoggerFactory.register("REMOTE", RemoteServiceLog.class); } public void log(String message) { // Implementation } }
我們通過一個例子,不斷迭代帶大家理解了工廠模式,工廠模式是一種建立型設計模式,用於建立同一型別的不同實現物件。我們來總結下這種動態選擇物件工廠模式的優缺點。
優點:
缺點:
static
塊,註冊自己到工廠中,一不小心就遺漏了。到此這篇關於Java工廠模式用法之如何動態選擇物件詳解的文章就介紹到這了,更多相關Java工廠模式動態選擇物件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45