1. 程式人生 > >設計模式:如何優雅地使用工廠模式

設計模式:如何優雅地使用工廠模式

本文從是什麼、為什麼、怎麼做的三個步驟,分析簡單工廠模式、工廠方法模式和抽象工廠模式,通過框架原始碼學習如何優雅地使用工廠模式。 # 一、什麼是工廠模式? 關於什麼是工廠模式這個問題呢?其實完全可以見名知意,工廠是做什麼的?工廠是生產產品的地方啊,那麼對映到程式設計領域,工廠模式不就是生產物件的一種模式嘛?借用百度百科的一句話來說:工廠模式是我們最常用的例項化物件模式了,是用工廠方法代替new操作的一種模式。 工廠模式,按照業務實際場景可以劃分為3中不同的實現方式,分別是簡單工廠模式、工廠方法模式和抽象方法模式。 簡單工廠模式又叫靜態工廠方法模式,簡單來說就是,該模式有**一個具體的工廠類**,可以生產許多不同的產品。有一件有意思的事情,簡單工廠模式其實不是一種設計模式(不屬於GoF23種設計模式),更像是一種程式設計等習慣,但是由於該習慣經常被使用,許多開發人員都把它稱為”工廠模式“,那麼就”將錯就錯“被大家所接受咯。 在簡單工廠模式中,生產產品是由一個單一的、具體的工廠類來實現的;在工廠方法模式中,不再採用這種方式,而是由**工廠類的子類**實現具體產品的生產工作。當增加一類產品的時候,只需要增加一個相應的工廠類的子類。這樣做的好處是可以解決簡單工廠模式由於生產產品種類過多而引發的程式碼臃腫的問題。舉個例子,需要建立3種類型的產品,簡單工廠模式需要3個分支(不管是if-else也好,switch-case也罷),工廠方法模式需要3個子類;那麼產品型別有10種呢?當生產的產品種類較多的時候,使用工廠方法模式可以解決簡單工廠方法模式大量程式碼重複的問題,這也就是工廠方法模式存在的意義。 抽象工廠模式是這麼定義的:抽象工廠模式提供一個介面,用於建立相關或依賴物件的家族,而不需要明確執行具體類。抽象工廠允許客戶使用抽象的介面來建立一組相關的產品,而不需要關係實際產出的具體產品是什麼。 # 二、為什麼使用工廠模式? ”為什麼要使用工廠模式“,關於這個問題我是這麼理解的: 第一,為什麼要使用設計模式?逼格,對,設計模式就是逼格的體現。除了逼格,說的實際點,就是可以使程式碼變得清晰和優雅,讓新人接手程式碼的時候在心裡少罵我們幾句。 第二,主要的目的還是解耦。如果沒有簡單工廠模式,我們需要關心生產邏輯(建立過程)和呼叫,可以使用一坨程式碼去實現,此時這二者是沒有分開的;當使用了簡單工廠模式,具體的生產邏輯放在了簡單工廠裡面,只需要呼叫即可;當品類增多,簡單工廠的工廠類的邏輯變得複雜,耦合變得嚴重,出現工廠方法模式,將不同品類的生產邏輯剝離到子類中進行,讓外部只是知道如何呼叫即可,實現解耦....其實從不使用設計模式--> 簡單工廠模式--> 工廠方法模式-->抽象工廠模式,這就是層次遞進的解耦關係。那為什麼需要使用工廠模式呢,我想本質也就在這裡吧。 # 三、如何優雅地使用工廠模式? 關於如何使用工廠模式?不寫之前的demo了,來看下優秀的框架中是如何使用工廠模式的。 ## 3.1 簡單工廠模式 簡單工廠模式java.util.Calendar類中的運用: ``` private static Calendar createCalendar(TimeZone zone, Locale aLocale) { CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale) .getCalendarProvider(); if (provider != null) { try { return provider.getInstance(zone, aLocale); } catch (IllegalArgumentException iae) { // fall back to the default instantiation } } Calendar cal = null; if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { // If no known calendar type is explicitly specified, // perform the traditional way to create a Calendar: // create a BuddhistCalendar for th_TH locale, // a JapaneseImperialCalendar for ja_JP_JP locale, or // a GregorianCalendar for any other locales. // NOTE: The language, country and variant strings are interned. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal; } ``` ## 3.2 工廠方法模式 Logback原始碼中的工廠方法模式的使用: ``` public static Logger getLogger(String name){ ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } public static Logger getLogger(Class clazz){ return getLogger(clazz.getName()); } ``` ## 3.3 抽象方法模式 在Spring原始碼中,所有工廠都是BeanFactory的子類。通過對BeanFactory的實現,我們可以從Spring的容器訪問Bean。根據不同的策略呼叫getBean()方法,從而獲得具體物件。 ``` public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String var1) throws BeansException; T getBean(String var1, Class var2) throws BeansException; Object getBean(String var1, Object... var2) throws BeansException; T getBean(Class var1) throws BeansException; T getBean(Class var1, Object... var2) throws BeansException; ObjectProvider getBeanProvider(Class var1); ObjectProvider getBeanProvider(ResolvableType var1); boolean containsBean(String var1); boolean isSingleton(String var1) throws NoSuchBeanDefinitionException; boolean isPrototype(String var1) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, Class var2) throws NoSuchBeanDefinitionException; @Nullable Class getType(String var1) throws NoSuchBeanDefinitionException; @Nullable Class getType(String var1, boolean var2) throws NoSuchBeanDefinitionException; String[] getAliases(String var1)