1、为什么要使用设计模式
使用设计模式可以重构整体架构代码、提交代码复用性、扩展性、减少代码冗余问题。Java高级工程师必备的技能!
2、什么是策略模式
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。
1、环境(Context)角色:持有一个Strategy的引用。
2、抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
3、具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
定义策略接口->实现不同的策略类->利用多态或其他方式调用策略。
3、策略模式应用场景
在搭建聚合支付平台时,需要对接很多第三方的支付接口,比如支付宝支付、微信支付、银联支付等,如果使用传统的if判断代码,这对于后期的项目维护性是非常差的。
public String toPayHtml(String payCode){if(payCode.equals("ali_pay")){return "调用支付宝接口...";}if(payCode.equals("union_pay")){return "调用银联支付接口";}if(payCode.equals("weChat_pay")){return "调用微信支付接口...";}return "未找到该接口...";}
所以在这个时候就可以使用策略模式来解决这样的多重if判断问题。
4、策略模式架构图
5、策略模式环境搭建
maven依赖信息
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version></parent><dependencies><!-- sprinboot web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.1.1</version></dependency><!-- mysql 依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency></dependencies>
PayStrategy(抽象角色)
/*** @Classname PayStrategy* @Description 共同算法的定义骨架* @Date /5/14 15:01* @Created by mark*/public interface PayStrategy {String toPayHtml();}
ConcreteStrategy (具体实现角色)
/*** @Classname AliPayStrategy* @Description 支付宝支付* @Date /5/14 15:04* @Created by mark*/@Componentpublic class AliPayStrategy implements PayStrategy {@Overridepublic String toPayHtml() {return "调用支付宝支付接口...";}}
/*** @Classname UnionPayStrategy* @Description 银联支付* @Date /5/14 15:05* @Created by mark*/@Componentpublic class UnionPayStrategy implements PayStrategy {@Overridepublic String toPayHtml() {return "调用银联支付接口...";}}
/*** @Classname WeChatPayStrategy* @Description 微信支付* @Date /5/14 15:04* @Created by mark*/@Componentpublic class WeChatPayStrategy implements PayStrategy {@Overridepublic String toPayHtml() {return "调用微信支付接口...";}}
PayContextStrategy (上下文)
/*** @Classname PayContextStrategy* @Description 上下文* @Date /5/14 15:06* @Created by mark*/@Componentpublic class PayContextStrategy {@Autowiredprivate PaymentChannelMapper paymentChannelMapper;public String toPayHtml(String payCode){//1、参数验证if(StringUtils.isEmpty(payCode)){return BaseReturnInfo.PAYCODE_IS_BLANK;}//2、使用payCode查询PaymentChannelEntity paymentChannel = paymentChannelMapper.getPaymentChannel(payCode);if(paymentChannel == null){return BaseReturnInfo.PAYMENTCHANNEL_IS_NULL;}//3、获取策略的beanidString strategyBeanId = paymentChannel.getStrategyBeanId();if (StringUtils.isEmpty(strategyBeanId)) {return BaseReturnInfo.STRATEGYBEANID_IS_BLANK;}//4、根据beanid在spring容器中查找到对应的beanPayStrategy payStrategy = SpringUtils.getBean(strategyBeanId, PayStrategy.class);//5、执行具体的策略return payStrategy.toPayHtml();}}
SpringUtils
@Componentpublic class SpringUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}//获取applicationContextpublic static ApplicationContext getApplicationContext() {return applicationContext;}//通过name获取 Bean.public static Object getBean(String name){return getApplicationContext().getBean(name);}//通过class获取Bean.public static <T> T getBean(Class<T> clazz){return getApplicationContext().getBean(clazz);}//通过name,以及Clazz返回指定的Beanpublic static <T> T getBean(String name,Class<T> clazz){return getApplicationContext().getBean(name, clazz);}}
BaseReturnInfo
/*** @Classname BaseReturnInfo* @Description TODO* @Date /5/14 15:29* @Created by mark*/public final class BaseReturnInfo {public static final String PAYMENTCHANNEL_IS_NULL="没有该渠道信息";public static final String STRATEGYBEANID_IS_BLANK="该渠道没有配置beanid";public static final String PAYCODE_IS_BLANK="渠道code不能为空";}
数据库访问层
sql脚本:
/*Navicat MySQL Data TransferSource Server : localhostSource Server Version : 50539Source Host : localhost:3306Source Database : design_patternTarget Server Type : MYSQLTarget Server Version : 50539File Encoding : 65001Date: -05-14 16:30:37*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for payment_channel-- ----------------------------DROP TABLE IF EXISTS `payment_channel`;CREATE TABLE `payment_channel` (`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',`CHANNEL_NAME` varchar(32) NOT NULL COMMENT '渠道名称',`CHANNEL_ID` varchar(32) NOT NULL COMMENT '渠道ID',`strategy_bean_id` varchar(255) DEFAULT NULL COMMENT '策略执行beanid',PRIMARY KEY (`ID`,`CHANNEL_ID`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='支付渠道 ';-- ------------------------------ Records of payment_channel-- ----------------------------INSERT INTO `payment_channel` VALUES ('4', '支付宝渠道', 'ali_pay', 'aliPayStrategy');INSERT INTO `payment_channel` VALUES ('5', '银联支付渠道', 'union_pay', 'unionPayStrategy');INSERT INTO `payment_channel` VALUES ('6', '微信支付渠道', 'wechat_pay', 'weChatPayStrategy');
Entity
@Datapublic class PaymentChannelEntity {/** ID */private Integer id;/** 渠道名称 */private String channelName;/** 渠道ID */private String channelId;/*** 策略执行beanId*/private String strategyBeanId;}
Mapper
public interface PaymentChannelMapper {@Select("SELECT id as id ,CHANNEL_NAME as CHANNELNAME ,CHANNEL_ID as CHANNELID,strategy_bean_id AS strategybeanid\n" +"FROM payment_channel where CHANNEL_ID=#{payCode}")PaymentChannelEntity getPaymentChannel(String payCode);}
controller
@RestControllerpublic class PayController {@Autowiredprivate PayContextStrategy payContextStrategy;@RequestMapping("/toPayHtml")public String toPayHtml(String payCode) {return payContextStrategy.toPayHtml(payCode);}}
application.yml
###服务启动端口号server:port: 8080spring:###数据库相关连接datasource:username: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/design_pattern?useUnicode=true&characterEncoding=UTF-8####打印MyBatias日志 logging:level:### 开发环境使用DEBUG 生产环境info或者errorcom.xwhy.mapper: DEBUG
启动类
/*** @Classname StrategyApp* @Description TODO* @Date /5/14 15:00* @Created by mark*/@SpringBootApplication@MapperScan("com.xwhy.mapper")public class StrategyApp {public static void main(String[] args) {SpringApplication.run(StrategyApp.class,args);}}
启动效果截图:
6、总结
优点
算法可以自由切换(高层屏蔽算法,角色自由切换)
避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)
扩展性好(可自由添加取消算法 而不影响整个功能)
缺点
策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)
所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)