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

聊一聊裝飾者模式

開發(fā) 前端
如果需要擴展更多功能的話,可以再定義其他的ConcreteDecorator類,實現(xiàn)其他的擴展功能。如果只有一個ConcreteDecorator類,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。

一、概述

裝飾者模式(Decorator Pattern)允許向一個現(xiàn)有的對象擴展新的功能,同時不改變其結(jié)構(gòu)。主要解決直接繼承下因功能的不斷橫向擴展導致子類膨脹的問題,無需考慮子類的維護。

裝飾者模式有4種角色:

抽象構(gòu)件角色(Component):具體構(gòu)件類和抽象裝飾者類的共同父類。

具體構(gòu)件角色(ConcreteComponent):抽象構(gòu)件的子類,裝飾者類可以給它增加額外的職責。

裝飾角色(Decorator):抽象構(gòu)件的子類,具體裝飾類的父類,用于給具體構(gòu)件增加職責,但在子類中實現(xiàn)。

具體裝飾角色(ConcreteDecorator):具體裝飾類,定義了一些新的行為,向構(gòu)件類添加新的特性。

圖片

二、入門案例

2.1、類圖

圖片

2.2、基礎(chǔ)類介紹

// 抽象構(gòu)件角色
public interface Component {

void doSomeThing();
}

// 具體構(gòu)件角色
public class ConcreteComponent implements Component {

@Override
public void doSomeThing(){
System.out.println("處理業(yè)務(wù)邏輯");
}
}

// 裝飾者類
public abstract class Decorator implements Component {

private Component component;

public Decorator(Component component){
this.component = component;
}

@Override
public void doSomeThing(){
// 調(diào)用處理業(yè)務(wù)邏輯
component.doSomeThing();
}
}

// 具體裝飾類
public class ConcreteDecorator extends Decorator {

public ConcreteDecorator(Component component){
super(component);
}

@Override
public void doSomeThing(){
System.out.println("業(yè)務(wù)邏輯功能擴展");
super.doSomeThing();
}
}

當然,如果需要擴展更多功能的話,可以再定義其他的ConcreteDecorator類,實現(xiàn)其他的擴展功能。如果只有一個ConcreteDecorator類,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。

三、應(yīng)用場景

如風之前在一家保險公司干過一段時間。其中保險業(yè)務(wù)員也會在自家產(chǎn)品注冊賬號,進行推銷。不過在這之前,他們需要經(jīng)過培訓,導入一張展業(yè)資格證書。然后再去推銷保險產(chǎn)品供用戶下單,自己則通過推銷產(chǎn)生的業(yè)績,參與分潤,拿對應(yīng)的傭金。

對于上面導證書這個場景,實際上是會根據(jù)不同的保險產(chǎn)品,導入不同的證書的。并且證書的類型也不同,對應(yīng)的解析、校驗、執(zhí)行的業(yè)務(wù)場景都是不同的。如何去實現(xiàn)呢?當然if-else確實也是一種不錯的選擇。下面放一段偽代碼

/**
* @author 往事如風
* @version 1.0
* @date 2022/11/17 11:32
* @description
*/
@RestController
@RequestMapping("/certificate")
public class CertificateController {

@Resource
private CommonCertificateService certificateService;

@PostMapping("/import")
public Result<Integer> importFile(@RequestParam MultipartFile file, @RequestParam String productCode){
return Result.success(certificateService.importCertificate(file, productCode));
}
}

/**
* @author 往事如風
* @version 1.0
* @date 2022/11/17 13:25
* @description
*/
@Service
public class CommonCertificateService {

public Integer importCertificate(MultipartFile file, String productCode){
// 1、參數(shù)非空校驗
// 2、通過file后綴判斷file類型,支持excel和pdf
// 3、解析file文件,獲取數(shù)據(jù),統(tǒng)一封裝到定義的CertificatePojo類中
// 4、根據(jù)產(chǎn)品類型判斷導入之前的業(yè)務(wù)邏輯
if (productCode.equals(DecorateConstants.PRODUCT_A)) {
// 重新計算業(yè)績邏輯
// 重新算業(yè)績類型邏輯
// 一坨坨代碼去實現(xiàn)....
}
else if (productCode.equals(DecorateConstants.PRODUCT_B)) {
// 導入證書的代理人自己以及上級身份晉升邏輯
// 業(yè)績計算邏輯
// 一坨坨代碼去實現(xiàn)...
} else if (productCode.equals(DecorateConstants.PRODUCT_C)) {
// c產(chǎn)品下的業(yè)務(wù)邏輯
// 一坨坨代碼去實現(xiàn)...
} else {
// 默認的處理邏輯
// 一坨坨代碼去實現(xiàn)...
}
// 5、證書數(shù)據(jù)保存
// 6、代理人信息保存
// 7、相關(guān)流水數(shù)據(jù)保存
// 返回代理人id
Integer agentId = Integer.MAX_VALUE;
return agentId;
}
}

