文档资料: wenku.itilzj.com

一、设计模式介绍

所谓 “设计模式”,就是一套反复被人使用或验证过的方法论。从抽象或者更宏观的角度上看,只要符合使用场景并且能解决实际问题,模式应该既可以应用在DDD中,也可以应用在设计模式中。

二、常用的设计模式有哪些?

  1. 策略模式

  2. 工厂模式

  3. 单例模式

  4. 代理模式

  5. 工厂方法模式

  6. 观察者模式

  7. 模板方法模式

  8. 适配器模式

三、设计模式简单实现模板

场景: 商场搞活动,根据客户购买商品的金额,收费时给与不同的打折,比如,购买金额>=2000的打八折(0.8),金额500 ~ 1000的,打九折(0.9),购买金额0 ~ 500的九五折(0.95),根据不同的金额走不同计算策略逻辑。

3.1 策略模式

首先定义一个Strategy接口来表示一个策略:

public interface Strategy {

/**
* 采用策略
*/
String strategy();

/**
* 计算方法逻辑
*/
void algorithm();
}

其中strategy方法返回当前策略的唯一标识,algorithm则是该策略的具体执行的计算逻辑。

下面是Strategy接口的两个实现类:

public class ConcreteStrategyA implements Strategy {
@Override
public String strategy() {
return StrategySelector.strategyA.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyA...");
}
}

public class ConcreteStrategyB implements Strategy {

@Override
public String strategy() {
return StrategySelector.strategyB.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyB...");
}
}

public class ConcreteStrategyC implements Strategy {

@Override
public String strategy() {
return StrategySelector.strategyC.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyC...");
}
}

自定义策略选择枚举 **StrategySelector**:

@Getter
public enum StrategySelector {

strategyA(1,"strategyA"),
strategyB(2,"strategyB"),
strategyC(3,"strategyC");
private Integer code;
private String strategy;

StrategySelector(Integer code, String strategy) {
this.code = code;
this.strategy = strategy;
}
}

然后定义一个StrategyRunner接口用来表示策略的调度器:

public interface StrategyRunner {
void execute(String strategy);
}

execute方法内部通过判断strategy的值来决定具体执行哪一个策略。

public class StrategyRunnerImpl implements StrategyRunner {

private static final List STRATEGIES = Arrays.asList( new ConcreteStrategyA(), new ConcreteStrategyB(), new ConcreteStrategyC());
private static Map< String, Strategy> STRATEGY_MAP = Maps.newHashMap();

static {
STRATEGY_MAP = STRATEGIES.stream().collect(Collectors.toMap(Strategy::strategy, s -> s));
}

@Override
public void execute( String strategy) {
STRATEGY_MAP.get(strategy).algorithm();
}
}

StrategyRunnerImpl内部,定义了一个STRATEGIES列表来保存所有Strategy实现类的实例,以及一个叫做STRATEGY_MAPMap来保存strategyStrategy实例之间的对应关系,static块中的代码用于从STRATEGIES列表构造STRATEGY_MAP。这样,在execute方法中就可以很方便地获取到指定strategyStrategy实例。

实现并运用策略模式

@Component
public class ConcreteStrategyA implements Strategy {
@Override
public String strategy() {
return StrategySelector.strategyA.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyA...");
}
}

@Component
public class ConcreteStrategyB implements Strategy {

@Override
public String strategy() {
return StrategySelector.strategyB.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyB...");
}
}

@Component
public class ConcreteStrategyC implements Strategy {

@Override
public String strategy() {
return StrategySelector.strategyC.getStrategy();
}

@Override
public void algorithm() {
System.out.println("process with strategyC...");
}
}

然后,定义一个StrategyConfig配置类,用于向容器注入一个StrategyRunner:

@Configuration
public class StrategyConfig {

@Bean
public StrategyRunner runner(List strategies) {
Map< String, Strategy> strategyMap = strategies.stream().collect(Collectors.toMap(Strategy::strategy, s -> s));
return flag -> strategyMap.get(flag).algorithm();
}
}

