自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文分清Java開發(fā)中容易混淆的四大設(shè)計模式

原創(chuàng)
開發(fā) 前端
為什么先說工廠模式呢?因?yàn)椤肮S”這個概念在我們代碼中到處都體現(xiàn)著,很多設(shè)計模式也離不開它,以它作為基礎(chǔ)演進(jìn),所以先要學(xué)習(xí)它。什么是“工廠”?就像我們生活中的工廠是一樣的,就是生產(chǎn)某些“物品”的地方。

作者 | 蔡柱梁

可能很多人認(rèn)為設(shè)計模式只有面試時用到,這也不能算錯吧。但是如果僅僅只是面試時背背八股文,在實(shí)際工作中遇到了應(yīng)該使用,卻不知道要用,那么你的代碼能有多好也是自欺欺人的了。那么什么時候應(yīng)該使用設(shè)計模式呢?

換個角度說吧,大家覺得設(shè)計模式是怎么出來的?其實(shí)就是大牛們寫代碼多了,覺得一些高度重復(fù)或相似的地方,可以做到更好的“高內(nèi)聚”,“低耦合”。他們不斷改進(jìn),然后對他們的這些設(shè)計思路進(jìn)行總結(jié),最后得到的就是我們現(xiàn)在的設(shè)計模式。本文就給大家介紹幾個常用的設(shè)計模式。

1.工廠模式

為什么先說工廠模式呢?因?yàn)椤肮S”這個概念在我們代碼中到處都體現(xiàn)著,很多設(shè)計模式也離不開它,以它作為基礎(chǔ)演進(jìn),所以先要學(xué)習(xí)它。什么是“工廠”?就像我們生活中的工廠是一樣的,就是生產(chǎn)某些“物品”的地方。

在 Java 代碼中,我們用各式各樣的對象做各種事情,而這個過程中,我們往往是不關(guān)心創(chuàng)建過程的,僅僅關(guān)注它有那些方法可使用,提供了什么功能。這時,我們可以使用工廠模式進(jìn)行解耦——創(chuàng)建的行為放在工廠里,而使用的人專注于使用工廠產(chǎn)生的工具。在下面的模板方法、策略模式、適配器模式中,都能看到工廠模式的身影。

我們所說的工廠模式一般有兩種:

  • 工廠方法
  • 抽象工廠

(1)工廠方法

工廠方法模式是一種創(chuàng)建型設(shè)計模式, 其在父類中提供一個創(chuàng)建對象的方法, 允許子類決定實(shí)例化對象的類型。

工廠方法的具體實(shí)現(xiàn)思路是:

  • 制定一個創(chuàng)建對象 A 的接口工廠
  • 這個工廠的實(shí)現(xiàn)類構(gòu)建 A 的子類,如:A1、A2、A3……

通過這種方式實(shí)現(xiàn)對象和對象的創(chuàng)建的分離,可能覺得很雞肋吧?下面通過場景對比說明它的好處。

用傳統(tǒng)做法與使用了工廠方法的場景對比:

  • 傳統(tǒng)寫代碼
  • 我需要用到某個類,比如 A1,我就 new A1 出來,然后進(jìn)行業(yè)務(wù)操作。有一天產(chǎn)品告訴我這段邏輯需要增加一個 A2 的業(yè)務(wù)操作邏輯,我就得通過條件判斷增加邏輯??墒?A1 和 A2 在業(yè)務(wù)抽象上是一致的,僅僅是實(shí)現(xiàn)細(xì)節(jié)不同(舉個例子:好比運(yùn)輸,我用貨車運(yùn)輸是運(yùn)輸,我用火車運(yùn)輸也是運(yùn)輸,也就是說運(yùn)輸是目的,我的實(shí)現(xiàn)方式可以多樣化)。這時,通過 if/else 或 switch 來寫就不符合開閉原則了。
  • 用了工廠方法寫代碼
  • 我代碼上一開始就寫著是運(yùn)輸工具,用這個運(yùn)輸工具運(yùn)輸(注意這里是抽象概念運(yùn)輸工具而已)。這樣,我就可以根據(jù)業(yè)務(wù)計算得到的條件(如:公路/鐵路/海運(yùn)/空運(yùn))丟給工廠,工廠給我返回具體的運(yùn)輸工具就行(反正子類能強(qiáng)轉(zhuǎn)成父類)。

