平時我們寫代碼呢秋忙,多數(shù)情況都是流水線式寫代碼 基本就可以實現(xiàn)業(yè)務邏輯了。如何在寫代碼中找到樂趣呢铭若,我覺得,好的方式就是:使用設計模式優(yōu)化自己的代碼递览。今天記錄下日常工作中叼屠,我都使用過哪些設計模式。
1.策略模式:
假設有這樣的業(yè)務場景绞铃,掃碼登錄:根據(jù)移動端掃碼動作采取不同的邏輯镜雨。
示例:
/**
* 如果是依靠if else來寫這個場景:
* 如果分支變多,這里的代碼就會變得臃腫儿捧,難以維護荚坞,可讀性低。
* 如果你需要接入一種新的解析類型菲盾,那只能在原有代碼上修改颓影。
* 說得專業(yè)一點的話,就是if else代碼懒鉴,違背了面向對象編程的開閉原則以及單一原則诡挂。
* 如果你的代碼就是醬紫:有多個if...else等條件分支,并且每個條件分支,可以封裝起來替換的璃俗,我們就可以使用策略模式來優(yōu)化奴璃。
*/
/**
* 策略模式醬紫實現(xiàn)的: 一個接口或者抽象類,里面兩個方法(一個方法匹配類型旧找,一個可替換的邏輯實現(xiàn)方法),和不同策略的差異化實現(xiàn)溺健。
*/
public abstract class AbstractWebLoginStrategyService {
// 屬于哪種動作類型
abstract WebLoginStrategyEnum getType();
// 封裝的公用算法(具體的動作邏輯)
public abstract void userLogin();
}
/**
* @author xx
* @since 不同策略的差異化實現(xiàn):掃碼動作
*/
@Service
public class ScanQrServiceImpl extends AbstractWebLoginStrategyService {
@Autowired
public ScanQrServiceImpl() {}
@Override
public WebLoginStrategyEnum getType() {
return WebLoginStrategyEnum.SCAN_QR;
}
@Override
public void userLogin() {
System.out.println("二維碼狀態(tài)變更為已掃碼");
}
}
/**
* @author xx
* @since 不同策略的差異化實現(xiàn): 確認登錄動作
*/
@Service
public class ConfirmLoginServiceImpl extends AbstractWebLoginStrategyService {
@Override
public WebLoginStrategyEnum getType() {
return WebLoginStrategyEnum.CONFIRM_LOGIN;
}
@Override
public void userLogin() {
System.out.println("二維碼狀態(tài)變更為已登錄");
}
}
/**
* @author xx
* @since 不同策略的差異化實現(xiàn):取消掃碼動作
*/
@Service
public class CancelLoginServiceImpl extends AbstractWebLoginStrategyService {
@Override
public WebLoginStrategyEnum getType() {
return WebLoginStrategyEnum.CANCEL_LOGIN;
}
@Override
public void userLogin() {
System.out.println("二維碼狀態(tài)變更為已失效");
}
}
/**
* 工廠模式與策略模式配合使用
*/
@Component
public class WebLoginStrategyFactory{
private static final Map<WebLoginStrategyEnum, AbstractWebLoginStrategyService> STRATEGY_SERVICE_MAP = new HashMap<>();
/**
* 把對應的策略初始化到map中
* 借助spring的特性定義的工廠模式
* 一般情況下工廠與其它模式配合使用,如當前的策略模式
* @param strategyServices 所有注入的實現(xiàn)類
*/
@Autowired
public WebLoginStrategyFactory(List<AbstractWebLoginStrategyService> strategyServices) {
Map<WebLoginStrategyEnum, AbstractWebLoginStrategyService> itemDataMap = strategyServices.stream().collect(Collectors.toMap(AbstractWebLoginStrategyService::getType, service -> service));
STRATEGY_SERVICE_MAP.putAll(itemDataMap);
}
public AbstractWebLoginStrategyService getService(WebLoginStrategyEnum strategyEnum){
return STRATEGY_SERVICE_MAP.get(strategyEnum);
}
}
/**
* 動作枚舉
*/
public enum WebLoginStrategyEnum {
SCAN_QR(1,"移動端掃描PC二維碼"),
CONFIRM_LOGIN(2,"移動端確認登錄"),
CANCEL_LOGIN(3,"移動端取消登錄"),
;
WebLoginStrategyEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
private Integer code;
private String message;
/**
* Gets code.
*
* @return the code
*/
public Integer getCode() {
return code;
}
/**
* Gets message.
*
* @return the message
*/
public String getMessage() {
return message;
}
/**
* Get by code enable enum.
*
* @param code the code
* @return the enable enum
*/
public static WebLoginStrategyEnum getByCode(Integer code) {
if (code == null) {
return null;
}
return Arrays.stream(WebLoginStrategyEnum.values())
.filter(goodsTypeEnums -> goodsTypeEnums.getCode() == code).findFirst().orElse(null);
}
}
/**
* 策略模式使用
*/
@RequestMapping("/strategy")
public void strategy(@RequestParam(value = "qrCode") Integer type){
AbstractWebLoginStrategyService service = strategyFactory.getService(WebLoginStrategyEnum.getByCode(type));
service.userLogin();
}
2.責任鏈模式:
我們來看一個常見的業(yè)務場景钮蛛,下訂單接口基本的邏輯鞭缭,一般有參數(shù)非空校驗、安全校驗魏颓、黑名單校驗岭辣、規(guī)則攔截等等..,當你想要讓一個以上的對象有機會能夠處理某個請求的時候,就使用責任鏈模式甸饱。
示例:
/**
* 責任鏈模式實際上是一種處理請求的模式沦童,它讓多個處理器(對象節(jié)點)都有機會處理該請求,直到其中某個處理成功為止叹话。責任鏈模式把多個處理器串成鏈偷遗,然后讓請求在鏈上傳遞。
* 責任鏈的使用:1.一個接口或者抽象類驼壶,2.每個對象差異化處理氏豌,3.對象鏈(數(shù)組)初始化(連起來)
*/
public abstract class AbstractHandler {
//責任鏈中的下一個對象
private AbstractHandler nextHandler;
/**
* 責任鏈的下一個對象
*/
public void setNextHandler(AbstractHandler nextHandler){
this.nextHandler = nextHandler;
}
/**
* 具體參數(shù)攔截邏輯,給子類去實現(xiàn)
*/
public void filter(String param) {
boolean b = doFilter(param);
if(!b){
throw new RuntimeException("檢查不通過");
}
if (getNextHandler() != null) {
getNextHandler().filter(param);
}
}
public AbstractHandler getNextHandler() {
return nextHandler;
}
abstract boolean doFilter(String param);
}
/**
* 差異化處理:黑名單校驗對象
*/
@Component
@Order(3) //校驗順序排第3
public class CheckBlackFilterObject extends AbstractHandler {
@Override
public boolean doFilter(String param) {
//invoke black list check
System.out.println("校驗黑名單");
return true;
}
}
@Component
@Order(1) //順序排第1,最先校驗
public class CheckParamFilterObject extends AbstractHandler {
@Override
public boolean doFilter(String param) {
System.out.println("非空參數(shù)檢查");
return true;
}
}
@Component
@Order(2) //校驗順序排第2
public class CheckSecurityFilterObject extends AbstractHandler {
@Override
public boolean doFilter(String param) {
//invoke Security check
System.out.println("安全調用校驗");
return true;
}
}
@Component
@Order(4) //校驗順序排第4
public class CheckRuleFilterObject extends AbstractHandler {
@Override
public boolean doFilter(String param) {
//check rule
System.out.println("check rule");
return true;
}
}
/**
* 責任鏈服務: 對象鏈連起來(初始化)&& 使用
*/
@Component("ChainPatternService")
public class ChainPatternService {
//自動注入各個責任鏈的對象
@Autowired
private List<AbstractHandler> abstractHandleList;
private AbstractHandler abstractHandler;
//spring注入后自動執(zhí)行热凹,責任鏈的對象連接起來
@PostConstruct
public void initializeChainFilter(){
for(int i = 0;i<abstractHandleList.size();i++){
if(i == 0){
abstractHandler = abstractHandleList.get(0);
}else{
AbstractHandler currentHander = abstractHandleList.get(i - 1);
AbstractHandler nextHander = abstractHandleList.get(i);
currentHander.setNextHandler(nextHander);
}
}
}
//直接調用這個方法使用
public void exec(String param) {
abstractHandler.filter(param);
}
}
/**
* 責任鏈模式:執(zhí)行
*/
@RequestMapping("/responsibility")
public void responsibility(){
String param = "";
chainPatternService.exec(param);
System.out.println("執(zhí)行具體業(yè)務");
}
3.模板方法模式:
若一些通用的方法卻在每一個子類都重復寫了一遍, 則用模板方法模式優(yōu)化泵喘。
示例:
/**
* 假設我們有這么一個業(yè)務場景:內部系統(tǒng)不同商戶,調用我們系統(tǒng)接口般妙,去跟外部第三方系統(tǒng)交互(http方式)
* 會經歷這幾個流程:查詢商戶信息->對請求報文加簽->發(fā)送http請求出去->對返回的報文驗簽
* 這里纪铺,有的商戶可能是走代理請求出去的,有的是走直連請求碟渺。
* 那么除了發(fā)送請求是不一樣 其它的(查詢商戶信息,對請求報文加密,對返回的報文驗簽)都是重復的鲜锚。
* 那就把重復的代碼用模板方法模式優(yōu)化。
*/
/**
* 模板方法使用:一個骨架流程抽象類(抽象方法放一起)->確定的共同方法步驟放到抽象類(去除抽象方法標記)->不確定的步驟給子類去差異化實現(xiàn)苫拍。
* 抽象類定義骨架流程(查詢商戶信息烹棉,加簽,http請求怯疤,驗簽)
*/
public abstract class AbstractMerchantService {
public void queryMerchantInfo(){
System.out.println("查詢商戶信息");
}
public void signature(){
System.out.println("加簽");
}
public void httpRequest(){ System.out.println("http 請求");}
public void verifySinature(){ System.out.println("驗簽");}
//模板方法流程
String handlerTempPlate(String req){
//查詢商戶信息
queryMerchantInfo();
//加簽
signature();
//http 請求
httpRequest();
// 驗簽
verifySinature();
return "result:".concat(req);
}
// Http是否走代理(提供給子類實現(xiàn))
abstract boolean isRequestByProxy();
}
@Component
public class CompanyAServiceImpl extends AbstractMerchantService {
public String hander(String req){
return handlerTempPlate(req);
}
// 走http代理的
public boolean isRequestByProxy(){
System.out.println("差異化實現(xiàn) true");
return true;
}
}
@Component
public class CompanyBServiceImpl extends AbstractMerchantService {
public String hander(String req) {
return handlerTempPlate(req);
}
// 不走代理的
public boolean isRequestByProxy() {
System.out.println("差異化實現(xiàn) false");
return false;
}
}
/**
* 模板方法模式使用
*/
@RequestMapping("/templateMethod")
public void templateMethod(){
companyBServiceImpl.hander("參數(shù)1");
companyBServiceImpl.isRequestByProxy();
companyAServiceImpl.hander("參數(shù)2");
companyAServiceImpl.isRequestByProxy();
}
2.觀察者模式:
使用場景: 完成某件事情后浆洗,異步通知場景。如登陸成功或注冊成功集峦,發(fā)個IM消息等等伏社。
示例1:
/**
* 觀察者模式使用: 一個被觀察者的類Observerable->多個觀察者Observer->觀察者的差異化實現(xiàn)抠刺。
*/
/**
* 一個被觀察的類
*/
@Component
public class Observerable {
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
notifyAllObservers(state);
}
//添加觀察者
public Observerable addServer(Observer observer){
observers.add(observer);
return this;
}
//移除觀察者
public void removeServer(Observer observer){
observers.remove(observer);
}
//通知
public void notifyAllObservers(int state){
if(state != 1){
System.out.println("不是通知的狀態(tài)");
return ;
}
for (Observer observer : observers) {
observer.doEvent();
}
}
}
/**
* 觀察者
*/
interface Observer {
void doEvent();
}
//Im消息
public class EmailObserver implements Observer{
public void doEvent(){
System.out.println("發(fā)送Email消息");
}
}
//Im消息
public class IMMessageObserver implements Observer{
public void doEvent(){
System.out.println("發(fā)送IM消息");
}
}
//Im消息
public class MobileNoObserver implements Observer{
public void doEvent(){
System.out.println("發(fā)送短信消息");
}
}
/**
* 觀察者模式
*/
@RequestMapping("/observer")
public void observer(){
// 沒必要鏈式注入,其實可以考慮利用spring來初始化進觀察者的。
observerableService.addServer(new EmailObserver())
.addServer(new IMMessageObserver())
.addServer(new MobileNoObserver());
observerableService.setState(1);
}
示例2:
/**
* 經典觀察者模式封裝:EventBus實戰(zhàn)
* 自己寫一套觀察者模式的代碼摘昌,還是有點小麻煩速妖。實際上,Guava EventBus就封裝好了聪黎,它提供一套基于注解的事件總線罕容,api可以靈活的使用。
* <!--guava依賴-->
* <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
* <dependency>
* <groupId>com.google.guava</groupId>
* <artifactId>guava</artifactId>
* <version>22.0</version>
* </dependency>
*/
/**
* 類似被觀察者
*/
public class EventBusCenter {
private static EventBus eventBus = new EventBus();
private EventBusCenter() {}
public static EventBus getInstance() {
return eventBus;
}
//添加觀察者
public static void register(Object obj) {
eventBus.register(obj);
}
//移除觀察者
public static void unregister(Object obj) {
eventBus.unregister(obj);
}
//把消息推給觀察者
public static void post(Object obj) {
eventBus.post(obj);
}
}
/**
* 觀察者
*/
public class EventListener {
@Subscribe //加了訂閱稿饰,這里標記這個方法是事件處理方法
public void handle(NotifyEvent notifyEvent) {
System.out.println("發(fā)送IM消息" + notifyEvent.getImNo());
System.out.println("發(fā)送短信消息" + notifyEvent.getMobileNo());
System.out.println("發(fā)送Email消息" + notifyEvent.getEmailNo());
}
}
/**
* 通知事件類
*/
public class NotifyEvent {
private String mobileNo;
private String emailNo;
private String imNo;
public NotifyEvent(String mobileNo, String emailNo, String imNo) {
this.mobileNo = mobileNo;
this.emailNo = emailNo;
this.imNo = imNo;
}
public String getMobileNo() {
return mobileNo;
}
public String getEmailNo() {
return emailNo;
}
public String getImNo() {
return imNo;
}
}
/**
* 觀察者模式之EventBus實戰(zhàn)
*/
@RequestMapping("/observerEventBus")
public void observerEventBus(){
EventListener eventListener = new EventListener();
EventBusCenter.register(eventListener);
EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));
}
2.單例模式:
業(yè)務場景: 保證一個類僅有一個實例并提供一個訪問它的全局訪問點锦秒。I/O與數(shù)據(jù)庫的連接,一般就用單例模式實現(xiàn)的。Windows里面的Task Manager(任務管理器)也是很典型的單例模式喉镰。
示例:
/**
* 餓漢式: 實例在初始化的時候就已經建好了旅择,不管你后面有沒有用到,都先新建好實例再說侣姆。這個就沒有線程安全的問題生真,但是呢,浪費內存空間捺宗。
* 不像懶漢式不加 synchronized 就會存在安全問題柱蟀。(這里對于懶漢式示例,懶的寫 ...)
*/
public class EHanSingleton {
private static EHanSingleton instance = new EHanSingleton();
private EHanSingleton(){}
public static EHanSingleton getInstance() {
return instance;
}
}
/**
* 雙重鎖:綜合了懶漢式和餓漢式兩者的優(yōu)缺點,在synchronized內外都加了一層if條件,這樣既保證了線程安全,又比直接上鎖提高了執(zhí)行效率,還節(jié)省了內存空間。
*/
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() { }
public static DoubleCheckSingleton getInstance(){
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
/**
* 靜態(tài)內部類實現(xiàn)方式:效果有點類似雙重鎖蚜厉。但這種方式只適用于靜態(tài)域場景产弹。
*/
public class InnerClassSingleton {
private static class InnerClassSingletonHolder{
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance(){
return InnerClassSingletonHolder.INSTANCE;
}
}
/**
* 枚舉:代碼簡潔清晰。并且它還自動支持序列化機制弯囊,絕對防止多次實例化。
*/
public enum SingletonEnum {
INSTANCE;
public static SingletonEnum getInstance(){
return INSTANCE;
}
}
/**
* 單例模式使用
*/
@RequestMapping("/singleton")
public void singleton(){
EHanSingleton instance = EHanSingleton.getInstance();
EHanSingleton instance1 = EHanSingleton.getInstance();
System.out.println("餓漢式單例:" + Objects.equals(instance, instance1));
DoubleCheckSingleton instance2 = DoubleCheckSingleton.getInstance();
DoubleCheckSingleton instance3 = DoubleCheckSingleton.getInstance();
System.out.println("雙重鎖單例:" + Objects.equals(instance2, instance3));
InnerClassSingleton instance4 = InnerClassSingleton.getInstance();
InnerClassSingleton instance5 = InnerClassSingleton.getInstance();
System.out.println("靜態(tài)內部類單例:" + Objects.equals(instance4, instance5));
SingletonEnum instance6 = SingletonEnum.getInstance();
SingletonEnum instance7 = SingletonEnum.getInstance();
System.out.println("枚舉單例:" + Objects.equals(instance6, instance7));
}