不难发现,strategyRunner方法的实现,其中的逻辑与之前的StrategyRunnerImpl几乎完全相同,也是根据一个List 来构造一个Map 。只不过,这里的strategies列表不是我们自己构造的,而是通过方法参数传进来的。由于strategyRunner标注了Bean注解,因此参数上的List 实际上是在Spring Boot初始化过程中从容器获取的,所以我们之前向容器中注册的那两个实现类会在这里被注入。

这样,我们再也无需操心系统中一共有多少个Strategy实现类,因为Spring Boot的自动配置会帮我们自动发现所有实现类。我们只需编写自己的Strategy实现类,然后将它注册进容器,并在任何需要的地方注入StrategyRunner

@Autowired private StrategyRunner strategyRunner;

然后直接使用strategyRunner就行了:

@RestController
@RequestMapping(value = "/designPatterns")
public class DesignPatternController {

@Autowired
private StrategyRunner strategyRunner;

@GetMapping(value = "/algorithm")
public void algorithm(@RequestParam("strategy") String strategy) {
strategyRunner.execute(strategy);
}
}

访问接口,控制台输出如下:

process with strategyA...

3.2 简单工厂模式

举个场景例子:

用户支付场景,目前支持支付宝支付和微信支付,未来会新增银行卡,云闪付等方式。使用策略模式,每一种支付方式都是一种策略,根据用户传入的支付类型,创建不同的策略类,使用工厂模式,通过封装一个PaymentStrategyHandler策略处理类,其他系统直接通过一个统一的入口,进行该功能的调用,使用门面模式。

3.2.1 定义一个策略类:

public interface IPayment {

/**
* 支付
*
* @param paymentBody
*/
Boolean pay(PaymentBody paymentBody);
}

public class AliPay implements IPayment {
@Override
public Boolean pay(PaymentBody paymentBody) {
System.out.println("支付宝支付...");
return Boolean.TRUE;
}
}

public class WechatPay implements IPayment {
@Override
public Boolean pay(PaymentBody paymentBody) {
System.out.println("微信支付...");
return Boolean.TRUE;
}
}

public class UnionPay implements IPayment {
@Override
public Boolean pay(PaymentBody paymentBody) {
System.out.println("银联支付...");
return Boolean.TRUE;
}
}

3.2.2 创建策略工厂

package com.universal.core.designPatterns.factory;

import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ReflectUtil;
import com.universal.core.designPatterns.enums.PayStrategyEnum;
import org.springframework.stereotype.Component;

