一. 設計模式概述
1.爲什麼使用設計模式?
使用設計模式可以重構整體架構代碼、提交代碼複用性、擴展性、減少代碼冗餘問題。Java高級工程師必備的技能!
2.設計模式六大原則:
① 開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。
② 里氏代換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。
③ 依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。
④ 接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。
⑤ 迪米特法則(最少知道原則)(Demeter Principle)
爲什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
⑥ 合成複用原則(Composite Reuse Principle)
原則是儘量使用合成/聚合的方式,而不是使用繼承。
3. 什麼是策略模式?
策略模式是對算法的包裝,是把使用算法的責任和算法本身分割開來,委派給不同的對象管理,最終可以解決多重if判斷問題。
①.環境(Context)角色:持有一個Strategy的引用。
②.抽象策略(Strategy)角色:這是一個抽象角色,通常由一個接口或抽象類實現。此角色給出所有的具體策略類所需的接口。
③.具體策略(ConcreteStrategy)角色:包裝了相關的算法或行爲,即實現類。
爲什麼叫做策略模式? 每個if判斷都可以理解爲就是一個策略。
4.策略模式優缺點
優點:算法可以自由切換(高層屏蔽算法,角色自由切換),避免使用多重條件判斷(如果算法過多就會出現很多種相同的判斷,很難維護),擴展性好(可自由添加取消算法 而不影響整個功能)
缺點:策略類數量增多(每一個策略類複用性很小,如果需要增加算法,就只能新增類),所有的策略類都需要對外暴露(使用的人必須瞭解使用策略,這個就需要其它模式來補充,比如工廠模式、代理模式)
二. 策略模式案例分析
聚合支付平臺:搭建聚合支付平臺的時候,這時候需要對接很多第三方支付接口,比如支付寶、微信支付、小米支付等,通過傳統if代碼判斷的,後期的維護性非常差!
這時候可以通過策略模式解決多重if判斷問題。
架構圖:
1. 基於枚舉+工廠方式實現策略模式
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>
</dependencies>
① 創建抽象策略(接口,共同行爲)
package com.example.strategy;
public interface PayStrategy {
// 共同算法實現骨架
String toPay();
}
② 創建策略實現
package com.example.strategy.impl;
import com.example.strategy.PayStrategy;
import org.springframework.stereotype.Component;
@Component
public class AliPayStrategy implements PayStrategy {
public String toPay() {
return "調用支付寶支付接口";
}
}
package com.example.strategy.impl;
import com.example.strategy.PayStrategy;
public class WxPayStrategy implements PayStrategy {
public String toPay() {
return "調用微信支付接口";
}
}
③ 定義枚舉(存放每個測了測實現 - class路徑)
package com.example.context;
public enum PayEnum {
// 支付寶支付
ALI_PAY("com.example.strategy.impl.AliPayStrategy"),
// 微信支付
WX_PAY("com.example.strategy.impl.WxPayStrategy");
PayEnum(String className) {
this.setClassName(className);
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
// class完整地址
private String className;
}
④ 定義策略工廠
package com.example.factory;
import com.example.context.PayEnum;
import com.example.strategy.PayStrategy;
public class StrategyFactory {
//使用策略工廠獲取具體策略實現
public static PayStrategy getPayStrategy(String strategyType) {
try {
// 1.獲取枚舉中className
String className = PayEnum.valueOf(strategyType).getClassName();
// 2.使用java反射技術初始化類
return (PayStrategy) Class.forName(className).newInstance();
} catch (Exception e) {
return null;
}
}
}
⑤ 定義上下文
package com.example.context;
import com.example.factory.StrategyFactory;
import com.example.strategy.PayStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class PayContext {
public String toPay(String payCode){
// 1.驗證參數
if(StringUtils.isEmpty(payCode)){
return "payCode不能爲空!";
}
// 2.使用策略工廠獲取具體的策略實現
PayStrategy payStrategy = StrategyFactory.getPayStrategy(payCode);
if(payStrategy==null){
return "沒有找到具體策略的實現...";
}
return payStrategy.toPay();
}
}
⑥ 編寫controller進行測試
package com.example.controller;
import com.example.context.PayContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
@Autowired
private PayContext payContext;
@RequestMapping("/toPay")
public String toPay(String payCode){
return payContext.toPay(payCode);
}
}
2. 基於數據庫實現策略模式
枚舉方式,具體的策略實現都沒有交給Spring管理,所以現在想全交給Spring進行管理,棄用工廠
準備工作:如圖建立一張支付渠道表,關鍵爲channel_id和strategy_bean_id,即和上面枚舉的key-value相似
具體實現:
① 首先需要把策略實現注入到spring容器中
② 此時直接棄用枚舉和工廠,直接修改context上下文代碼:
package com.example.context;
import com.example.dao.PaymentDao;
import com.example.model.PaymentDo;
import com.example.strategy.PayStrategy;
import com.example.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class PayContext {
@Autowired
private PaymentDao paymentDao;
public String toPay(String payCode){
// 1.驗證參數
if(StringUtils.isEmpty(payCode)){
return "payCode不能爲空!";
}
// 1.查詢數據庫獲取具體策略實現
PaymentDo paymentDo = paymentDao.getPaymentChannel(payCode);
if (paymentDo == null) {
return "沒有查詢到支付渠道";
}
System.out.println(paymentDo);
// 獲取spring注入的bean的id
String strategyBeanId = paymentDo.getStrategyBeanId();
if (StringUtils.isEmpty(strategyBeanId)) {
return "數據庫沒有配置strategyBeanId";
}
PayStrategy payStrategy = SpringUtils.getBean(strategyBeanId, PayStrategy.class);
return payStrategy.toPay();
}
}
③ 編寫yml配置
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
mybatis:
configuration:
mapUnderscoreToCamelCase: true
測試一下,和基於枚舉+工廠效果一樣: