学习不用那么功利,二师兄带你一起轻松读源码~
番外篇简介
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技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。