/**
* Factory for payment methods
*/
@Component
public class PaymentFactory {

public static IPayment getPayStrategy(String type) {

// 1.通过枚举中的type获取对应的value

String value = EnumUtil.getFieldBy(PayStrategyEnum::getValue, PayStrategyEnum::getType, type);
// 2.使用反射机制创建对应的策略类

IPayment payment = ReflectUtil.newInstance(value);
return payment;

3.3.3 定义策略枚举

/**
* 支付策略枚举
*/
@Getter
public enum PayStrategyEnum {

ZFB("ZFB", "com.universal.core.designPatterns.factory.impl.AliPay"),
WX("WX", "com.universal.core.designPatterns.factory.impl.WechatPay"),
UNION("UNION", "com.universal.core.designPatterns.factory.impl.UnionPay");
String type;
String value;

PayStrategyEnum(String type, String value) {
this.type = type;
this.value = value;
}
}

3.3.4 创建策略的上下文角色

@Data
public class PaymentContext {

@Resource
private IPayment paymentStrategy;

public PaymentContext(IPayment paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}

public Boolean pay(PaymentBody paymentBody) {
return this.paymentStrategy.pay(paymentBody);
}
}

3.4.5 提供统一访问处理入口

package com.universal.core.designPatterns.factory;

import cn.hutool.core.util.EnumUtil;
import com.universal.core.designPatterns.enums.PayStrategyEnum;
import org.springframework.stereotype.Component;

@Component
public class PaymentStrategyHandler {

public static Boolean pay(PaymentBody payBody) {
if (!EnumUtil.contains(PayStrategyEnum.class, payBody.getType())) {
throw new IllegalArgumentException("不支持的支付方式!");
}

// 1.获取支付策略对象

IPayment payStrategy = PaymentFactory.getPayStrategy(payBody.getType());
// 2.获取支付策略上下文

PaymentContext paymentContext = new PaymentContext(payStrategy);
// 3.进行支付

return paymentContext.pay(payBody);

3.4.6 创建Controller

@RestController
@RequestMapping(value = "/designPatterns")
public class DesignPatternController {

@PostMapping("/pay")
public Boolean pay(@RequestBody PaymentBody paymentBody){
return PaymentStrategyHandler.pay(paymentBody);
}

}

3.3 单例模式

代码演示:

//懒汉式(静态内部类)

class Singleton {
private Singleton() {}

//写一个静态内部类,该类中有一个静态属性Singleton

private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();

public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}

3.4 代理模式

代理模式Proxy, 为其他对象提供一种代理以控制对这个对象的访问。

代理模式实际上在平时中也运用的非常广泛,最经典的例子就是房东委托中介代理出租房子的案例,本文也是采用这个案例对代理模式进行解释和代码实现。

image.png
打开网易新闻 查看精彩图片
image.png

代码实例:

创建一个Subject类:

/**
* 活动类,目的是出租房子
*/
public interface Subject {

/**
* 租房接口
*/
void rentHouse();
}

定义一个房东角色,现在活动类:

/**
* 房东
*/
public class HouseOwner implements Subject {

/**
* 实现租房方法
*/
@Override
public void rentHouse() {
System.out.println("房东成功出租了房子...");
}
}

定义一个中介代理对象:


/**
* 中介代理类
*
* 一般情况下我们不能直接联系到房东,所以需要提供一个代理类,即中介类
*/
public class HouseProxy implements Subject {

private HouseOwner houseOwner = new HouseOwner();

@Override
public void rentHouse() {
System.out.println("中介收取代理费,帮助房东出租房子...");
houseOwner.rentHouse();
}
}

模拟用户找中介租房子:

image.png3.5 工厂方法模式
打开网易新闻 查看精彩图片
image.png3.5 工厂方法模式

上面我们也提到了简单工厂模式,那么工厂方法模式和简单工厂的区别在于哪里呢,其实,简单工厂模式的最大优点在于包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,相对于客户端来说,去除了与具体产品的依赖。

工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法是一个类的实例化延迟到其子类,通俗来说:它提供了一种实例化逻辑委托子类的方法。

代码示例:

定义NetworkConfigFactoryService工厂类

package com.universal.core.designPatterns.factoryMethod.factory;

import com.universal.core.designPatterns.factoryMethod.NetworkConfigCrudService;

public interface NetworkConfigFactoryService {

/**
* 获取指定的处理逻辑类
*
* @param productType
* @return
*/
NetworkConfigCrudService getSpecificService(String productType);
}

NetworkConfigFactoryService工厂实现类

@Service
public class NetworkConfigFactoryServiceImpl implements NetworkConfigFactoryService {

private final AServiceImpl aService;
private final BServiceImpl bService;
private final CServiceImpl cService;
private final DServiceImpl dService;

@Override
public NetworkConfigCrudService getSpecificService(String productType) {
NetworkConfigCrudService networkConfigCrudService = null;
switch (productType){
case "A" :
networkConfigCrudService = aService;
break;
case "B":
networkConfigCrudService = bService;
break;
case "C":
networkConfigCrudService = cService;
break;
case "D":
networkConfigCrudService = dService;
break;
}
return networkConfigCrudService;
}
}

定义网点操作接口NetworkConfigCrudService

public interface NetworkConfigCrudService {

NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO);
}

它的实现类分别是AServiceImpl、BServiceImpl、CServiceImpl、DServiceImpl,分别对应不同的逻辑:

image.png
打开网易新闻 查看精彩图片
image.png

@Service
public class AServiceImpl implements NetworkConfigCrudService {
@Override
public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
return new NetworkConfigVO();
}
}

@Service
public class BServiceImpl implements NetworkConfigCrudService {
@Override
public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
return new NetworkConfigVO();
}
}
@Service
public class CServiceImpl implements NetworkConfigCrudService {
@Override
public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
return new NetworkConfigVO();
}
}
@Service
public class DServiceImpl implements NetworkConfigCrudService {
@Override
public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
return new NetworkConfigVO();
}
}

