日常工作中不知道你没有碰到这种情况: 测试环境调用一个服务, 但是这个服务却不可用,导致流程进行不下去。
我经历过的做法大体有两种:
- 把调用这个服务的相关代码注掉。
- 把服务发到预发环境。
这两种方法, 如果说外行可能用点过分,毕竟很多时候,这两种方式是最实用,最容易想到的。那么是对是错就暂时不讨论了。我提供一种我使用的方法。话不多说,"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地址
如果认为我写的文章不错,可以添加我的微信公众号,我会每周发一篇原创文章,和大家共同探讨编程,学习编程。