成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

工廠模式進(jìn)階用法,如何動(dòng)態(tài)選擇對(duì)象?

開發(fā) 前端
我們通過一個(gè)例子,不斷迭代帶大家理解了工廠模式,工廠模式是一種創(chuàng)建型設(shè)計(jì)模式,用于創(chuàng)建同一類型的不同實(shí)現(xiàn)對(duì)象。我們來總結(jié)下這種動(dòng)態(tài)選擇對(duì)象工廠模式的優(yōu)缺點(diǎn)。

前言

工廠設(shè)計(jì)模式可能是最常用的設(shè)計(jì)模式之一,我想大家在自己的項(xiàng)目中都用到過。可能你會(huì)不屑一顧,但這篇文章不僅僅是關(guān)于工廠模式的基本知識(shí),更是討論如何在運(yùn)行時(shí)動(dòng)態(tài)選擇不同的方法進(jìn)行執(zhí)行,你們可以看看是不是和你們項(xiàng)目中用的一樣?

小菜鳥的問題

直接上例子說明,設(shè)計(jì)一個(gè)日志記錄的功能,但是支持記錄到不同的地方,例如:

  • 內(nèi)存中
  • 磁盤上的文件
  • 數(shù)據(jù)庫
  • 百度網(wǎng)盤等遠(yuǎn)程存儲(chǔ)服務(wù)

面對(duì)這么一個(gè)需求,你會(huì)怎么做呢?我們先來看看小菜鳥的做法吧。

  1. 小菜鳥創(chuàng)建了一個(gè)Logger類
class Logger {
public void log(String message, String loggerMedium) {}
}
  1. 小菜鳥想都不想,直接一通if else。
class Logger {
public void log(String message, String loggerMedium) {
if (loggerMedium.equals("MEMORY")) {
logInMemory(message);
} else if (loggerMedium.equals("FILE")) {
logOnFile(message);
} else if (loggerMedium.equals("DB")) {
logToDB(message);
} else if (loggerMedium.equals("REMOTE_SERVICE")) {
logToRemote(message);
}
}

private void logInMemory(String message) {
// Implementation
}

private void logOnFile(String message) {
// Implementation
}

private void logToDB(String message) {
// Implementation
}

private void logToRemote(String message) {
// Implementation
}
}

現(xiàn)在突然說要增加一種存儲(chǔ)介質(zhì)FLASH_DRIVE,就要改了這個(gè)類?不拍改錯(cuò)嗎?也不符合“開閉原則”,而且隨著存儲(chǔ)介質(zhì)變多,類也會(huì)變的很大,小菜鳥懵逼了,不知道怎么辦?

有沒有更好的方法呢?

這時(shí)候小菜鳥去找你幫忙,你一頓操作,改成了下面這樣:

class InMemoryLog {
public void logToMemory(String message) {
// Implementation
}
}

class FileLog {
public void logToFile(String message) {
//Implementation
}
}

class DBLog {
public void logToDB(String message) {
// Implementation
}
}

class RemoteServiceLog {
public void logToService(String message) {
// Implementation
}
}

class Logger {
private InMemoryLog mLog;
private FileLog fLog;
private DBLog dbLog;
private RemoteServiceLog sLog;

public Logger() {
mLog = new InMemoryLog();
fLog = new FileLog();
dbLog = new DBLog();
sLog = new RemoteServiceLog();
}

public void log(String message, String loggerMedium) {
if (loggerMedium.equals("MEMORY")) {
mLog.logToMemory(message);
} else if (loggerMedium.equals("FILE")) {
fLog.logToFile(message);
} else if (loggerMedium.equals("DB")) {
dbLog.logToDB(message);
} else if (loggerMedium.equals("REMOTE_SERVICE")) {
sLog.logToService(message);
}
}
}

在這個(gè)實(shí)現(xiàn)中,你已經(jīng)將單獨(dú)的代碼分離到它們對(duì)應(yīng)的文件中,但是Logger?類與存儲(chǔ)介質(zhì)的具體實(shí)現(xiàn)緊密耦合,如FileLog、DBLog?等。隨著存儲(chǔ)介質(zhì)的增加,類中將引入更多的實(shí)例Logger。

還有什么更好的辦法嗎?