控制层NetworkConfigController

@RestController
@Slf4j
@RequestMapping(value = "/networkConfig")
public class NetworkConfigController {
private final NetworkConfigFactoryService factoryService;

@PostMapping(value = "/getNetworkDetails", produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResult getNetworkDetails(@RequestBody NetworkConfigDTO networkConfigDTO) {
//获取AService处理类逻辑
NetworkConfigCrudService aService = factoryService.getSpecificService("A");
NetworkConfigVO network = aService.getNetwork(networkConfigDTO);
return ApiResult.success(network);
}
}

3.6 观察者模式

观察者模式Observer 定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

*初识观察者模式:报社+订阅者 = 观察者模式。

要点

  • 观察者模式定义了对象之间一对多的关系。

  • 主题(可观察者)用一个共同的接口来更新观察者。

  • 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。

  • 使用次模式时,你可以从被观察者处推或拉数据(推的方式被认为是更正确的)。

  • 有多个观察者时,不可以依赖特定的通知次序。

  • java中有多种观察者模式的实现,包括了通用的java.util.Observable,不过需要注意Observable实现上所带来的问题,有必要的话,可以实现自己的Observable

  • Spring也大量使用观察者模,比如ListenrEvent消息订阅与发布;

案例

直接以气象站为例,其中天气信息就表示被观察者,天气布告板就表示订阅者和观察者,当天气发生变化(被观察者)时,会通过notifyObserver通知所有观察者,并调用他们的控制方法处理数据。

一个WeatherData对象负责追踪目前的天气状况(温度,湿度,气压)。我们希望你们能建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。

气象监测应用的对象分析

此系统中的三个部分是:

  • 气象站(获取实际气象数据的物理装置)

  • WeatherData对象(最总来自气象站的数据,并更新布告板)

  • 布告板(显示目前天气状况给用户看)。

实现气象站

/主题接口
interface Subject{

//注册观察者

public void registerObserver(Observer o);
//删除观察者

public void removeObserver(Observer o);
//当主题状态改变时,这个方法会被调用,以通知所有的观察者

public void notifyObserver();

interface Observer {

//当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者

public void update(float temp,float humidity,float pressure);

interface Display {

//当布告板需要显示时,调用此方法

public void display();

在WeatherData中实现主题接口

class WeatherData implements Subject{

private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData(){
observers= new ArrayList ();
}

@ Override
public void registerObserver(Observer o) {
observers. add(o);
}

@ Override
public void removeObserver(Observer o) {
int i=observers.indexOf(o);
if(i>= 0){
observers. remove(i);
}
}

@ Override
public void notifyObserver() {
for(Observer observer:observers){
observer.update(temperature,humidity,pressure);
}
}

//当从气象站得到更新观测值时,我们通知观察者

public void measurementsChanged(){
notifyObserver();
}

public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
measurementsChanged();
}
//WeatherData的其他方法

}

建立布告板

其中的一个布告板:

class CurrentConditionDisplay implements Observer, DisplayElement {

// 温度

private float temperature;
// 湿度

private float humidity;
// 气压

private float pressure;

private Subject weatherData;

public CurrentConditionDisplay(Subject weatherData){
this.weatherData=weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("这里气象台更新的天气数据...");
}

@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure
display();
}
}

利用内置的支持重写WeatherData

class WeatherDataTWO extends Observable{
private float temperature;
private float humidity;
private float pressure;

public WeatherDataTWO(){

}

public void measurementsChanged(){

//在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变

setChanged();
//我们没有调用notifyObservers传送数据对象,表示我们采用的做法是拉。

notifyObservers();

public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
measurementsChanged();
}

public float getTemperature() {
return temperature;
}

public float getHumidity() {
return humidity;
}

public float getPressure() {
return pressure;
}
}

利用内置观察者重写布告板

class CurrentConditionsDisplay implements java.util.Observer,DisplayElement{

Observable observable;
private float temperature;
private float humidity;

public CurrentConditionsDisplay(Observable observable){
this.observable=observable;
observable.addObserver(this);
}

@Override
public void display() {
System.out.println("这里气象台更新的天气数据...");
}

@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherDataTWO){
WeatherDataTWO weatherDataTWO= (WeatherDataTWO) o;
this.temperature=weatherDataTWO.getTemperature();
this.humidity=weatherDataTWO.getHumidity();
display();
}
}
}

