委派模式和策略模式
委派模式
什么是委派模式巍实?
維基百科對(duì)委派模式的解釋是:委派模式(delegation pattern)是軟件設(shè)計(jì)模式中的一項(xiàng)基本技巧。在委派模式中呛占,有兩個(gè)對(duì)象參與處理同一個(gè)請(qǐng)求,接受請(qǐng)求的對(duì)象將請(qǐng)求委托給另一個(gè)對(duì)象來(lái)代理挨务。
委派模式的基本作用就是負(fù)責(zé)任務(wù)的調(diào)用和分配任務(wù)紧武,跟代理模式很像剃氧,可以看做是一種特殊情況下的靜態(tài)代理的全權(quán)代理,但是代理模式注重過(guò)程阻星,而委派模式注重結(jié)果朋鞍。委派模式在Spring中應(yīng)用非常多,大家常用的DispatcherServlet其實(shí)就是用到了委派模式⊥谆現(xiàn)實(shí)生活中也常有委派的場(chǎng)景發(fā)生滥酥。例如:教室要進(jìn)行大掃除,老師(Teacher)布置任務(wù)給班長(zhǎng)(Monitor)矾踱,班長(zhǎng)再根據(jù)任務(wù)分配給學(xué)生(Student)恨狈,我們以代碼的形式進(jìn)行模擬一下疏哗。
/**
* @author: Winston
* @createTime: 2021/6/24
*
* 老師
*/
public class Teacher {
/**
* 給班長(zhǎng)下發(fā)命令
* @param command
* @param monitor
*/
public void command(String command, Monitor monitor){
monitor.doing(command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
* 學(xué)生聽(tīng)指令的接口
*/
public interface IStudent {
/**
* 根據(jù)指令做事
* @param command
*/
void doing(String command);
}
/**
* @author: Winston
* @createTime: 2021/6/24
*
* 班長(zhǎng)
*/
public class Monitor implements IStudent{
/**
* map用來(lái)存放接收指令的學(xué)生呛讲,key是指令 map 是學(xué)生
*/
private Map<String,IStudent> studentMap = new HashMap<>();
public Monitor(){
studentMap.put("掃地",new StudentA());
studentMap.put("擦黑板",new StudentB());
}
/**
* 班長(zhǎng)不干活,根據(jù)指令給學(xué)生分派任務(wù)
* @param command
*/
@Override
public void doing(String command) {
studentMap.get(command).doing(command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class StudentA implements IStudent {
@Override
public void doing(String command) {
System.out.println("我是StudentA返奉,我大掃除的職責(zé)是,"+command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class StudentB implements IStudent {
@Override
public void doing(String command) {
System.out.println("我是StudentB贝搁,我大掃除的職責(zé)是,"+command);
}
}
測(cè)試類(lèi)
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class DelegateTest {
public static void main(String[] args) {
//客戶(hù)請(qǐng)求(Teacher)、委派者(Monitor)芽偏、被被委派者(Student)
//委派者要持有被委派者的引用
//代理模式注重的是過(guò)程雷逆, 委派模式注重的是結(jié)果
//策略模式注重是可擴(kuò)展(外部擴(kuò)展),委派模式注重內(nèi)部的靈活和復(fù)用
//委派的核心:就是分發(fā)污尉、調(diào)度膀哲、派遣
//委派模式:就是靜態(tài)代理和策略模式一種特殊的組合
new Teacher().command("掃地", new Monitor());
}
}
運(yùn)行結(jié)果:我是StudentA,我大掃除的職責(zé)是,掃地
委派模式在SpringMVC源碼中的體現(xiàn)
下面我們來(lái)簡(jiǎn)單模擬一下SpringMVC的DispatcherServlet是怎么實(shí)現(xiàn)委派模式的被碗。先創(chuàng)建幾個(gè)Controller
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class UserController {
public void findUserById(String id){
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class OrderController {
public void findOrderById(String id){
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class IndexController {
public void index(){
}
}
創(chuàng)建一個(gè)DispatcherServlet用于分發(fā)請(qǐng)求
/**
* 這個(gè)類(lèi)就相當(dāng)于是班長(zhǎng)角色
* @author: Winston
* @createTime: 2021/6/24
*/
public class DispatcherServlet extends HttpServlet {
private void doDispatcher(HttpServletRequest request, HttpServletResponse response){
String requestURI = request.getRequestURI();
String id = request.getParameter("id");
// 根據(jù)uri判斷分發(fā)請(qǐng)求到Controller
if("findUserById".equals(requestURI)){
new UserController().findUserById(id);
}else if("findOrderById".equals(requestURI)){
new OrderController().findOrderById(id);
}else if("index".equals(requestURI)){
new IndexController().index();
}else {
try {
response.getWriter().write("404 NOT FOUND");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
配置web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Gupao Web Application</display-name>
<servlet>
<servlet-name>delegateServlet</servlet-name>
<servlet-class>com.gupaoedu.vip.pattern.delegate.mvc.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>delegateServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
小結(jié)
一個(gè)模擬委派模式的簡(jiǎn)單DispatcherServlet就寫(xiě)完了某宪。Spring中有許多地方都用到了委派模式,我們可以通過(guò)命名來(lái)識(shí)別锐朴,只要是以Delegate結(jié)尾的都是事先了委派模式兴喂。例如:BeanDefinitionParserDelegate 根據(jù)不同類(lèi)型委派不同邏輯解析BeanDefinition。
策略模式
策略模式概念
首先了解一下策略模式的概念焚志,在策略模式(Strategy Pattern)中衣迷,一個(gè)類(lèi)的行為或其算法可以在運(yùn)行時(shí)更改。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式酱酬。
策略模式的應(yīng)用場(chǎng)景
1壶谒、假如系統(tǒng)中有很多類(lèi),而他們的區(qū)別僅僅在于他們的行為不同膳沽。
2汗菜、一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種泼差。
案例一
上面對(duì)策略模式的介紹都比較抽象不太好理解,舉個(gè)生活中的小例子呵俏,在我們進(jìn)行網(wǎng)上購(gòu)物時(shí)如果遇到一些活動(dòng)商家會(huì)給商品以某些形式降價(jià)堆缘,比如優(yōu)惠券、拼團(tuán)普碎、返現(xiàn)吼肥。下面我們通過(guò)用代碼來(lái)模擬一下。
首先創(chuàng)建一個(gè)促銷(xiāo)活動(dòng)Promotion接口麻车,所有的降價(jià)形式都要實(shí)現(xiàn)這個(gè)接口缀皱。
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 促銷(xiāo)活動(dòng)
*/
public interface Promotion {
/**
* 活動(dòng)內(nèi)容
*/
void activity();
}
無(wú)優(yōu)惠活動(dòng)的 EmptyActivity類(lèi)
public class EmptyActivity implements Promotion{
@Override
public void activity() {
System.out.println("無(wú)促銷(xiāo)活動(dòng)");
}
}
優(yōu)惠券活動(dòng)
public class CouponActivity implements Promotion {
@Override
public void activity() {
System.out.println("優(yōu)惠券立減20");
}
}
返現(xiàn)活動(dòng)
public class CashbackActivity implements Promotion {
@Override
public void activity() {
System.out.println("購(gòu)買(mǎi)立馬返現(xiàn)15");
}
}
組團(tuán)活動(dòng)
public class GroupActivity implements Promotion {
@Override
public void activity() {
System.out.println("兩人拼團(tuán)立減25");
}
}
促銷(xiāo)方案類(lèi)PromotionActivity
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 舉辦的促銷(xiāo)活動(dòng)
*/
public class PromotionActivity {
private Promotion promotion;
public PromotionActivity(Promotion promotion) {
this.promotion = promotion;
}
public void execute(){
promotion.activity();
}
}
測(cè)試類(lèi):
public class PromotionTest {
public static void main(String[] args) {
// 不同活動(dòng)對(duì)應(yīng)不同的優(yōu)惠策略,默認(rèn)策略是無(wú)優(yōu)惠
PromotionActivity promotionActivity = null;
String promotionKey = "anniversary";
if ("doubleEleven".equals(promotionKey)) {
// 雙十一活動(dòng)动猬,優(yōu)惠券
promotionActivity = new PromotionActivity(new CouponActivity());
} else if ("activity618".equals(promotionKey)) {
// 618啤斗,返現(xiàn)
promotionActivity = new PromotionActivity((new CashbackActivity()));
} else if ("anniversary".equals(promotionKey)) {
// 店慶,組團(tuán)
promotionActivity = new PromotionActivity((new GroupActivity()));
} else {
promotionActivity = new PromotionActivity(new EmptyActivity());
}
promotionActivity.execute();
}
}
從上面的測(cè)試代碼可以看出赁咙,如果又增加了一個(gè)促銷(xiāo)活動(dòng)钮莲,那么我們就又要加上新活動(dòng),增加if else彼水,相當(dāng)于又修改了業(yè)務(wù)代碼崔拥,那么又要進(jìn)行測(cè)試,如此一來(lái)工作量難免是重復(fù)且耗時(shí)的凤覆,下面我們結(jié)合工廠模式和單例模式對(duì)其進(jìn)行改造链瓦,在這里說(shuō)一下,設(shè)計(jì)模式一般都不是單獨(dú)出現(xiàn)的盯桦,而是結(jié)合使用慈俯。
創(chuàng)建一個(gè)存放活動(dòng)策略key的枚舉,PromotionEnum
/**
* @author: Winston
* @createTime: 2021/6/25|
*
* 促銷(xiāo)活動(dòng)策略的enum
*/
public enum PromotionEnum {
COUPON_ACTIVITY("優(yōu)惠券活動(dòng)","COUPON_ACTIVITY"),
CASHBACK_ACTIVITY("返現(xiàn)活動(dòng)","CASHBACK_ACTIVITY"),
GROUP_ACTIVITY("組團(tuán)活動(dòng)","GROUP_ACTIVITY")
;
/**
* 活動(dòng)名稱(chēng)
*/
private String activityName;
/**
* 活動(dòng)key
*/
private String promotionKey;
PromotionEnum(String activityName, String promotionKey) {
this.activityName = activityName;
this.promotionKey = promotionKey;
}
public String getActivityName() {
return activityName;
}
public void setActivityName(String activityName) {
this.activityName = activityName;
}
public String getPromotionKey() {
return promotionKey;
}
public void setPromotionKey(String promotionKey) {
this.promotionKey = promotionKey;
}
}
創(chuàng)建促銷(xiāo)策略工廠PromotionFactory
/**
* @author: Winston
* @createTime: 2021/6/25
* 促銷(xiāo)策略工廠
*/
public class PromotionFactory {
/**
* 活動(dòng)
*/
private static Promotion promotion;
/**
* 存放活動(dòng)策略的map拥峦,Key是活動(dòng)名稱(chēng)贴膘,value是具體活動(dòng)
*/
private static Map<String, Promotion> promotionMap = new HashMap<>();
/**
* 默認(rèn)策略,無(wú)優(yōu)惠
*/
private static final EmptyActivity emptyActivity = new EmptyActivity();
static {
promotionMap.put(PromotionEnum.COUPON_ACTIVITY.getPromotionKey(), new CouponActivity());
promotionMap.put(PromotionEnum.CASHBACK_ACTIVITY.getPromotionKey(), new CashbackActivity());
promotionMap.put(PromotionEnum.GROUP_ACTIVITY.getPromotionKey(), new GroupActivity());
}
/**
* 根據(jù)活動(dòng)key拿到對(duì)應(yīng)的活動(dòng)
* @param key
* @return
*/
public static Promotion getPromotionByKey(String key) {
promotion = promotionMap.get(key);
return promotion == null ? emptyActivity : promotion;
}
}
測(cè)試類(lèi):
public class OptimizePromotionTest {
public static void main(String[] args) {
Promotion promotion = PromotionFactory.getPromotionByKey(PromotionEnum.COUPON_ACTIVITY.getPromotionKey());
promotion.activity();
}
}
案例二
通過(guò)第一個(gè)案例事镣,我們對(duì)策略模式有了一點(diǎn)了解步鉴,下面再舉一個(gè)例子來(lái)加深對(duì)策略模式的印象。在我們點(diǎn)外賣(mài)進(jìn)行付款時(shí)璃哟,往往有多種支付方式氛琢,比如微信支付、支付寶支付随闪、銀行卡支付阳似、QQ支付等,這些支付方式的選擇其實(shí)也是策略模式的一種體現(xiàn)铐伴,下面我們通過(guò)代碼來(lái)進(jìn)行模擬撮奏。
創(chuàng)建一個(gè)Payment抽象類(lèi)俏讹,定義支付規(guī)范和支付邏輯:
/**
* @author: Winston
* @createTime: 2021/6/25
* <p>
* 支付工具中所具有的方法
*/
public abstract class Payment {
/**
* 支付類(lèi)型
*
* @return
*/
public abstract String getName();
/**
* 余額查詢(xún)
*/
protected abstract Double queryBalance();
/**
* 支付方法
*
* @param amount 支付金額
* @param balance 余額
*/
public PayState pay(Double amount, Double balance) {
if (balance >= amount) {
return new PayState(200, "支付成功");
} else {
return new PayState(500, "支付失敗,余額不足");
}
}
}
創(chuàng)建幾種支付方式畜吊,微信支付泽疆、支付寶支付、qq支付玲献、銀行卡支付
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class WechatPay extends Payment {
@Override
public String getName() {
return "微信支付";
}
@Override
protected Double queryBalance() {
return 14.43D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class AliPay extends Payment {
@Override
public String getName() {
return "支付寶支付";
}
@Override
protected Double queryBalance() {
return 561.24D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class QqPay extends Payment {
@Override
public String getName() {
return "qq支付";
}
@Override
protected Double queryBalance() {
return 5.43D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class CardPay extends Payment {
@Override
public String getName() {
return "銀行卡支付";
}
@Override
protected Double queryBalance() {
return 20051.15D;
}
}
創(chuàng)建一個(gè)支付狀態(tài)的包裝類(lèi)PayState
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class PayState {
/**
* 狀態(tài)碼
*/
private int code;
/**
* 狀態(tài)信息
*/
private String msg;
public PayState(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "PayState{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
創(chuàng)建支付策略管理類(lèi):
/**
* @author: Winston
* @createTime: 2021/6/25
* 支付策略管理
*/
public class PayStrategy {
public static final String ALI_PAY= "ALI_PAY";
public static final String WECHAT_PAY= "WECHAT_PAY";
public static final String CARD_PAY= "CARD_PAY";
public static final String QQ_PAY= "QQ_PAY";
public static final String DEFAULT_PAY= ALI_PAY;
// 存放支付策略的map
private static Map<String,Payment> paymentMap = new HashMap<>();
static {
paymentMap.put(ALI_PAY,new AliPay());
paymentMap.put(WECHAT_PAY,new WechatPay());
paymentMap.put(CARD_PAY,new CardPay());
paymentMap.put(QQ_PAY,new QqPay());
}
public static Payment get(String payKey){
if(!paymentMap.containsKey(payKey)){
return paymentMap.get(DEFAULT_PAY);
}
return paymentMap.get(payKey);
}
}
創(chuàng)建訂單類(lèi)殉疼,模擬下單支付時(shí)的操作
/**
* @author: Winston
* @createTime: 2021/6/25
* 訂單類(lèi)
*/
public class Order {
/**
* 訂單id
*/
private String id;
/**
* 訂單金額
*/
private Double amount;
public Order(String id, Double amount) {
this.id = id;
this.amount = amount;
}
public void pay() {
pay(PayStrategy.DEFAULT_PAY);
}
/**
* 使用指定支付方式支付
*
* @param payKey
*/
public void pay(String payKey) {
System.out.println("訂單編號(hào)是:" + this.id);
System.out.println("支付金額為:" + this.amount);
Payment payment = PayStrategy.get(payKey);
System.out.println("支付方式:" + payment.getName());
PayState pay = payment.pay(this.amount, payment.queryBalance());
System.out.println(pay);
}
}
測(cè)試類(lèi):
public class PaymentTest {
public static void main(String[] args) {
System.out.println("開(kāi)始下單點(diǎn)外賣(mài):");
Order order = new Order("10001", 21.31D);
order.pay(PayStrategy.WECHAT_PAY);
}
}
運(yùn)行結(jié)果:
開(kāi)始下單點(diǎn)外賣(mài):
訂單編號(hào)是:10001
支付金額為:21.31
支付方式:微信支付
PayState{code=500, msg='支付失敗,余額不足'}
策略模式在JDK源碼中的體現(xiàn)
Comparator接口中的compare()方法捌年,就是一個(gè)策略抽象實(shí)現(xiàn)瓢娜。
int compare(T o1, T o2);
Comparator 抽象下面有非常多的實(shí)現(xiàn)類(lèi),我們經(jīng)常會(huì)把 Comparator 作為參數(shù)傳入作
為排序策略礼预,例如 Arrays 類(lèi)的 parallelSort 方法等:
public class Arrays {
...
public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> cmp) {
...
}
...
}
還有TreeMap的構(gòu)造方法:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
...
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
...
}
策略模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 策略模式符合開(kāi)閉原則
- 避免使用多重條件轉(zhuǎn)移語(yǔ)句眠砾,如if...else...、switch
- 使用策略模式可以提高算法的保密性和安全性
缺點(diǎn):
- 客戶(hù)端必須知道所有的策略托酸,并且自行決定使用哪一個(gè)策略類(lèi)
- 代碼中會(huì)產(chǎn)生非常多的策略類(lèi)褒颈,增加維護(hù)難度
委派模式與策略模式綜合應(yīng)用
在委派模式中我們寫(xiě)了一個(gè)例子模仿了SpringMVC的DispatcherServlet,在那個(gè)例子中我們用了很多的if...else語(yǔ)句获高,在實(shí)際項(xiàng)目中Controller數(shù)量往往很大哈肖,如果都用if..else來(lái)寫(xiě)的話,那么不僅工作量大念秧,而且出錯(cuò)的可能性也很大,下面我們使用委派模式和策略模式將它進(jìn)行改造布疼。
創(chuàng)建一個(gè)Handler類(lèi)摊趾,用于存Method和url對(duì)應(yīng)的關(guān)系
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 保存Controller的一些信息,將url和method的關(guān)系綁定
*/
public class Handler {
/**
* controller的實(shí)例
*/
private Object controller;
/**
* 存放方法
*/
private Method method;
/**
* 對(duì)應(yīng)映射方法的url
*/
private String url;
public Handler(Object controller, Method method, String url) {
this.controller = controller;
this.method = method;
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
之前寫(xiě)過(guò)的三個(gè)controller
public class UserController {
public void findUserById(String id){
}
}
public class OrderController {
public void findOrderById(String id){
}
}
public class IndexController {
public void index(){
}
}
改造的servlet游两,重寫(xiě)init方法用于模擬在啟動(dòng)時(shí)將各個(gè)controller信息保存到Handler中去砾层,代碼如下:
/**
* 這個(gè)類(lèi)就相當(dāng)于是班長(zhǎng)角色
*
* @author: Winston
* @createTime: 2021/6/24
*/
public class DispatcherServlet extends HttpServlet {
// 初始化所有controller的信息存到handlerList中
public static List<Handler> handlerList = new ArrayList<>();
/**
* 模擬controller初始化時(shí)將信息存到handlerList中
*/
@Override
public void init() throws ServletException {
Class<UserController> userControllerClass = UserController.class;
Class<OrderController> orderControllerClass = OrderController.class;
Class<IndexController> indexControllerClass = IndexController.class;
try {
Handler userHandler = new Handler(userControllerClass.newInstance(),
userControllerClass.getMethod("findUserById", new Class[]{String.class}), "/web/findUserById");
Handler orderHandler = new Handler(orderControllerClass.newInstance(),
orderControllerClass.getMethod("findOrderById", new Class[]{String.class}), "/web/findOrderById");
Handler indexHandler = new Handler(indexControllerClass.newInstance(),
indexControllerClass.getMethod("index", null), "/web/index");
// 將handler添加到list中
handlerList.add(userHandler);
handlerList.add(orderHandler);
handlerList.add(indexHandler);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatcher(req, resp);
}
private void doDispatcher(HttpServletRequest request, HttpServletResponse response) {
// 獲取url
String requestURI = request.getRequestURI();
// 要執(zhí)行的handler
Handler handler = null;
// 根據(jù)url獲取handler
for (Handler h : handlerList) {
if(h.getUrl().equals(requestURI)){
handler = h;
break;
}
}
if(handler == null){
try {
response.getWriter().write("Not found 404!");
} catch (IOException e) {
e.printStackTrace();
}
return;
}
// 執(zhí)行方法的結(jié)果
Object result = null;
try {
// 根據(jù)獲取到的執(zhí)行器利用反射執(zhí)行方法,實(shí)際沒(méi)那么簡(jiǎn)單贱案,這里簡(jiǎn)單模擬不用深究
result = handler.getMethod().invoke(handler.getController(),request.getParameter("id"));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 將結(jié)果返回
try {
response.getWriter().write(result.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}