美文网首页
工厂方法模式

工厂方法模式

作者: 刀藏水 | 来源:发表于2020-08-22 16:14 被阅读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地址

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


    刀藏水

    相关文章

      网友评论

          本文标题:工厂方法模式

          本文链接:https://www.haomeiwen.com/subject/boevjktx.html