3.7 模板方法模式

模板方法(Template Method) 是一种行为设计模式。模板方法设计模式用于创建方法存根并将某些实现步骤推迟到子类。

模板方法定义了执行算法的步骤,它可以提供可能对所有或者部分子类通用的默认实现,下面通过一个简单的例子来理解这个模式,假设我们想提供一种算法了该房子,建造房屋需要执行的步骤是:建造地基->建造支柱->建造墙壁和窗户。重点的一点是我们不能改变执行的顺序,因为我们不能在构建基础之前构建窗口,所以在这种情况下,我们可以创建一个模板方法,它将使用不同的方法来建造房子,现在盖房子的地基对于所有类型的房子都是一样的,无论是木房、玻璃房子还是混泥土房。所以我们可以为此提供基础实现,如果子类想要覆盖这个方法,他们可以自己选择,但是大多数情况下,所有类型的房屋都很常见。为了确保子类不覆盖模板方法,我们应该将其设为最终方法。

模板方法抽象类

由于我们希望某些方法由子类实现,因此我们必须将我们的基类设为抽象类。

定义抽象类HouseTemplate

public abstract class HouseTemplate {

/**
* buildHouse()是模板方法,定义个执行几个步骤的执行顺序
*
* template method, final so subclasses can't override final修饰,子类不能重写
*/
public final void buildHouse(){

//建造地基

buildFoundation();
//建造柱子

buildPillars();
//建造墙壁

buildWalls();
//建造窗户

buildWindows();
System.out.println("House is built successfully");

private void buildFoundation() {
System.out.println("Building foundation with cement, iron rods and sand");
}

/**
* methods to be implemented by subclasses
*/
public abstract void buildPillars();
public abstract void buildWalls();

/**
* default implementation
*/
private void buildWindows() {
System.out.println("Building Glass Windows");
}

}

WoodenHouse

package com.universal.core.designPatterns.templateMethod;

/**
* 木房
*/
public class WoodenHouse extends HouseTemplate {
@Override
public void buildPillars() {
System.out.println("Building Pillars With Wood coating...");
}

@Override
public void buildWalls() {
System.out.println("Building Wooden Walls...");
}
}

GlassHouse

package com.universal.core.designPatterns.templateMethod;

/**
* 玻璃房
*/
public class GlassHouse extends HouseTemplate {

@Override
public void buildPillars() {
System.out.println("Building Pillars With Glass coating...");
}

@Override
public void buildWalls() {
System.out.println("Building Glass Walls...");

}
}

ConcreteHouse

package com.universal.core.designPatterns.templateMethod;

/**
* 混泥土房屋
*/
public class ConcreteHouse extends HouseTemplate {
@Override
public void buildPillars() {
System.out.println("Building Pillars With Concrete coating...");
}

@Override
public void buildWalls() {
System.out.println("Building Concrete Walls...");

}
}

HousingClient

package com.universal.core.designPatterns.templateMethod;

public class HousingClient {

public static void main(String[] args) {
HouseTemplate houseBuilder = new WoodenHouse();
houseBuilder.buildHouse();

System.out.println("--------------");

houseBuilder = new GlassHouse();
houseBuilder.buildHouse();

System.out.println("--------------");
houseBuilder = new ConcreteHouse();
houseBuilder.buildHouse();

}
}

输出结果:

Building foundation with cement,iron rods and sand
Building Pillars With Wood coating...
Building Wooden Walls...
Building Glass Windows
House is built successfully
--------------
Building foundation with cement,iron rods and sand
Building Pillars With Glass coating...
Building Glass Walls...
Building Glass Windows
House is built successfully
--------------
Building foundation with cement,iron rods and sand
Building Pillars With Concrete coating...
Building Concrete Walls...
Building Glass Windows
House is built successfully

Process finished with exit code 0

3.8 适配器模式

适配器模式Adapter 是将一个接口转换成另一个客户所期望的接口。**Adapter** 适配器让那些本来因为接口不兼容的类可以合作无间。

适配器模式中的角色分析

