所谓 “设计模式”,就是一套反复被人使用或验证过的方法论。从抽象或者更宏观的角度上看,只要符合使用场景并且能解决实际问题,模式应该既可以应用在DDD中,也可以应用在设计模式中。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/ 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud 视频教程:https://doc.iocoder.cn/video/ 场景: 商场搞活动,根据客户购买商品的金额,收费时给与不同的打折,比如,购买 金额>=2000 的打八折(0.8),金额 500 ~ 1000 的,打九折(0.9),购买金额 0 ~ 500 的九五折(0.95),根据不同的金额走不同计算策略逻辑。
首先定义一个Strategy接口来表示一个策略:
public interfaceStrategy{/** * 采用策略 */ Stringstrategy();/** * 计算方法逻辑 */ voidalgorithm(); }
其中strategy方法返回当前策略的唯一标识,algorithm则是该策略的具体执行的计算逻辑。
下面是Strategy接口的两个实现类:
public classConcreteStrategyAimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyA.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyA..." ); } } publicclassConcreteStrategyBimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyB.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyB..." ); } } publicclassConcreteStrategyCimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyC.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyC..." ); } }
自定义策略选择枚举 StrategySelector:
@Getter publicenumStrategySelector{ strategyA(1 ,"strategyA" ), strategyB(2 ,"strategyB" ), strategyC(3 ,"strategyC" ); privateInteger code; privateString strategy; StrategySelector(Integer code,String strategy){this .code = code;this .strategy = strategy; } }
然后定义一个StrategyRunner接口用来表示策略的调度器:
public interface StrategyRunner { void execute (String strategy) ; }
execute方法内部通过判断strategy的值来决定具体执行哪一个策略。
public classStrategyRunnerImplimplementsStrategyRunner{ privatestaticfinalList STRATEGIES =Arrays.asList(newConcreteStrategyA(),newConcreteStrategyB(),newConcreteStrategyC()); privatestaticMap STRATEGY_MAP =Maps.newHashMap();static { STRATEGY_MAP = STRATEGIES.stream().collect(Collectors.toMap(Strategy::strategy, s -> s)); }@Override publicvoidexecute(String strategy){ STRATEGY_MAP.get(strategy).algorithm(); } }
在StrategyRunnerImpl内部,定义了一个STRATEGIES
列表来保存所有Strategy实现类的实例,以及一个叫做STRATEGY_MAP
的Map来保存strategy和Strategy实例之间的对应关系,static块中的代码用于从STRATEGIES
列表构造STRATEGY_MAP
。这样,在execute方法中就可以很方便地获取到指定strategy的Strategy实例。
@Component publicclassConcreteStrategyAimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyA.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyA..." ); } }@Component publicclassConcreteStrategyBimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyB.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyB..." ); } }@Component publicclassConcreteStrategyCimplementsStrategy{@Override publicStringstrategy(){ returnStrategySelector.strategyC.getStrategy(); }@Override publicvoidalgorithm(){ System.out.println("process with strategyC..." ); } }
然后,定义一个StrategyConfig配置类,用于向容器注入一个StrategyRunner:
@Configuration publicclassStrategyConfig{@Bean publicStrategyRunnerrunner(List strategies){ Map 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" ) publicclassDesignPatternController{@Autowired privateStrategyRunner strategyRunner;@GetMapping (value = "/algorithm" ) publicvoidalgorithm(@RequestParam ("strategy" ) String strategy){ strategyRunner.execute(strategy); } }
访问接口,控制台输出如下:
process with strategyA...
举个场景例子🌰:
用户支付场景,目前支持支付宝支付和微信支付,未来会新增银行卡,云闪付等方式。使用策略模式,每一种支付方式都是一种策略,根据用户传入的支付类型,创建不同的策略类,使用工厂模式,通过封装一个PaymentStrategyHandler策略处理类,其他系统直接通过一个统一的入口,进行该功能的调用,使用门面模式。
public interfaceIPayment{/** * 支付 * * @param paymentBody */ Booleanpay(PaymentBody paymentBody); } publicclassAliPayimplementsIPayment{@Override publicBooleanpay(PaymentBody paymentBody){ System.out.println("支付宝支付..." ); returnBoolean.TRUE; } } publicclassWechatPayimplementsIPayment{@Override publicBooleanpay(PaymentBody paymentBody){ System.out.println("微信支付..." ); returnBoolean.TRUE; } } publicclassUnionPayimplementsIPayment{@Override publicBooleanpay(PaymentBody paymentBody){ System.out.println("银联支付..." ); returnBoolean.TRUE; } }
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 publicclassPaymentFactory{ publicstaticIPaymentgetPayStrategy(String type){// 1.通过枚举中的type获取对应的value Stringvalue=EnumUtil.getFieldBy(PayStrategyEnum::getValue,PayStrategyEnum::getType, type);// 2.使用反射机制创建对应的策略类 IPaymentpayment=ReflectUtil.newInstance(value);return payment; } }
/** * 支付策略枚举 */ @Getter publicenumPayStrategyEnum{ 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; } }
@Data publicclassPaymentContext{@Resource privateIPayment paymentStrategy; publicPaymentContext(IPayment paymentStrategy){this .paymentStrategy = paymentStrategy; } publicBooleanpay(PaymentBody paymentBody){ returnthis.paymentStrategy.pay(paymentBody); } }
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 publicclassPaymentStrategyHandler{ publicstaticBooleanpay(PaymentBody payBody){if (!EnumUtil.contains(PayStrategyEnum.class , payBody .getType ())) { thrownewIllegalArgumentException("不支持的支付方式!" ); }// 1.获取支付策略对象 IPaymentpayStrategy=PaymentFactory.getPayStrategy(payBody.getType());// 2.获取支付策略上下文 PaymentContextpaymentContext=newPaymentContext(payStrategy);// 3.进行支付 return paymentContext.pay(payBody); } }
@RestController @RequestMapping (value = "/designPatterns" ) publicclassDesignPatternController{@PostMapping ("/pay" ) publicBooleanpay(@RequestBody PaymentBody paymentBody){ returnPaymentStrategyHandler.pay(paymentBody); } }
代码演示:
//懒汉式(静态内部类) classSingleton{ privateSingleton(){}//写一个静态内部类,该类中有一个静态属性Singleton privatestaticclassSingletonInstance{ privatestaticfinalSingletonINSTANCE=newSingleton(); } publicstaticsynchronizedSingletongetInstance(){ returnSingletonInstance.INSTANCE; } }
代理模式Proxy, 为其他对象提供一种代理以控制对这个对象的访问。
代理模式 实际上在平时中也运用的非常广泛,最经典的例子就是房东委托中介代理出租房子的案例,本文也是采用这个案例对代理模式进行解释和代码实现。
代码实例🌰:
创建一个Subject类:
/** * 活动类,目的是出租房子 */ public interface Subject { /** * 租房接口 */ void rentHouse () ; }
定义一个房东角色,现在活动类:
/** * 房东 */ publicclassHouseOwnerimplementsSubject{/** * 实现租房方法 */ @Override publicvoidrentHouse(){ System.out.println("房东成功出租了房子..." ); } }
定义一个中介代理对象:
/** * 中介代理类 * * 一般情况下我们不能直接联系到房东,所以需要提供一个代理类,即中介类 */ publicclassHouseProxyimplementsSubject{ privateHouseOwnerhouseOwner=newHouseOwner();@Override publicvoidrentHouse(){ System.out.println("中介收取代理费,帮助房东出租房子..." ); houseOwner.rentHouse(); } }
模拟用户找中介租房子:
上面我们也提到了简单工厂模式,那么工厂方法模式和简单工厂的区别在于哪里呢,其实,简单工厂模式的最大优点在于包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,相对于客户端来说,去除了与具体产品的依赖。
工厂方法模式(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 publicclassNetworkConfigFactoryServiceImplimplementsNetworkConfigFactoryService{ privatefinalAServiceImpl aService; privatefinalBServiceImpl bService; privatefinalCServiceImpl cService; privatefinalDServiceImpl dService;@Override publicNetworkConfigCrudServicegetSpecificService(String productType){ NetworkConfigCrudServicenetworkConfigCrudService=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
,分别对应不同的逻辑:
@Service publicclassAServiceImplimplementsNetworkConfigCrudService{@Override publicNetworkConfigVOgetNetwork(NetworkConfigDTO networkConfigDTO){ returnnewNetworkConfigVO(); } }@Service publicclassBServiceImplimplementsNetworkConfigCrudService{@Override publicNetworkConfigVOgetNetwork(NetworkConfigDTO networkConfigDTO){ returnnewNetworkConfigVO(); } }@Service publicclassCServiceImplimplementsNetworkConfigCrudService{@Override publicNetworkConfigVOgetNetwork(NetworkConfigDTO networkConfigDTO){ returnnewNetworkConfigVO(); } }@Service publicclassDServiceImplimplementsNetworkConfigCrudService{@Override publicNetworkConfigVOgetNetwork(NetworkConfigDTO networkConfigDTO){ returnnewNetworkConfigVO(); } }
控制层NetworkConfigController
@RestController @Slf 4j@RequestMapping (value = "/networkConfig" ) publicclassNetworkConfigController{ privatefinalNetworkConfigFactoryService factoryService;@PostMapping (value = "/getNetworkDetails" , produces = MediaType.APPLICATION_JSON_VALUE) publicApiResultgetNetworkDetails(@RequestBody NetworkConfigDTO networkConfigDTO){//获取AService处理类逻辑 NetworkConfigCrudServiceaService= factoryService.getSpecificService("A" ); NetworkConfigVOnetwork= aService.getNetwork(networkConfigDTO); returnApiResult.success(network); } }
观察者模式Observer 定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
初识观察者模式:报社+订阅者 = 观察者模式。
观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。 使用次模式时,你可以从被观察者处推或拉数据(推的方式被认为是更正确的)。 java中有多种观察者模式的实现,包括了通用的java.util.Observable
,不过需要注意Observable实现上所带来的问题,有必要的话,可以实现自己的Observable。 Spring也大量使用观察者模,比如ListenrEvent消息订阅与发布; 直接以气象站为例,其中天气信息就表示被观察者,天气布告板就表示订阅者和观察者,当天气发生变化(被观察者)时,会通过notifyObserver通知所有观察者,并调用他们的控制方法处理数据。
一个WeatherData对象负责追踪目前的天气状况(温度,湿度,气压)。我们希望你们能建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。
此系统中的三个部分是:
WeatherData对象(最总来自气象站的数据,并更新布告板) //主题接口 interfaceSubject{//注册观察者 publicvoidregisterObserver(Observer o);//删除观察者 publicvoidremoveObserver(Observer o);//当主题状态改变时,这个方法会被调用,以通知所有的观察者 publicvoidnotifyObserver(); } interfaceObserver{//当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者 publicvoidupdate(float temp,float humidity,float pressure); } interfaceDisplay{//当布告板需要显示时,调用此方法 publicvoiddisplay(); }
在WeatherData中实现主题接口
class WeatherDataimplementsSubject { privateArrayList observers; privatefloat temperature; privatefloat humidity; privatefloat pressure; publicWeatherData(){ observers=newArrayList(); }@Override publicvoidregisterObserver(Observer o){ observers.add(o); }@Override publicvoidremoveObserver(Observer o){int i=observers.indexOf(o);if (i>=0 ){ observers.remove(i); } }@Override publicvoidnotifyObserver(){for (Observer observer:observers){ observer.update(temperature,humidity,pressure); } }//当从气象站得到更新观测值时,我们通知观察者 publicvoidmeasurementsChanged(){ notifyObserver(); } publicvoidsetMeasurements(float temperature,float humidity,float pressure){this .temperature=temperature;this .humidity=humidity;this .pressure=pressure; measurementsChanged(); }//WeatherData的其他方法 }
其中的一个布告板:
class CurrentConditionDisplayimplementsObserver ,DisplayElement {// 温度 privatefloat temperature;// 湿度 privatefloat humidity;// 气压 privatefloat pressure; privateSubject weatherData; publicCurrentConditionDisplay(Subject weatherData){this .weatherData=weatherData; weatherData.registerObserver(this ); }@Override publicvoiddisplay(){ System.out.println("这里气象台更新的天气数据..." ); }@Override publicvoidupdate(float temp, float humidity, float pressure){this .temperature = temp;this .humidity = humidity;this .pressure = pressuredisplay () ; } }
利用内置的支持重写WeatherData
class WeatherDataTWOextendsObservable { privatefloat temperature; privatefloat humidity; privatefloat pressure; publicWeatherDataTWO(){ } publicvoidmeasurementsChanged(){//在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变 setChanged();//我们没有调用notifyObservers传送数据对象,表示我们采用的做法是拉。 notifyObservers(); } publicvoidsetMeasurements(float temperature,float humidity,float pressure){this .temperature=temperature;this .humidity=humidity;this .pressure=pressure; measurementsChanged(); } publicfloatgetTemperature(){return temperature; } publicfloatgetHumidity(){return humidity; } publicfloatgetPressure(){return pressure; } }
利用内置观察者重写布告板
class CurrentConditionsDisplayimplementsjava .util .Observer ,DisplayElement { Observable observable; privatefloat temperature; privatefloat humidity; publicCurrentConditionsDisplay(Observable observable){this .observable=observable; observable.addObserver(this ); }@Override publicvoiddisplay(){ System.out.println("这里气象台更新的天气数据..." ); }@Override publicvoidupdate(Observable o, Object arg){if (o instanceofWeatherDataTWO){ WeatherDataTWO weatherDataTWO=(WeatherDataTWO) o;this .temperature=weatherDataTWO.getTemperature();this .humidity=weatherDataTWO.getHumidity(); display(); } } }
模板方法(Template Method) 是一种行为设计模式。模板方法设计模式用于创建方法存根并将某些实现步骤推迟到子类。
模板方法定义了执行算法的步骤,它可以提供可能对所有或者部分子类通用的默认实现,下面通过一个简单的例子来理解这个模式,假设我们想提供一种算法了该房子,建造房屋需要执行的步骤是:建造地基->建造支柱->建造墙壁和窗户。
重点的一点是我们不能改变执行的顺序,因为我们不能在构建基础之前构建窗口,所以在这种情况下,我们可以创建一个模板方法,它将使用不同的方法来建造房子,现在盖房子的地基对于所有类型的房子都是一样的,无论是木房、玻璃房子还是混泥土房。
所以我们可以为此提供基础实现,如果子类想要覆盖这个方法,他们可以自己选择,但是大多数情况下,所有类型的房屋都很常见。为了确保子类不覆盖模板方法,我们应该将其设为最终方法。
由于我们希望某些方法由子类实现,因此我们必须将我们的基类设为抽象类。
定义抽象类HouseTemplate
public abstractclassHouseTemplate{/** * buildHouse()是模板方法,定义个执行几个步骤的执行顺序 * * template method, final so subclasses can't override final修饰,子类不能重写 */ publicfinalvoidbuildHouse(){//建造地基 buildFoundation();//建造柱子 buildPillars();//建造墙壁 buildWalls();//建造窗户 buildWindows(); System.out.println("House is built successfully" ); } privatevoidbuildFoundation(){ System.out.println("Building foundation with cement, iron rods and sand" ); }/** * methods to be implemented by subclasses */ publicabstractvoidbuildPillars(); publicabstractvoidbuildWalls();/** * default implementation */ privatevoidbuildWindows(){ System.out.println("Building Glass Windows" ); } }
WoodenHouse
package com.universal.core.designPatterns.templateMethod;/** * 木房 */ publicclassWoodenHouseextendsHouseTemplate{@Override publicvoidbuildPillars(){ System.out.println("Building Pillars With Wood coating..." ); }@Override publicvoidbuildWalls(){ System.out.println("Building Wooden Walls..." ); } }
GlassHouse
package com.universal.core.designPatterns.templateMethod;/** * 玻璃房 */ publicclassGlassHouseextendsHouseTemplate{@Override publicvoidbuildPillars(){ System.out.println("Building Pillars With Glass coating..." ); }@Override publicvoidbuildWalls(){ System.out.println("Building Glass Walls..." ); } }
ConcreteHouse
package com.universal.core.designPatterns.templateMethod;/** * 混泥土房屋 */ publicclassConcreteHouseextendsHouseTemplate{@Override publicvoidbuildPillars(){ System.out.println("Building Pillars With Concrete coating..." ); }@Override publicvoidbuildWalls(){ System.out.println("Building Concrete Walls..." ); } }
HousingClient
package com.universal.core.designPatterns.templateMethod; publicclassHousingClient{ publicstaticvoidmain(String[] args){ HouseTemplatehouseBuilder=newWoodenHouse(); houseBuilder.buildHouse(); System.out.println("--------------" ); houseBuilder =newGlassHouse(); houseBuilder.buildHouse(); System.out.println("--------------" ); houseBuilder =newConcreteHouse(); houseBuilder.buildHouse(); } }
输出结果:
Building foundation with cement,iron rods and sand BuildingPillarsWithWood coating... BuildingWoodenWalls... BuildingGlassWindows Houseis built successfully -------------- Building foundation with cement,iron rods and sand BuildingPillarsWithGlass coating... BuildingGlassWalls... BuildingGlassWindows Houseis built successfully -------------- Building foundation with cement,iron rods and sand BuildingPillarsWithConcrete coating... BuildingConcreteWalls... BuildingGlassWindows Houseis built successfully Process finished withexit code 0
适配器模式Adapter 是将一个接口转换成另一个客户所期望的接口。Adapter 适配器让那些本来因为接口不兼容的类可以合作无间。
适配器模式中的角色分析
目标接口(Traget):客户期待的接口,目标可以是具体的或者抽象的类,也可以是接口。 需要适配的对象(Source Adaptee):需要适配的对象。 适配器(Adapter):通过包装一个需要适配的对象,把原接口转成目标接口。 举个例子🌰:我们以网线上网为例,现在有一根水晶头网线,但是它的接口与电脑的不匹配(因为电脑的是usb或者typec),那么就需要一个转接头,也就是我们说的适配器,才能够上网,下面的转接头可以理解为适配器:
类适配器
首先我们拥有一根网线, 他有上网的功能,但是它的接口与电脑不匹配:
//要适配的类:网线 public class Adaptee { //功能:上网 public void request () { System.out.println("链接网线上网" ); } }
因此我们定义了一个usb接口,也就是上面提到的目标接口(Target):
//接口转换器的抽象实现 public interface NetToUsb { //作用:处理请求,网线 => usb public void handleRequest () ; }
定义一个适配器继承着网线,连接着usb接口:
//真正的适配器,余姚链接usb,连接网线 publicclassAdapterextendsAdapteeimplementsNetToUsb{@Override publicvoidhandleRequest(){//可以上网了 super .request(); } }
上网的具体实现:
//客户端类:想上网,插不上网线 publicclassComputer{//电脑需要连接上转接器才可以上网 publicvoidnet(NetToUsb adapter){//上网的具体实现:找一个转接头 adapter.handleRequest(); } publicstaticvoidmain(String[] args){//电脑,适配器,网线 Computercomputer=newComputer();//电脑 Adapteradapter=newAdapter();//转接器 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) 代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。
这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。对于一个进阶高级开发的技术人员来说,了解设计模式的理念和具体的实现颇为重要,本期内容分享就到这里了,如果感觉对你有帮助的,欢迎点赞👍+评论💬+收藏❤