從上面的偽代碼看到,所有的業(yè)務(wù)邏輯是在一起處理的,通過productCode去處理對應(yīng)產(chǎn)品的相關(guān)邏輯。這么一看,好像也沒毛病,但是還是被技術(shù)大佬給否決了。好吧,如風決定重寫。運用裝飾者模式,重新處理下了下這段代碼。1、一切再從注解出發(fā),自定義Decorate注解,這里定義2個屬性,scene和type

  • scene:標記具體的業(yè)務(wù)場景
  • type:表示在該種業(yè)務(wù)場景下,定義一種具體的裝飾器類
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:44
* @description
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Decorate {
/**
* 具體的業(yè)務(wù)場景
* @return
*/
String scene();
/**
* 類型:不同業(yè)務(wù)場景下,不同的裝飾器類型
* @return
*/
String type();
}

2、抽象構(gòu)件接口,BaseHandler,這個是必須滴

/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:07
* @description 抽象處理接口
*/
public interface BaseHandler<T, R> {
/**
* 統(tǒng)一的處理方法
* @param t
* @return
*/
R handle(T t);
}

3、抽象裝飾器類,AbstractHandler,持有一個被裝飾類的引用,這個引用具體在運行時被指定

/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 22:10:05
* @desc 抽象父類
*/
public abstract class AbstractHandler<T, R> implements BaseHandler<T, R> {
protected BaseHandler service;

public void setService(BaseHandler service){
this.service = service;
}
}

4、具體的裝飾器類AProductServiceDecorate?,主要負責處理“導師證書”這個業(yè)務(wù)場景下,A產(chǎn)品相關(guān)的導入邏輯,并且標記了自定義注解Decorate,表示該類是裝飾器類。主要負責對A產(chǎn)品證書導入之前邏輯的增強,我們這里稱之為“裝飾”。

/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 23:11:16
* @desc
*/
@Decorate(scene = SceneConstants.CERTIFICATE_IMPORT, type = DecorateConstants.PRODUCT_A)
public class AProductServiceDecorate extends AbstractHandler<MultipartFile, Integer> {

/**
* 重寫父類處理數(shù)據(jù)方法
* @param file
* @return
*/
@Override
public Integer handle(MultipartFile file){
// 解析
CertificatePojo data = parseData(file);
// 校驗
check(data);
// 業(yè)績計算
calAchievement(data.getMobile());
return (Integer) service.handle(data);
}

public CertificatePojo parseData(MultipartFile file){
// file,證書解析
System.out.println("A產(chǎn)品的證書解析......");
CertificatePojo certificatePojo = new CertificatePojo();
certificatePojo.setMobile("12323");
certificatePojo.setName("張三");
certificatePojo.setMemberNo("req_343242ds");
certificatePojo.setEffectDate("2022-10-31:20:20:10");
return certificatePojo;
}

/**
* 證書數(shù)據(jù)校驗
* @param data
*/
public void check(CertificatePojo data){
// 數(shù)據(jù)規(guī)范和重復性校驗
// .....
System.out.println("A證書數(shù)據(jù)校驗......");
}

/**
* 計算業(yè)績信息
*/
private void calAchievement(String mobile){
System.out.println("查詢用戶信息, 手機號:" + mobile);
System.out.println("重新計算業(yè)績...");
}
}

當然,還是其他裝飾類,BProductServiceDecorate,CProductServiceDecorate等等,負責裝飾其他產(chǎn)品,這里就不舉例了。

5、當然還有管理裝飾器類的裝飾器類管理器DecorateManager,內(nèi)部維護一個map,負責存放具體的裝飾器類