  • 目标接口(Traget):客户期待的接口,目标可以是具体的或者抽象的类,也可以是接口。

  • 需要适配的对象(Source Adaptee):需要适配的对象。

  • 适配器(Adapter):通过包装一个需要适配的对象,把原接口转成目标接口。

举个例子:我们以网线上网为例,现在有一根水晶头网线,但是它的接口与电脑的不匹配(因为电脑的是usb或者typec),那么就需要一个转接头,也就是我们说的适配器,才能够上网,下面的转接头可以理解为适配器:

image.png类适配器
打开网易新闻 查看精彩图片
image.png类适配器

首先我们拥有一根网线, 他有上网的功能,但是它的接口与电脑不匹配:

//要适配的类:网线

public class Adaptee {
//功能:上网

public void request(){
System.out.println("链接网线上网");

因此我们定义了一个usb接口,也就是上面提到的目标接口(Target):

//接口转换器的抽象实现

public interface NetToUsb {
//作用:处理请求,网线 => usb

public void handleRequest();

定义一个适配器继承着网线,连接着usb接口:

//真正的适配器,余姚链接usb,连接网线

public class Adapter extends Adaptee implements NetToUsb {
@Override
public void handleRequest() {
//可以上网了

super.request();

上网的具体实现:

//客户端类:想上网,插不上网线

public class Computer {
//电脑需要连接上转接器才可以上网

public void net(NetToUsb adapter){
//上网的具体实现:找一个转接头

adapter.handleRequest();

public static void main(String[] args) {

//电脑,适配器,网线

Computer computer = new Computer();
//电脑

Adapter adapter = new Adapter();
//转接器

computer.net(adapter);
//电脑直接连接转接器就可以


对象适配器应用

  • java.util.Arrays#asList()open in new window

  • java.util.Collections#list()open in new window

  • java.util.Collections#enumeration()open in new window

  • javax.xml.bind.annotation.adapters.XMLAdapter

四、总结

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。对于一个进阶高级开发的技术人员来说,了解设计模式的理念和具体的实现颇为重要,本期内容分享就到这里了,希望对你能有所帮助。

IT架构师/技术大咖的交流圈子,为您提供架构体系知识、技术文章、流行实践案例、解决方案等,行业大咖分享交流/同行经验分享互动,期待你的加入!扫码即可加入哦,随着材料不断增多社群会不定期涨价早加入更优惠

免责声明:

本公众号部分分享的资料来自网络收集和整理,所有文字和图片版权归属于原作者所有,且仅代表作者个人观点,与本公众号无关,文章仅供读者学习交流使用,并请自行核实相关内容,如文章内容涉及侵权,请联系后台管理员删除。