最近完成了使用阿里雲提供的短信服務,完成了短信推送。代碼中用到了一個工廠類,其實使用的並不是傳統意義上的那三種工廠模式,更大程度上是使用了工廠模式的思想。
先談用到的工廠模式的思想
一、問題場景
系統需要支持多種短信通道(阿里雲、騰訊雲等),根據application.yml的配置選擇使用不同的服務。
- 開發時,使用本地local避免真實發送
- 生產時,切換為ali
- 未來可以輕鬆切換其他廠商
二、實現方式(核心思想)
借鑑工廠模式的思想,將“對象選擇邏輯”集中管理,業務代碼只依賴接口。
具體做法:
- 所有的短信服務都是ShortMessageSerivce的實現類
- 工廠啓動時自動收集所有類,按type構建映射表
- 通過配置文件指定默認type
關鍵代碼
1、統一接口
public interface ShortMessageService {
/**
* 返回唯一標識,如 local = 0, ali = 1
*/
Short getType();
/**
* @param data 短信模板需要的變量
* @param phoneNumber 手機號
*/
void sendMessage(ShortMessageDto.TeacherScheduleInfo data, String phoneNumber);
}
2、核心工廠類
@Component
public class ShortMessageServiceFactory {
private final Short smsTypeValue;
private final Map<Short, ShortMessageService> serviceMap;
public ShortMessageServiceFactory(ShortMessageProperties shortMessageProperties,
List<ShortMessageService> shortMessageServices) {
this.serviceMap = shortMessageServices.stream()
.collect(Collectors.toConcurrentMap(
ShortMessageService::getType, s -> s));
// 根據短信屬性的配置,獲取到application.yml文件中默認的type對應的code
this.smsTypeValue = shortMessageProperties.getCode();
}
/**
* 根據配置文件獲取默認類型的service
*/
public ShortMessageService getDefaultService() {
return serviceMap.get(this.smsTypeValue);
}
public ShortMessageService getService(short type) {
ShortMessageService service = serviceMap.get(type);
if (service == null) {
throw new RuntimeException("不支持的短信類型:" + type);
}
return service;
}
}
3、業務代碼
ShortMessageService shortMessageService = shortMessageServiceFactory.getDefaultService();
三、為什麼要用工廠模式?
使用了工廠模式,有以下幾點好處
- 如果想要新增短信渠道,使用其他廠商提供的服務,比如想要添加騰訊雲,只需要新增一個TencentSmsServiceImpl。符合開閉原則:對擴展開放,對修改關閉。
- 業務代碼解耦,Controller或Service只依賴ShortMessageSerivce接口:
ShortMessageService service = factory.getDefaultService();
service.send(...);
四、不使用工廠模式會怎麼樣?
-
硬編碼if-else
1、每次新增廠商,必須修改這個方法 -> 違反開閉原則
2、無法通過修改配置實現切換廠商public void sendSms(String phone, String msg, String type) { if ("ali".equals(type)) { new AliyunSmsService().send(phone, msg); } else if ("tencent".equals(type)) { new TencentSmsService().send(phone, msg); } else if ("huawei".equals(type)) { new HuaweiSmsService().send(phone, msg); } // 每加一個廠商,這裏就要改! } -
直接在業務代碼中new
1、無法統一管理,不同的業務代碼中使用不同實現,行為不一致。
2、強耦合,如果想要修改廠商,需要修改很多地方。// 實際發送短信的業務代碼中 AliSmsService smsService = new AliyunSmsService(); smsService.send(...);
傳統的工廠模式
上面的場景只是使用了工廠模式的思想,下面簡單介紹一下傳統的工廠模式——簡單工廠模式、工廠方法、抽象工廠模式。看了實驗室的《Head First 設計模式》這本書,寫的超級好,通俗易懂,所以在這裏借用書裏面的例子,簡單交流一下思想。
一、前言
假設你是一家比薩店的老闆,剛開始只有一種比薩,顧客來了,new一個Pizza
代碼如下:
public Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza
}
那如果想要增加其他風味的比薩,怎麼增加呢?直接修改原代碼,再去增加if else判斷嗎,每要增加一種比薩,就要修改一次代碼,違反開閉原則;new 很多對象,難以管理。
二、簡單工廠模式
這個時候就需要使用到簡單工廠模式了。不自己 new 比薩,而是創建一個簡單的比薩工廠,如果想要增加其他風味的比薩,修改這個工廠就可以了。
代碼如下:
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
可能會説,這跟原來的修改方式不是沒什麼區別嗎?對的,這種方式仍然違背了開閉原則。但是SimplePizzaFactory工廠可以有很多客户,不僅僅有orderPizza,還有PizzaMenu(比薩菜單)等等,這樣將創建比薩的代碼包裝成一個類,當需要修改時,只需要修改工廠的代碼就可以了。
三、工廠方法模式
隨着比薩店越做越大,有了很多加盟店。每家加盟店都想要提供不同風味的比薩(比如紐約、芝加哥、加州),那麼比薩的製作就受到了地區的影響,不同的地區製作方法不同。
代碼如下:
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type); // 工廠方法
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// 工廠方法 —— 由子類實現抽象的方法
protected abstract Pizza createPizza(String type);
}
做到了真正的“對擴展開放”——加新分店只需要繼承,實現對應的抽象方法。
四、抽象工廠模式
隨着比薩店越開越大,怎麼保證每家加盟店使用的都是高品質的原料,來保證比薩的質量呢?但是對於不同地區不同的比薩加盟店,要用到的原料也不相同。
需要引入原料工廠,不同地區的加盟店通過實現原料工廠製作原料。
原料工廠代碼如下:
public interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
Veggies[] createVeggies();
Pepperoni createPepperoni();
Clams createClam();
}
紐約原料工廠:
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
}
製作比薩:
public abstract class Pizza {
protected Dough dough;
protected Sauce sauce;
protected Cheese cheese;
public abstract void prepare();
// 其他方法 bake(), cut(), box()
}
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
this.dough = ingredientFactory.createDough();
this.sauce = ingredientFactory.createSauce();
this.cheese = ingredientFactory.createCheese();
}
}
抽象工廠解決的不是單個產品怎麼變,而是構建了整個產品族,一組相關產品如何整體切換。
五、對比總結
| 模式 | 解決的問題 | 擴展是否需要修改代碼 | 適合場景 |
| 簡單工廠 | 隱藏創建細節,集中管理 | 要改(加 if) | 產品少、變化少 |
| 工廠方法 | 每個產品由獨立工廠創建 | 不用改(加新工廠類) | 需要靈活擴展單個產品 |
| 抽象工廠 | 創建“一組相關產品” | 要改(加新產品需改所有工廠) | 產品成族、需整體切換 |
總結
本文只是對工廠模式進行了一個簡單的瞭解,主要是學習了工廠模式的思想,沒有在具體場景中實踐。主要參考《Head First設計模式》這本書中的例子。如有不對,請多指教。