/**
* @author 往事如風
* @version 1.0
* @date 2022/11/15 17:18
* @description 裝飾管理器
*/
public class DecorateManager {

/**
* 用于存放裝飾器類
*/
private Map<String, AbstractHandler> decorateHandleMap = new HashMap<>();

/**
* 將具體裝飾器類放在map中
*
* @param handlerList
*/
public void setDecorateHandler(List<AbstractHandler> handlerList){
for (AbstractHandler h : handlerList) {
Decorate annotation = AnnotationUtils.findAnnotation(h.getClass(), Decorate.class);
decorateHandleMap.put(createKey(annotation.scene(), annotation.type()), h);
}
}

/**
* 返回具體的裝飾器類
*
* @param type
* @return
*/
public AbstractHandler selectHandler(String scene, String type){
String key = createKey(scene, type);
return decorateHandleMap.get(key);
}

/**
* 拼接map的key
* @param scene
* @param type
* @return
*/
private String createKey(String scene, String type){
return StrUtil.builder().append(scene).append(":").append(type).toString();
}
}

6、用了springboot,當然需要將這個管理器交給spring的bean容器去管理,需要創(chuàng)建一個配置類DecorateAutoConfiguration

/**
* @author 往事如風
* @version 1.0
* @date 2022-11-12 19:22:41
* @desc
*/
@Configuration
public class DecorateAutoConfiguration {

@Bean
public DecorateManager handleDecorate(List<AbstractHandler> handlers){
DecorateManager manager = new DecorateManager();
manager.setDecorateHandler(handlers);
return manager;
}
}

7、被裝飾的service類,CertificateService,只需要關(guān)注自己的核心邏輯就可以

/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:10
* @description 執(zhí)行證書導入的service
*/
@Service
public class CertificateService implements BaseHandler<CertificatePojo, Integer> {

/**
* 處理導入證書的核心邏輯service
* @param certificate
* @return
*/
@Override
public Integer handle(CertificatePojo certificate){
System.out.println("核心業(yè)務(wù),證書數(shù)據(jù):" + JSONUtil.toJsonStr(certificate));
// 1、證書數(shù)據(jù)保存
// 2、代理人信息保存
// 3、相關(guān)流水數(shù)據(jù)保存
// 其他的一些列核心操作
Integer agentId = Integer.MAX_VALUE;
// 返回代理人id
return agentId;
}
}

8、在原來的controller中,注入管理器類DecorateManager去調(diào)用,以及service,也就是被裝飾的類。首先拿到裝飾器,然后再通過setService方法,傳入被裝飾的service。也就是具體裝飾什么類,需要在運行時才確定。

/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 23:30:37
* @desc
*/
@RestController
public class WebController {

@Resource
private DecorateManager decorateManager;

@Resource
private CertificateService certificateService;

@PostMapping("/import")
public Result importFile(@RequestParam MultipartFile file, @RequestParam String productCode){
AbstractHandler handler = decorateManager.selectHandler(SceneConstants.CERTIFICATE_IMPORT, productCode);
if (Objects.isNull(handler)) {
return Result.fail();
}
handler.setService(certificateService);
return Result.success(handler.handle(file));
}
}

下面模擬下代理人導入證書的流程,當選擇A產(chǎn)品,productCode傳A過來,后端的處理流程。

  • 對于A產(chǎn)品下,證書的解析,A產(chǎn)品傳的是excel
  • 然后數(shù)據(jù)校驗,這個產(chǎn)品下,特有的數(shù)據(jù)校驗
  • 最后是核心的業(yè)績重算,只有A產(chǎn)品才會有這個邏輯

圖片

當選擇B產(chǎn)品,productCode傳A過來,后端的處理流程。

  • 對于B產(chǎn)品下,證書的解析,A產(chǎn)品傳的是pdf
  • 然后數(shù)據(jù)校驗,跟A產(chǎn)品也不同,多了xxx步驟
  • 核心是代理人的晉升處理,這部分是B產(chǎn)品獨有的

圖片

最后說一句,既然都用springboot了,這塊可以寫一個starter,做一個公用的裝飾器模式。如果哪個服務(wù)需要用到,依賴這個裝飾器的starter,然后標記Decorate注解,定義對應(yīng)的scene和type屬性,就可以直接使用了。

四、源碼中運用

4.1、JDK源碼中的運用

來看下IO流中,InputStream、FilterInputStream、FileInputStream、BufferedInputStream的一段代碼

public abstract class InputStream implements Closeable {

public abstract int read() throws IOException;


public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;

int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
//--------------------------
public class FilterInputStream extends InputStream {

protected FilterInputStream(InputStream in){
this.in = in;
}
public int read() throws IOException {
return in.read();
}
}

//--------------------------
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in, int size){
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}

