《跟二师兄学Nacos吧》EXT-01篇 看看Nacos是怎么活学活用简单工厂模式的!

2021-12-07 14:19:39 浏览数 (1)

学习不用那么功利,二师兄带你一起轻松读源码~

番外篇简介

Nacos源码分析系列文章,在开篇已经提到过,写作的目标有两个:第一,能够系统的学习Nacos知识;第二,能够基于Nacos学到涉及到的知识点或面。

为了方便大家学习,相对应的文章标题会有所区别,Nacos原理部分命名按照正常编号进行。而番外篇,也就是技术点的讲解则会在文章编号上添加“EXT-”的前缀。这样,如果大家只想学习Nacos原理知识,则可跳过EXT前缀的文章。

这篇文章我们来看看Nacos Client中对工厂模式的使用。这里分两个步骤来了解,首先看看标准的工厂模式是什么样子的,然后再对比一下Nacos中的实现与标准实现有什么区别。

工厂模式概述

在23种设计模式当中,工厂模式包含两种:工厂方法模式和抽象工厂模式。它们都属于创建型模式,而还有一种简单工厂模式,虽然经常被用到,但可能是过于简单,未被纳入23种设计模式当中。

简单工厂模式

下面先介绍一下,简单工厂模式,并对Nacos中的使用进行对比,并思考为什么会这样设计。

简单工厂模式简介

简单工厂模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是由一个工厂对象根据不同的参数类型返回不同实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

对于简单工厂模式,要解决的问题便是封装实例创建的过程。需要什么类,只需传入一个对应的参数,就可以获得所需的对象,调用者无需知道实例的创建过程。被创建的实例通常都具有共同的父类。

简单工厂模式的结构

UML图展示如下:

简单工厂通常包括三部分:

  • Factory(工厂):核心部分,负责实现创建所有产品的内部逻辑,工厂类可以被外界直接调用,创建所需对象;
  • AbstractProduct(抽象产品类):工厂类所创建的所有对象的父类,封装了产品对象的公共方法,所有的具体产品为其子类对象;
  • ConcreteProduct(具体产品):简单工厂模式的创建目标,实现了抽象产品类,所有被创建的对象都是某个具体类的实例;

其中抽象产品类可以是接口,也可以说抽象类。

简单工厂模式的实现

这里假设Nacos的配置中心服务和命名服务都继承自统一的NacosService,同时都需要提供一个注册方法。

抽象产品类定义如下:

代码语言:javascript复制
public interface NacosService {
   /**
    * 注册实例信息
    * @param object 实例信息,这里用Object代替
    */
   void register(Object object);
}

命名服务NamingService的具体实现:

代码语言:javascript复制
public class NamingService implements NacosService {

   @Override
   public void register(Object object) {
      System.out.println("注册命名服务成功");
   }
}

配置服务ConfigService的具体实现:

代码语言:javascript复制
public class ConfigService implements NacosService {

   @Override
   public void register(Object object) {
      System.out.println("配置中心实例注册成功");
   }
}

提供一个工厂类NacosFactory:

代码语言:javascript复制
public class NacosFactory {

   public static NacosService getService(String name) {
      if ("naming".equals(name)) {
         return new NamingService();
      } else {
         return new ConfigService();
      }
   }
}

其中根据传入的参数,生成不同类型的NacosService具体实现。

此时,客户端就可以直接调用该工厂:

代码语言:javascript复制
public class Client {

   public static void main(String[] args) {

      NacosService nacosService = NacosFactory.getService("naming");
      nacosService.register(new Object());
   }
}

可以看出,此时客户端并不需要关注NacosService的具体实现类是如何被创建的,只需要通过NacosFactory来创建即可。这样,就把比较复杂的创建过程封装在了工厂类中。

简单工厂模式的优缺点

简单工厂模式的优点:

  • 工厂类可包含必要的逻辑判断,可决定不同参数创建不同产品的实例。实现了创建职责的分离;
  • 客户端无需知道所创建具体产品的类名,只需知道参数即可;
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性;

简单工厂模式的缺点:

  • 工厂类集成了产品创建逻辑,职责过重;
  • 增加系统中类的个数,增加系统复杂度和理解难度;
  • 违反了设计模式中的开闭原则,新增产品需修改工厂逻辑;
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

Nacos Client的简单工厂模式

在Nacos的client中,提供了一个NacosFactory的工厂类,该类统一提供了创建ConfigService(配置中心服务)、NamingService(注册中心服务)和NamingMaintainService(注册中心实例操作服务)的实例化方法。

在源码中可以看到是通过如下方式创建NamingService的:

代码语言:javascript复制
NamingService namingService = NacosFactory.createNamingService(properties);

NacosFactory的部分源码:

代码语言:javascript复制
public class NacosFactory {
    
    public static ConfigService createConfigService(Properties properties) throws NacosException {
        return ConfigFactory.createConfigService(properties);
    }
    
    public static NamingService createNamingService(Properties properties) throws NacosException {
        return NamingFactory.createNamingService(properties);
    }
    
    // ... 省略其他方法
}

乍一看该类的名字,你可能已经意识到它是工厂模式中的一种。但仔细对比会发现,哪一种好像都不是。

首先,我们来看最终创建出来的NamingService和ConfigService,它们各自独立,并不属于同一个抽象产品类。那这样创建出来也叫简单工厂模式吗?

这里要注意前面定义简单工厂模式时说过“被创建的实例通常都具有共同的父类”,这里的“通常”也就是说大多数情况下是这样的,也允许不实现自同一个接口或抽象类。

其次,我们会发现,NacosFactory中也没有根据方法参数进行不同的对象进行创建,而是直接提供了多个方法来创建不同的对象实例。这又是为什么呢?

这样设计可能出于三个目的:

第一,无论NamingService还是ConfigService,它们本身已经对应的工厂类ConfigFactory和NamingFactory了,如果需要单独创建其实是可以直接调用对应的工厂类的;

第二,NacosFactory存在的目的,本身就是为了达到“聚合”的作用,也就是把所有Nacos相关的服务实例集中对外提供,比如Spring Cloud集成时使用的NacosFactory来创建NamingService,而不是通过NamingFactory来创建。

第三,业务创建比较简单。也就是对于Nacos来说,目前版本中只会提供这三个Service,而不会再多出来其他的,因此开闭原则也就没那么重要了。于是就进行了简化处理。

因此,在使用工厂模式时并不一定非要按标准定义,教条式的进行实现,根据具体的场景可以灵活运用。基本上所有的设计模式都有类似的特点。

Nacos API的简单工厂模式

上面看到的是Nacos Client项目中的简单工厂模式实现,再进一步,我们看看该工厂模式中嵌套的NamingFactory:

代码语言:javascript复制
public class NamingFactory {

    public static NamingService createNamingService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            return (NamingService) constructor.newInstance(properties);
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
    // ...
}

在NamingFactory中的实现,才更接近标准的简单工厂模式。因为它提供的createNamingService方法,返回的是抽象产品类NamingService(接口)。而在该方法内部呢,才真正创建了它的实现类,虽然实现类只有一个。

小结

学习本文其实想给大家传递两个观点:第一,阅读源码时,其实我们可以多思考一步,比如看看它用到了什么设计模式或知识点;第二,学习设计模式时一定要活学活用,真实的实践环境变化完全,没必要刻板的按照概念来。活学活用,灵活变动,才达到了学习的最高境界。

源码地址:https://github.com/secbr/nacos-learn

博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。

0 人点赞