4

最近完成了使用阿里云提供的短信服务,完成了短信推送。代码中用到了一个工厂类,其实使用的并不是传统意义上的那三种工厂模式,更大程度上是使用了工厂模式的思想。

先谈用到的工厂模式的思想

一、问题场景

系统需要支持多种短信通道(阿里云、腾讯云等),根据application.yml的配置选择使用不同的服务。

  • 开发时,使用本地local避免真实发送
  • 生产时,切换为ali
  • 未来可以轻松切换其他厂商

二、实现方式(核心思想)

借鉴工厂模式的思想,将“对象选择逻辑”集中管理,业务代码只依赖接口。
具体做法:

  • 所有的短信服务都是ShortMessageSerivce的实现类
  • 工厂启动时自动收集所有类,按type构建映射表
  • 通过配置文件指定默认type

image.png

关键代码

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(比萨菜单)等等,这样将创建比萨的代码包装成一个类,当需要修改时,只需要修改工厂的代码就可以了。

三、工厂方法模式

随着比萨店越做越大,有了很多加盟店。每家加盟店都想要提供不同风味的比萨(比如纽约、芝加哥、加州),那么比萨的制作就受到了地区的影响,不同的地区制作方法不同。

image.png
代码如下:

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);
}

做到了真正的“对扩展开放”——加新分店只需要继承,实现对应的抽象方法。

四、抽象工厂模式

随着比萨店越开越大,怎么保证每家加盟店使用的都是高品质的原料,来保证比萨的质量呢?但是对于不同地区不同的比萨加盟店,要用到的原料也不相同。
需要引入原料工厂,不同地区的加盟店通过实现原料工厂制作原料。

image.png

原料工厂代码如下:

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设计模式》这本书中的例子。如有不对,请多指教。


wyhhh
21 声望12 粉丝