public BufferedInputStream(InputStream in){
this(in, DEFAULT_BUFFER_SIZE);
}

public int read() throws IOException {
return in.read();
}

public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
}

//--------------------------
public class FileInputStream extends InputStream {
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
}

再來看下這幾個類的類圖 

圖片

 這些類的代碼有刪改,可以看到BufferedInputStream中定義了很多屬性,這些數(shù)據(jù)都是為了可緩沖讀取來作準備的,看到其有構(gòu)造方法會傳入一個InputStream的實例。實際編碼如下

//被裝飾的對象,文件輸入流
InputStream in=new FileInputStream("/data/log/app.log");
//裝飾對象,可緩沖
InputStream bufferedIn=new BufferedInputStream(in);
bufferedIn.read();

這里覺得很眼熟吧,其實已經(jīng)運用了裝飾模式了。

4.2、mybatis源碼中的運用

在mybatis中,有個接口Executor?,顧名思義這個接口是個執(zhí)行器,它底下有許多實現(xiàn)類,如CachingExecutor、SimpleExecutor、BaseExecutor等等。類圖如下: 

圖片

主要看下CachingExecutor類,看著很眼熟,很標準的裝飾器。其中該類中的update是裝飾方法,在調(diào)用真正update方法之前,會執(zhí)行刷新本地緩存的方法,對原來的update做增強和擴展。

public class CachingExecutor implements Executor {

private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

public CachingExecutor(Executor delegate){
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}

@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 增強內(nèi)容
// 修改方法就要清空本地的緩存
flushCacheIfRequired(ms);
// 調(diào)用原有的方法
return delegate.update(ms, parameterObject);
}
}

再來看下BaseExecutor類,這里有一個update方法,這個是原本的被裝飾的update方法。然后再看這個原本的update方法,它調(diào)用的doUpdate方法是個抽象方法,用protected修飾。咦,這不就是模板方法么,關(guān)于模板方法模式,這里就不展開贅述了。

public abstract class BaseExecutor implements Executor {
protected Executor wrapper;

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
}

五、總結(jié)

優(yōu)點

通過組合而非繼承的方式,動態(tài)地擴展一個對象的功能,在運行時可以選擇不同的裝飾器從而實現(xiàn)不同的功能。

有效的避免了使用繼承的方式擴展對象功能而帶來的靈活性差、子類無限制擴張的問題。

具體組件類與具體裝飾類可以獨立變化,用戶可以根據(jù)需要新增具體組件類跟裝飾類,在使用時在對其進行組合,原有代碼無須改變,符合"開閉原則"。

缺點

這種比繼承更加靈活機動的特性,也同時意味著更加多的復雜性。

裝飾模式會導致設(shè)計中出現(xiàn)許多小類 (I/O 類中就是這樣),如果過度使用,會使程序變得很復雜。

裝飾模式是針對抽象組件(Component)類型編程。但是,如果你要針對具體組件編程時,就應(yīng)該重新思考你的應(yīng)用架構(gòu),以及裝飾者是否合適。

六、參考源碼

?編程文檔: https://gitee.com/cicadasmile/butte-java-note

應(yīng)用倉庫: https://gitee.com/cicadasmile/butte-flyer-parent

責任編輯:武曉燕 來源: 知了一笑
相關(guān)推薦

2023-05-15 08:38:58

模板方法模式

2022-11-01 08:46:20

責任鏈模式對象

2022-06-01 09:51:51

Golang方法接收者

2023-12-14 11:35:32

.NET泄露模式

2023-02-09 10:39:15

gRPC通信模式

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2022-09-26 08:03:25

VMware虛擬機

2018-06-07 13:17:12

契約測試單元測試API測試

2022-08-08 08:25:21

Javajar 文件

2021-08-04 09:32:05

Typescript 技巧Partial

2018-11-29 09:13:47

CPU中斷控制器

2019-02-13 14:15:59

Linux版本Fedora

2021-01-29 08:32:21

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

2021-02-06 08:34:49

函數(shù)memoize文檔

2023-07-06 13:56:14

微軟Skype

2020-10-15 06:56:51

MySQL排序

2020-09-08 06:54:29

Java Gradle語言

2022-03-08 16:10:38

Redis事務(wù)機制
點贊
收藏

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