使用了工廠方法后,我的業(yè)務(wù)代碼不需要關(guān)注具體的運(yùn)輸工具是什么,然后再去看它怎么運(yùn)輸,后續(xù)產(chǎn)品加再多運(yùn)輸工具,transport()的這段代碼都不會被干擾,符合了開閉原則。

偽代碼如下:


public interface TransportToolFactory {
TransportTool createTool();
}

public class TruckTransportToolFactory implements TransportToolFactory {
@Override
public TransportTool createTool() {
...
}
}

public class BoatTransportToolFactory implements TransportToolFactory {
@Override
public TransportTool createTool() {
...
}
}

public class Transport {
private TransportToolFactory factory;

public Transport(int way) {
if (way == 0) {
factory = new TruckTransportToolFactory();
}
...
}

public void transport() {
TransportTool tool = factory.createTool();
// 繼續(xù)業(yè)務(wù)處理
}
}

簡單說下“簡單工廠”,偽代碼如下:

public void transport() {
int way = getWay();// 經(jīng)過計算也好,前端傳過來也好,反正得到了具體的運(yùn)輸方式
TransportTool tool = new TransportToolFactory(way).createTool();
// 繼續(xù)業(yè)務(wù)處理
}

public TransportTool createTool() {
if (way == 0) {
// 貨車
}
...
}

不過簡單工廠的缺點(diǎn)很明顯:

沒有做到單一職責(zé),從上面的例子不難看出,汽車、輪船、飛機(jī)、大炮都包了,如果業(yè)務(wù)足夠復(fù)雜,這個工廠類真的是誰維護(hù)誰知道!

(2)抽象工廠

抽象工廠模式是一種創(chuàng)建型設(shè)計模式, 它能創(chuàng)建一系列相關(guān)的對象, 而無需指定其具體類。

在我看來,JDBC 對抽象工廠模式的應(yīng)用就十分經(jīng)典。DB 有很多種,但是在不同的公司選擇可能都不太一樣,有些是 MySQL,有些是 Oracle,甚至有些是 SQL Sever 等等。但是對于我們開發(fā)而言,這些都是 DB,如果它們的連接,提交事務(wù),回滾事務(wù)等細(xì)節(jié)都需要我們注意的話(不同 DB 的具體實(shí)現(xiàn)處理會有差異),這顯然是很麻煩的,而且我們也不關(guān)心。我們要的只是使用 Connection 創(chuàng)建 Session,Session 開啟事務(wù)等等。

如果有一個類可以將這一系列共性的行為都提取出來(如連接,事務(wù)處理等),我們只要使用這個抽象類和它提供的方法就好了。事實(shí)上,JDBC 也的確是這么做的,我們在配置好具體的數(shù)據(jù)庫配置后,在代碼上只要用接口 Factory 創(chuàng)建連接、會話,開啟事務(wù)……

首先,連接是個對象,會話也是對象,事務(wù)也是,創(chuàng)建這些對象的方法都抽象到一個工廠里面,而這個工廠本身也只是一個接口定義,這就是所謂的抽象工廠;如果這時我使用的是MySQL,那么剛剛羅列的那些對象都是MySQL定制化的一系列相關(guān)對象,這就是所謂的“能創(chuàng)建一系列相關(guān)的對象”。

2.模板方法模式

模板方法模式是一種行為設(shè)計模式,它在超類中定義了一個算法的框架, 允許子類在不修改結(jié)構(gòu)的情況下重寫算法的特定步驟。

模板方法的核心在于抽象上行為性質(zhì)一樣,實(shí)際行為上有差別。

舉個例子:

我們產(chǎn)品常常要收集各式各樣的數(shù)據(jù)來分析用戶行為。有時他們?yōu)榱诵蕰o開發(fā)一堆電子文檔(如 CSV、DOC、TXT等等,這些文檔記錄著類似的數(shù)據(jù),但是數(shù)據(jù)結(jié)構(gòu)肯定是不同的),讓開發(fā)按照他們要求開發(fā)個系統(tǒng)功能可以導(dǎo)入,按他們的要求統(tǒng)計這些數(shù)據(jù)。