你想了想,上面的實(shí)現(xiàn)都是直接寫具體的實(shí)現(xiàn)類,是面向?qū)崿F(xiàn)編程,更合理的做法是面向接口編程,接口意味著協(xié)議,契約,是一種更加穩(wěn)定的方式。

  1. 定義一個(gè)日志操作的接口
public interface LoggingOperation {
void log(String message);
}
  1. 實(shí)現(xiàn)這個(gè)接口
class InMemoryLog implements LoggingOperation {
public void log(String message) {
// Implementation
}
}

class FileLog implements LoggingOperation {
public void log(String message) {
//Implementation
}
}

class DBLog implements LoggingOperation {
public void log(String message) {
// Implementation
}
}

class RemoteServiceLog implements LoggingOperation {
public void log(String message) {
// Implementation
}
}
  1. 你定義了一個(gè)類,根據(jù)傳遞的參數(shù),在運(yùn)行時(shí)動(dòng)態(tài)選擇具體實(shí)現(xiàn),這就是所謂的工廠類,不過是基礎(chǔ)版。
class LoggerFactory {
public static LoggingOperation getInstance(String loggerMedium) {
LoggingOperation op = null;
switch (loggerMedium) {
case "MEMORY":
op = new InMemoryLog();
break;
case "FILE":
op = new FileLog();
break;
case "DB":
op = new DBLog();
break;
case "REMOTE_SERVICE":
op = new RemoteServiceLog();
break;
}

return op;
}
}
  1. 現(xiàn)在你的 Logger類的實(shí)現(xiàn)就是下面這個(gè)樣子了。
class Logger {
public void log(String message, String loggerMedium) {
LoggingOperation instance = LoggerFactory.getInstance(loggerMedium);
instance.log(message);
}
}

這里的代碼變得非常統(tǒng)一,創(chuàng)建實(shí)際存儲(chǔ)實(shí)例的責(zé)任已經(jīng)轉(zhuǎn)移到LoggerFactory?,各個(gè)存儲(chǔ)類只實(shí)現(xiàn)它們?nèi)绾螌⑾⒂涗浀剿鼈兊奶囟ń橘|(zhì),最后該類Logger?只關(guān)心通過LoggerFactory?將實(shí)際的日志記錄委托給具體的實(shí)現(xiàn)。這樣,代碼就很松耦合了。你想要添加一個(gè)新的存儲(chǔ)介質(zhì),例如FLASH_DRIVE?,只需創(chuàng)建一個(gè)實(shí)現(xiàn)LoggingOperation?接口的新類并將其注冊(cè)到LoggerFactory中就好了。這就是工廠模式可以幫助您動(dòng)態(tài)選擇實(shí)現(xiàn)的方式。

還能做得更好嗎?

你已經(jīng)完成了一個(gè)松耦合的設(shè)計(jì),但是想象一下假如有數(shù)百個(gè)存儲(chǔ)介質(zhì)的場景,所以我們最終會(huì)在工廠類LoggerFactory?中的switch case?部分case數(shù)百個(gè)。這看起來還是很糟糕,如果管理不當(dāng),它有可能成為技術(shù)債務(wù),這該怎么辦呢?

擺脫不斷增長的if else?或者 switch case?的一種方法是維護(hù)類中所有實(shí)現(xiàn)類的列表,LoggerFactory代碼如下所示:

class LoggerFactory {
private static final List<LoggingOperation> instances = new ArrayList<>();

static {
instances.addAll(Arrays.asList(
new InMemoryLog(),
new FileLog(),
new DBLog(),
new RemoteServiceLog()
));
}

public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) {
for(LoggingOperation op : instances) {
// 比如判斷StrUtil.equals(loggerMedium, op.getType()) op本身添加一個(gè)type
}

return null;
}
}

但是請(qǐng)注意,還不夠,在所有上述實(shí)現(xiàn)中,無論if else、switch case? 還是上面的做法,都是讓存儲(chǔ)實(shí)現(xiàn)與LoggerFactory?緊密耦合的。你添加一種實(shí)現(xiàn),就要修改LoggerFactory,有什么更好的做法嗎?

逆向思維一下,我們是不是讓具體的實(shí)現(xiàn)主動(dòng)注冊(cè)上來呢?通過這種方式,工廠不需要知道系統(tǒng)中有哪些實(shí)例可用,而是實(shí)例本身會(huì)注冊(cè)并且如果它們?cè)谙到y(tǒng)中可用,工廠就會(huì)為它們提供服務(wù)。具體代碼如下:

