
本篇是設計模式的第五篇,前四篇可見:
設計模式最佳套路1——愉快地使用策略模式
設計模式最佳套路2——愉快地使用管道模式
設計模式最佳套路3——愉快地使用代理模式
設計模式最佳套路4——愉快地使用模板模式
什么是工廠方法模式
工廠方法模式(Factory Method Pattern)也被稱為多型工廠模式,其定義了一個創建某種產品的介面,但由子類決定要實體化的產品是哪一個,從而把產品的實體化推遲到子類,

何時使用工廠方法模式
工廠模式一般配合策略模式一起使用,當系統中有多種產品(策略),且每種產品有多個實體時,此時適合使用工廠模式:每種產品對應的工廠提供該產品不同實體的創建功能,從而避免呼叫方和產品創建邏輯的耦合,完美符合迪米特法則(最少知道原則),

愉快地使用工廠方法模式
? 背景
在平常開發中,我們經常會在 Spring 中實作諸如這樣的功能:收集某一類具有共同特征的 Bean(都實作了某個介面或者都打上了某個注解等),然后放入容器中(一般是 Map),使用的時候根據 Bean 的標識,來獲取到對應的 Bean,比如我之前文章中的 通過表單標識獲得表單對應提交處理器的 FormDataHandlerFactory:
@Component
public class FormDataHandlerFactory {
private static final
Map<String, FormDataHandler> FORM_DATA_HANDLER_MAP = new HashMap<>(16);
/**
* 根據表單標識,獲取對應的 Handler
*
* @param formCode 表單標識
* @return 表單對應的 Handler
*/
public FormDataHandler getHandler(String formCode) {
return FORM_DATA_HANDLER_MAP.get(formCode);
}
@Autowired
public void setFormDataHandlers(List<FormDataHandler> handlers) {
for (FormDataHandler handler : handlers) {
FORM_DATA_HANDLER_MAP.put(handler.getFormCode(), handler);
}
}
}
通過表單項型別獲得表單項轉換器的 FormItemConverterFactory:
@Component
public class FormItemConverterFactory {
private static final
EnumMap<FormItemTypeEnum, FormItemConverter> CONVERTER_MAP = new EnumMap<>(FormItemTypeEnum.class);
/**
* 根據表單項型別獲得對應的轉換器
*
* @param type 表單項型別
* @return 表單項轉換器
*/
public FormItemConverter getConverter(FormItemTypeEnum type) {
return CONVERTER_MAP.get(type);
}
@Autowired
public void setConverters(List<FormItemConverter> converters) {
for (final FormItemConverter converter : converters) {
CONVERTER_MAP.put(converter.getType(), converter);
}
}
}
在我見過的系統中,看到過非常多類似的代碼,每次需要這樣的功能,就是定義一個新的 XxxFactory,甚至還有直接在呼叫者里面直接寫上這些獲取對應 Bean 的代碼,直接違反 單一原則,在這個時候,其實我們已經趨近于使用工廠方法模式,我們更傾向于稱這種 XxxFactory 為簡單工廠,不停地使用這種簡單工廠的問題在于會導致 重復的代碼,因而也就自然而然的違背了 DRY 原則(Don't Repeat Yourself),雖然重復的代碼并不多,但是對于我們 Programmer 來說,寫重復的代碼無異于往我們臉上吐唾沫 —— 是可忍,孰不可忍!

所以接下來基于上面這個場景,我分享一下我目前基于 Spring 實作工廠方法模式的 “最佳套路”(如果你有更好的套路,歡迎賜教和討論哦)~
? 方案
其實設計模式的核心就在于,找出變化的部分,然后對變化進行抽象和封裝,從而使得代碼能夠滿足面向物件的基本原則,對于工廠方法模式來說,變化的是產品、工廠,因而我們可以先定義出抽象的產品和抽象的工廠,
抽象的產品(策略):
public interface Strategy<T> {
/**
* 獲得策略的標識
*/
T getId();
}
每個產品必須實作 Strategy 介面,代表每個產品必須有一個唯一的標識,
抽象的策略工廠:
public abstract class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
private Map<T, S> strategyMap;
private ApplicationContext appContext;
/**
* 根據策略 id 獲得對應的策略的 Bean
*
* @param id 策略 id
* @return 策略的 Bean
*/
public S getStrategy(T id) {
return strategyMap.get(id);
}
/**
* 獲取策略的型別(交給子類去實作)
*
* @return 策略的型別
*/
protected abstract Class<S> getStrategyType();
@Override
public void afterPropertiesSet() {
// 獲取 Spring 容器中,所有 S 型別的 Bean
Collection<S> strategies = appContext.getBeansOfType(getStrategyType()).values();
strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());
// 將所有 S 型別的 Bean 放入到 strategyMap 中
for (final S strategy : strategies) {
T id = strategy.getId();
strategyMap.put(id, strategy);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
Spring 容器在啟動的時候,會去掃描工廠指定的型別(Class<S>)的 Bean,并將其注冊到工廠中(加入到 strategyMap),所以對于工廠中產品的生產程序,借助 Spring,我們躺好就行,

接下來基于我們的抽象產品和抽象工廠,我們重構上面的兩個 Factory:
通過表單標識獲得表單對應提交處理器的 FormDataHandlerFactory
@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {
@Override
protected Class<FormDataHandler> getStrategyType() {
return FormDataHandler.class;
}
}
FormDataHandlerFactory 只需要指定一下其產品型別為 FormDataHandler,當然,FormDataHandler 我們也需要改造一下:
public interface FormDataHandler extends Strategy<String> {
@Override
default String getId() { return getFormCode(); }
String getFormCode();
CommonResponse<Object> submit(FormSubmitRequest request);
}
通過表單項型別獲得表單項轉換器的 FormItemConverterFactory
@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {
@Override
protected Class<FormItemConverter> getStrategyType() {
return FormItemConverter.class;
}
}
此時,FormItemConverterFactory 也只需要指定一下產品的型別,不再會寫重復代碼,同理,需要改造一下 FormItemConverter:
public interface FormItemConverter extends Strategy<FormItemTypeEnum> {
@Override
default FormItemTypeEnum getId() { return getType(); }
FormItemTypeEnum getType();
FormItem convert(FormItemConfig config);
}

如果這個時候新加一個 通過串列標識獲得串列資料拉取器的 ListDataFetcherFactory,那么首先定義出獲取串列資料的介面(產品):
public interface ListDataFetcher extends Strategy<String> {
CommonResponse<JSONObject> fetchData(ListDataFetchRequest request);
}
然后再實作 ListDataFetcherFactory(工廠):
@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {
@Override
protected Class<ListDataFetcher> getStrategyType() {
return ListDataFetcher.class;
}
}
通過抽象產品 Strategy 和抽象工廠 StrategyFactory,我們的代碼完美符合了 DRY 原則,

優化
? 借助反射
借助反射,我們還可以使得工廠代碼變得更加簡單:因為如果父類包含泛型引數,且子類對泛型引數進行了具體化,那么這個具體化的泛型型別,可在運行時獲取到,基于這個特性,我們可以改造 StrategyFactory:
public abstract class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
...
/**
* 通過反射獲取策略的型別
*
* @return 策略的型別
*/
protected Class<S> getStrategyType() {
// getClass 獲取當前運行時實體的類,getGenericSuperclass 獲得泛型父類
Type superclass = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) superclass;
Type[] actualTypeArguments = pt.getActualTypeArguments();
// 獲得索引為 1 的實際引數型別,即第二個實際引數的型別
Type actualTypeArgument = actualTypeArguments[1];
@SuppressWarnings("unchecked")
Class<S> result = (Class<S>) actualTypeArgument;
return result;
}
...
}
那么上面三個 Factory 寫起來就更簡單了:
@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {}
@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {}
@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {}
? 組合優先于繼承
上述的方案是通過繼承,并借助泛型的反射功能,由子類來指定策略( S getStrategyType)的類型,如果工廠型別較多,那么每次新加一個工廠類,容易導致 “類爆炸”,對于上述的方案,變化的部分就是策略的型別,除了繼承,我們還可以通過組合來解決這個變化,修改我們的 StrategyFactory:
public class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
private final Class<S> strategyType;
private Map<T, S> strategyMap;
private ApplicationContext appContext;
/**
* 創建一個策略工廠
*
* @param strategyType 策略的型別
*/
public StrategyFactory(Class<S> strategyType) {
this.strategyType = strategyType;
}
/**
* 根據策略 id 獲得對應的策略的 Bean
*
* @param id 策略 id
* @return 策略的 Bean
*/
public S getStrategy(T id) {
return strategyMap.get(id);
}
@Override
public void afterPropertiesSet() {
// 獲取 Spring 容器中,所有 S 型別的 Bean
Collection<S> strategies = appContext.getBeansOfType(strategyType).values();
strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());
// 將 所有 S 型別的 Bean 放入到 strategyMap 中
for (final S strategy : strategies) {
T id = strategy.getId();
strategyMap.put(id, strategy);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
此時 StrategyFactory 不再是抽象類,并且為 StrategyFactory 引入一個新的屬性 strategyType,并且在構造 StrategyFactory 就必須設定當前工廠中的策略(產品)型別,那么對于 FormDataHandlerFactory、FormItemConverterFactory 和 ListDataFetcherFactory,我們不需要再通過繼承產生,直接通過配置進行組合即可:
@Configuration
public class FactoryConfig {
@Bean
public StrategyFactory<String, FormDataHandler> formDataHandlerFactory() {
return new StrategyFactory<>(FormDataHandler.class);
}
@Bean
public StrategyFactory<FormItemTypeEnum, FormItemConverter> formItemConverterFactory() {
return new StrategyFactory<>(FormItemConverter.class);
}
@Bean
public StrategyFactory<String, ListDataFetcher> listDataFetcherFactory() {
return new StrategyFactory<>(ListDataFetcher.class);
}
}

全域營銷團隊
戰斗在阿里電商的核心地帶,負責連接供需兩端,支持電商營銷領域的各類產品、平臺和解決方案,其中包括聚劃算、百億補貼、天貓U先、天貓小黑盒、天貓新品范訓、品牌號等重量級業務,我們深度參與雙11、618、99劃算節等年度大促,不斷挑戰技術的極限!我們致力于打造幸福感極強的技術團隊,有深耕電商精研技術的老司機,也有朝氣蓬勃的小萌新,更有可顏可甜的小姐姐,期待具有好奇心和思考力的你的加入!
? 拓展閱讀




作者|之葉
編輯|橙子君
出品|阿里巴巴新零售淘系技術


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290069.html
標籤:java
上一篇:2021-07-24