對于開發(fā)而言,代碼是差不多的,都要導(dǎo)入文件,解析文件,邏輯計算后入庫。偏偏我們導(dǎo)入文件后,解析文件代碼不同,邏輯計算有時也會有差異,但是對于最后一步落庫卻大概率是一樣的。對于這種類型的業(yè)務(wù)場景,我們可以定個類去規(guī)定好這些流程,上游調(diào)用時就是調(diào)用我這個類的子類,子類會根據(jù)自己的業(yè)務(wù)場景重寫自己需要的流程節(jié)點(diǎn)的邏輯。

3.策略模式

策略模式是一種行為設(shè)計模式, 它能讓你定義一系列算法, 并將每種算法分別放入獨(dú)立的類中, 以使算法的對象能夠相互替換。

舉例子說明:

我們接入一個審批流組件,我們自己后臺也要留一份提審的記錄(方便查詢和回溯),現(xiàn)在我們希望我們做的這個功能通用性要強(qiáng)一些,也就是可以做到讓其他功能想加入這個審批流程就加入,如:功能鑒權(quán)的授權(quán),工作流配置等等。

那么一開始審批時,一定是只有提審數(shù)據(jù),而我們的鑒權(quán)授權(quán)或者工作流配置肯定是沒生成到對應(yīng)表的,只有審批通過后才會真的授權(quán)或者生成配置。這時問題來了,當(dāng)工作流組件回調(diào)我們,難道我們每加入一個就 copy 上一個功能的回調(diào)代碼,刪掉修改審批狀態(tài)后的代碼,改改就好了嗎?這里得冗余多少代碼,哪怕你修改審批流的代碼抽取成一個方法,你也會發(fā)現(xiàn)每個回調(diào)方法里都有你那個方法。

具體偽代碼如下:

public class CallBack {
public void callback1(Param param) {
// 查詢審批記錄的合法性

// 修改審批記錄

// 處理業(yè)務(wù)邏輯1
}
public void callback1(Param param) {
// 查詢審批記錄的合法性

// 修改審批記錄

// 處理業(yè)務(wù)邏輯2
}
......
}

這種場景我們可以使用策略模式優(yōu)化,我們將處理業(yè)務(wù)邏輯當(dāng)成個算法對象抽離出來,不同業(yè)務(wù)場景的回調(diào)業(yè)務(wù)處理器實(shí)現(xiàn)這個抽離接口,用策略自動分配對應(yīng)的處理器即可。

偽代碼如下:

public class CallBack {
private Strategy strategy = new Strategy();
public void callback(Param param) {
// 查詢審批記錄的合法性

// 修改審批記錄

// 處理業(yè)務(wù)邏輯
strategy.getHandle(param.getServiceName()).invokeHandle();
}
}
public class Strategy {
private Map<String, Iservice> map;

static {
map = new HashMap<String, Iservice>();
map.put("Service1", new Service1());
map.put("Service2", new Service2());
......
}

public Iservice getHandle(String serviceName) {
return map.get(serviceName);
}
}
public class Service1 implements Iservice {
@Override
public void invokeHandle() {
...
}
}
public class Service2 implements Iservice {
@Override
public void invokeHandle() {
...
}
}
......

4.適配器模式

適配器模式是一種結(jié)構(gòu)型設(shè)計模式, 它能使接口不兼容的對象能夠相互合作。

說到適配器,我想大家很快就想到了一個場景:

我們家庭的標(biāo)準(zhǔn)電壓是220V左右(實(shí)際會有點(diǎn)誤差),我們大家電自然需要這么高的電壓才能工作,但是我們小家電呢?如:手機(jī)充電,電腦等等。這些小家電往往都會有個“中介”——適配器去幫他們將標(biāo)準(zhǔn)電壓轉(zhuǎn)化成他們的適用電壓。

其實(shí)我們的適配模式也是一樣的。這里我們來看下 Spring 的實(shí)戰(zhàn)使用,上源碼:


