最近完成了使用阿里云提供的短信服务,完成了短信推送。代码中用到了一个工厂类,其实使用的并不是传统意义上的那三种工厂模式,更大程度上是使用了工厂模式的思想。
先谈用到的工厂模式的思想
一、问题场景
系统需要支持多种短信通道(阿里云、腾讯云等),根据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设计模式》这本书中的例子。如有不对,请多指教。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。