class LoggerFactory {
private static final Map<String, LoggingOperation> instances = new HashMap<>();

public static void register(String loggerMedium, LoggingOperation instance) {
if (loggerMedium != null && instance != null) {
instances.put(loggerMedium, instance);
}
}

public static LoggingOperation getInstance(String loggerMedium) {
if (instances.containsKey(loggerMedium)) {
return instances.get(loggerMedium);
}
return null;
}
}

在這里,LoggerFactory?提供了一個(gè)register?注冊(cè)的方法,具體的存儲(chǔ)實(shí)現(xiàn)可以調(diào)用該方法注冊(cè)上來,保存在工廠的instancesmap對(duì)象中。

我們來看看具體的存儲(chǔ)實(shí)現(xiàn)注冊(cè)的代碼如下:

class RemoteServiceLog implements LoggingOperation {
static {
LoggerFactory.register("REMOTE", new RemoteServiceLog());
}

public void log(String message) {
// Implementation
}
}

由于注冊(cè)應(yīng)該只發(fā)生一次,所以它發(fā)生在static類加載器加載存儲(chǔ)類時(shí)的塊中。

但是又有一個(gè)問題,默認(rèn)情況下JVM不加載類RemoteServiceLog,除非它由應(yīng)用程序在外部實(shí)例化或調(diào)用。因此,盡管存儲(chǔ)類有注冊(cè)的代碼,但實(shí)際上注冊(cè)并不會(huì)發(fā)生,因?yàn)闆]有被JVM加載,不會(huì)調(diào)用static代碼塊中的代碼, 你又犯難了。

你靈機(jī)一動(dòng),LoggerFactory是獲取存儲(chǔ)實(shí)例的入口點(diǎn),能否在這個(gè)類上做點(diǎn)文章,就寫下了下面的代碼:

class LoggerFactory {
private static final Map<String, LoggingOperation> instances = new HashMap<>();

static {
try {
loadClasses(LoggerFactory.class.getClassLoader(), "com.alvin.storage.impl");
} catch (Exception e) {
// log or throw exception.
}
}

public static void register(String loggerMedium, LoggingOperation instance) {
if (loggerMedium != null && instance != null) {
instances.put(loggerMedium, instance);
}
}

public static LoggingOperation getInstance(String loggerMedium) {
if (instances.containsKey(loggerMedium)) {
return instances.get(loggerMedium);
}
return null;
}

private static void loadClasses(ClassLoader cl, String packagePath) throws Exception {

String dottedPackage = packagePath.replaceAll("[/]", ".");

URL upackage = cl.getResource(packagePath);
URLConnection conn = upackage.openConnection();

String rr = IOUtils.toString(conn.getInputStream(), "UTF-8");

if (rr != null) {
String[] paths = rr.split("\n");

for (String p : paths) {
if (p.endsWith(".class")) {
Class.forName(dottedPackage + "." + p.substring(0, p.lastIndexOf('.')));
}

}
}
}
}

在上面的實(shí)現(xiàn)中,你使用了一個(gè)名為loadClasses?的方法,該方法掃描提供的包名稱com.alvin.storage.impl?并將駐留在該目錄中的所有類加載到類加載器。以這種方式,當(dāng)類加載時(shí),它們的static?塊被初始化并且它們將自己注冊(cè)到LoggerFactory中。

如何在 SpringBoot 中實(shí)現(xiàn)此技術(shù)?

你突然發(fā)現(xiàn)你的是springboot應(yīng)用,突然想到有更方便的解決方案。

因?yàn)槟愕拇鎯?chǔ)實(shí)現(xiàn)類都被標(biāo)記上注解@Component?,這樣 Spring? 會(huì)在應(yīng)用程序啟動(dòng)時(shí)自動(dòng)加載類,它們會(huì)自行注冊(cè),在這種情況下你不需要使用loadClasses?功能,Spring 會(huì)負(fù)責(zé)加載類。具體的代碼實(shí)現(xiàn)如下:

class LoggerFactory {
private static final Map<String, Class<? extends LoggingOperation>> instances = new HashMap<>();

public static void register(String loggerMedium, Class<? extends LoggingOperation> instance) {
if (loggerMedium != null && instance != null) {
instances.put(loggerMedium, instance);
}
}

public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) {
if (instances.containsKey(loggerMedium)) {
return context.getBean(instances.get(loggerMedium));
}
return null;
}
}