/**
* Extended variant of the standard {@link ApplicationListener} interface,
* exposing further metadata such as the supported event and source type.
*
* <p>As of Spring Framework 4.2, this interface supersedes the Class-based
* {@link SmartApplicationListener} with full handling of generic event types.
*
* @author Stephane Nicoll
* @since 4.2
* @see SmartApplicationListener
* @see GenericApplicationListenerAdapter
*/
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
...

在 4.2 版本之前,Spring 監(jiān)聽觸發(fā)事件的監(jiān)聽器使用的是 ApplicationListener,經(jīng)過這么多迭代后,它想增強(qiáng)下該功能,所以又定義了一個 GenericApplicationListener。但是這里有個問題,以前實(shí)現(xiàn) ApplicationListener 的那些子類也還是要兼容的!!!全部重寫,那很累人;不兼容,作為高熱度的開源框架,這是不能接受的。這時,Spring 的作者就采用了適配模式,具體應(yīng)用代碼如下:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
// 我們都知道 spring 的廣播事件都是是用了這個接口,我們看下 spring 是怎么做兼容的
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 重點(diǎn)在這 getApplicationListeners(event, type),看看他們是怎么 get 這個 list 的
// getApplicationListeners 是父類 AbstractApplicationEventMulticaster 的方法
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
}

AbstractApplicationEventMulticaster#getApplicationListeners 里面做了大量的性能優(yōu)化,不是本文的重點(diǎn),所以這里跳過了。大家只要知道它第一次拿的地方是:

AbstractApplicationEventMulticaster#retrieveApplicationListeners 。

這就夠了,而這個方法里面給 list 添加元素的方法是:

AbstractApplicationEventMulticaster#supportsEvent(ApplicationListener, ResolvableType, Class),這才是我們要看的代碼。

protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
// 這里先看下這個 listener 是不是 GenericApplicationListener 的子類
// 不是就轉(zhuǎn)化成 GenericApplicationListener,這樣以前 ApplicationListener 的子類就能被兼容了
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

5.總結(jié)

希望上面的幾個設(shè)計模式的應(yīng)用例子能給大家一點(diǎn)啟發(fā),能在自己工作中找到共同點(diǎn)去嘗試應(yīng)用。不過,也不要濫用設(shè)計模式,因?yàn)橐恍﹦偲鸩降墓?,業(yè)務(wù)方向也還不穩(wěn)定,很難去抽取共同的抽象部分又或者由于業(yè)務(wù)太簡單了,造成了過度設(shè)計,這些都是不可取的。

作者介紹

蔡柱梁,51CTO社區(qū)編輯,從事Java后端開發(fā)8年,做過傳統(tǒng)項目廣電BOSS系統(tǒng),后投身互聯(lián)網(wǎng)電商,負(fù)責(zé)過訂單,TMS,中間件等。

責(zé)任編輯:武曉燕 來源: 51CTO技術(shù)棧
相關(guān)推薦

2020-04-07 09:21:45

MySQL數(shù)據(jù)庫SQL

2022-06-29 11:28:57

數(shù)據(jù)指標(biāo)體系數(shù)據(jù)采集

2025-01-03 09:30:01

2024-02-19 13:11:38

門面模式系統(tǒng)

2024-02-26 11:52:38

代理模式設(shè)計

2024-01-29 12:22:07

設(shè)計模式策略模式

2023-05-22 13:27:17

2024-02-04 12:04:17

2024-02-27 11:59:12

享元模式對象

2024-01-30 13:15:00

設(shè)計模式責(zé)任鏈

2024-02-23 12:11:53

裝飾器模式對象

2024-02-21 12:24:33

模板設(shè)計模式框架

2022-09-21 16:56:16

設(shè)計模式微服務(wù)架構(gòu)

2024-12-31 10:36:40

AIAgent場景

2023-11-02 13:33:00

Python數(shù)據(jù)結(jié)構(gòu)

2024-02-20 12:09:32

模式工廠方法接口

2024-02-22 12:13:49

適配器模式代碼

2024-02-18 12:36:09

2018-07-06 05:05:07

2010-09-15 13:35:25

SwingHibernateStruts
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號