工厂方法模式

2020-08-22  本文已影响0人  刀藏水

日常工作中不知道你没有碰到这种情况: 测试环境调用一个服务, 但是这个服务却不可用,导致流程进行不下去。

我经历过的做法大体有两种:

  1. 把调用这个服务的相关代码注掉。
  2. 把服务发到预发环境。

这两种方法, 如果说外行可能用点过分,毕竟很多时候,这两种方式是最实用,最容易想到的。那么是对是错就暂时不讨论了。我提供一种我使用的方法。话不多说,"talk is cheap, show me the code".

有一个功能Notification,Notification会调用一个短信服务SmsService。 你知道的,测试环境没必要真发短信吧,但是直接调用服务又没有必要,又没有测试环境的短信服务。那怎么办呢?我先看了一下短信服务的代码:

public class UglySmsService {
    // 此处省略n行代码
    public void send() {
        System.out.println("--------real sms service");
        // 此处省略n行代码
    }
    // 此处省略n行代码
}

我在想, 是不是可以先定义一个接口, 然后实现两套逻辑。一套是真正发短信功能,一套是模拟发短信功能。当在测试环境的时候,就用模拟的实现。当上线后,就用真正的短信功能? 先定义一个接口如下:

public interface SmsService {
    public void send();
}

然后是两套实现:

@Service
public class MockSmsService implements SmsService{
    @Override
    public void send() {
        System.out.println("---------mock sms service");
    }
}
@Service
public class RealSmsService implements SmsService{
    // 此处省略n行代码
    @Override
    public void send() {
        System.out.println("--------real sms service");
        // 此处省略n行代码
    }
    // 此处省略n行代码
}

代码没有重构之前, 短信服务是这样调用的:

public class UglyNotification {
    UglySmsService uglySmsService;
    public void send() {
        uglySmsService.send();
    }
}

现在我有两套短信服务的实现了,send()方法里面用哪个实现呢?我写代码的时候是不确定的,只需要运行的时候把这个服务对象注入进去就行了。所以定义了一个抽象类,代码如下:

public abstract class Notification {
    abstract SmsService createSmsService();
    public void sendSms() {
        SmsService smsService = createSmsService();
        smsService.send();
    }
}

当我在测试环境的时候,我使用Notification的测试环境的实现,当在线上的时候,我使用Notification的线上实现,代码如下:

@Service
@ConditionalOnProperty(name = "notification.env", havingValue = "test")
public class MockNotification extends Notification{
    @Resource
    SmsService mockSmsService;

    @Override
    SmsService createSmsService() {
        return mockSmsService;
    }
}
@Service
@ConditionalOnProperty(name = "notification.env", havingValue = "prod")
public class RealNotification extends Notification{
    @Resource
    SmsService realSmsService;

    @Override
    SmsService createSmsService() {
        return realSmsService;
    }
}

不知道你体会到了没有。代码的关键就在于抽象类的createSmsService()这个方法。这个方法是用来创建对象用的。它就是平常所说的工厂方法模式。在咱们的例子里面可以这样理解:我有两个短信服务,我根据不同的环境需要调用不同的短信服务。两个短信服务实现相同的接口,所以我的代码逻辑是:我只管取一个短信服务实现,至于具体是真实服务还是模拟服务由子类来决定。 测试一下

@SpringBootTest
class PawnMoveApplicationTests {

  @Resource
  Notification notification;

  @Test
  void testFactoryMethod() {
    notification.sendSms();
  }
}

当我的配置文件如下时:

notification.env=test

打印

---------mock sms service

这就实现了根据不同的配置实例化不同的短信服务。

我的这个例子,是把service当作对象来处理, 只不过这个service是没有状态的。这种情况在日常开发中非常多见。因为web开发基本都是mvc架构,而且mvc架构是基本于“贫血”模型的。

“工厂方法”模式是模板模式的一个特殊例子,也是虚拟工厂的基本组件。理解工厂方法模式的关键就是抽象类中的抽象方法。它的意义在于让子类决定生成一个什么样的对象。切记切记。让子类来决定生成什么样的对象。

文章里面的代码可以访问  代码的github地址

如果认为我写的文章不错,可以添加我的微信公众号,我会每周发一篇原创文章,和大家共同探讨编程,学习编程。


刀藏水
上一篇下一篇

猜你喜欢

热点阅读