getInstance?需要傳入ApplicationContext對(duì)象,這樣就可以根據(jù)類型獲取具體的實(shí)現(xiàn)了。

修改所有存儲(chǔ)實(shí)現(xiàn)類,如下所示:

import org.springframework.stereotype.Component;

@Component
class RemoteServiceLog implements LoggingOperation {
static {
LoggerFactory.register("REMOTE", RemoteServiceLog.class);
}

public void log(String message) {
// Implementation
}
}

總結(jié)

我們通過一個(gè)例子,不斷迭代帶大家理解了工廠模式,工廠模式是一種創(chuàng)建型設(shè)計(jì)模式,用于創(chuàng)建同一類型的不同實(shí)現(xiàn)對(duì)象。我們來總結(jié)下這種動(dòng)態(tài)選擇對(duì)象工廠模式的優(yōu)缺點(diǎn)。

優(yōu)點(diǎn):

  • 容易管理。在添加新的存儲(chǔ)類時(shí),只需將該類放入特定包中,在static代碼塊中注冊(cè)它自己到工廠中。
  • 松耦合,當(dāng)您添加新的存儲(chǔ)實(shí)現(xiàn)時(shí),您不需要在工廠類中進(jìn)行任何更改。
  • 遵循SOLID編程原則。

缺點(diǎn):

  • 如果是用原生通過類加載的方式,代價(jià)比較大,因?yàn)樗婕?I/O 操作。但是如果使用的是SpringBoot,則無需擔(dān)心,因?yàn)榭蚣鼙旧頃?huì)調(diào)用組件。
  • 需要額外編寫一個(gè)static塊,注冊(cè)自己到工廠中,一不小心就遺漏了。
責(zé)任編輯:武曉燕 來源: JAVA旭陽
相關(guān)推薦

2021-03-06 22:50:58

設(shè)計(jì)模式抽象

2024-12-05 15:44:13

工廠模式接口

2011-11-17 16:03:05

Java工廠模式Clojure

2024-03-06 13:19:19

工廠模式Python函數(shù)

2022-01-12 13:33:25

工廠模式設(shè)計(jì)

2020-08-21 07:23:50

工廠模式設(shè)計(jì)

2020-07-09 08:00:25

Git分支模式

2021-09-29 13:53:17

抽象工廠模式

2020-10-19 09:28:00

抽象工廠模式

2009-01-15 10:55:29

JavaScript設(shè)計(jì)模式抽象工廠

2023-11-01 11:27:42

ping命令網(wǎng)絡(luò)

2022-05-09 08:04:50

工廠模式設(shè)計(jì)模式

2010-04-19 09:30:00

工廠模式PHP設(shè)計(jì)模式

2023-07-27 06:51:46

Android架構(gòu)模式

2010-10-09 09:25:35

Python工廠模式

2024-07-31 08:12:33

2013-11-26 16:29:22

Android設(shè)計(jì)模式

2025-01-06 13:00:00

RedisSentinel高可用模式

2020-09-14 17:26:48

抽象工廠模式

2009-08-04 09:22:26

C#工廠模式
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 精品视频一区二区三区在线观看 | 日日操夜夜操天天操 | www.久久.com| 国产欧美在线 | 九九精品在线 | 国产一区二区免费 | 国产精品视频一二三区 | 亚洲一区二区免费视频 | 日本天天操 | 91精品国产色综合久久 | 992tv人人草| 一区二区av | 欧美日韩国产欧美 | 黄色片视频免费 | 极品销魂美女一区二区 | 久久久成人一区二区免费影院 | 国产一级片免费在线观看 | 四虎成人av | 久久激情视频 | 国产伦精品一区二区三区高清 | 欧美日韩国产传媒 | 操久久| 久久久免费电影 | 日韩有码一区 | 国产精品久久久久久久久久妞妞 | 久久y| 久久久久网站 | 99资源| 国产一区二区三区在线 | 男女视频在线观看网站 | 久久成人高清视频 | 色99视频 | 久久免费观看一级毛片 | 久久精品 | 国产视频一二三区 | 国产在线观看一区二区 | 欧美黄色一区 | 日韩中文字幕在线播放 | 日韩欧美综合在线视频 | 亚洲国产精品成人久久久 | 欧美精品a∨在线观看不卡 国产精品久久国产精品 |