摘要:設(shè)計(jì)模式無(wú)論是對(duì)于最底層的的編碼實(shí)現(xiàn)還是較高層的架構(gòu)設(shè)計(jì)都有著重要的指導(dǎo)作用。所謂光說(shuō)不練假把式,今天我就把項(xiàng)目中常見(jiàn)的應(yīng)用場(chǎng)景涉及到的主要設(shè)計(jì)模式及其相關(guān)設(shè)計(jì)模式總結(jié)一下,用實(shí)例分析和對(duì)比的方式在一片文章中就把最常見(jiàn)的種設(shè)計(jì)模式梳理清楚。
設(shè)計(jì)模式無(wú)論是對(duì)于最底層的的編碼實(shí)現(xiàn)還是較高層的架構(gòu)設(shè)計(jì)都有著重要的指導(dǎo)作用。所謂光說(shuō)不練假把式,今天我就把項(xiàng)目中常見(jiàn)的應(yīng)用場(chǎng)景涉及到的主要設(shè)計(jì)模式及其相關(guān)設(shè)計(jì)模式總結(jié)一下,用實(shí)例分析和對(duì)比的方式在一片文章中就把最常見(jiàn)的21種設(shè)計(jì)模式梳理清楚。
Redis發(fā)布訂閱在項(xiàng)目中常常使用redis的發(fā)布/訂閱功能,用來(lái)實(shí)現(xiàn)進(jìn)程間通信甚至IM等業(yè)務(wù)。
使用 jedis 實(shí)現(xiàn)頻道訂閱的模式一般如下:
try( Jedis jedis = RedisClient.getJedis() ) { JedisPubSub listener = new MySubListener(); // 訂閱 jedis.subscribe(listener, "channel"); }
其中 MySubListener :
class MySubListener extends JedisPubSub { // 取得訂閱的消息后的處理 public void onMessage(String channel, String message) { logger.info("頻道:{},收到消息:{}",channel,message); } // 初始化訂閱時(shí)候的處理 public void onSubscribe(String channel, int subscribedChannels) { logger.info("訂閱:{},總數(shù):{}",channel,subscribedChannels); } // 取消訂閱時(shí)候的處理 public void onUnsubscribe(String channel, int subscribedChannels) { logger.info("取消訂閱:{},總數(shù):{}",channel,subscribedChannels); } }
這里使用了策略模式(對(duì)算法的封裝,把使用算法的責(zé)任和算法本身分隔開(kāi),委派給不同的對(duì)象管理。策略模式通常把一系列的算法包裝到一系列的策略類(lèi)里面,作為抽象策略類(lèi)的子類(lèi))。
圖:
本例中,JedisPubSub是抽象策略類(lèi),定義不同事件發(fā)生時(shí)的響應(yīng)模式亦即所支持的算法的公共接口;MySubListener是一個(gè)具體策略類(lèi)定義了一種具體的事件響應(yīng)方式(簡(jiǎn)單的打?。?;jedis是就是Context,負(fù)責(zé)維護(hù)調(diào)用者與策略之間的聯(lián)系。這樣不同的調(diào)用者只需要傳入不同的事件響應(yīng)具體算法如MySubListener1、2、3等即可(而不是去修改已有算法),實(shí)現(xiàn)了對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉的開(kāi)閉原則。
jedis 發(fā)布事件的代碼如下:
try { jedis.publish("channel","message to be published"); }
說(shuō)到這就不得不說(shuō)說(shuō)狀態(tài)模式(當(dāng)一個(gè)對(duì)象內(nèi)在狀態(tài)改變時(shí)允許其改變行為, 這個(gè)對(duì)象看起來(lái)像改變了其類(lèi))
圖:
Context定義客戶(hù)端需要的接口, 并且負(fù)責(zé)具體狀態(tài)的切換
State接口或抽象類(lèi),負(fù)責(zé)對(duì)象狀態(tài)定義,并且封裝Context以實(shí)現(xiàn)狀態(tài)切換;
ConcreteState每一個(gè)具體狀態(tài)必須完成兩個(gè)職責(zé):就是本狀態(tài)下要做的事情,以及本狀態(tài)如何過(guò)渡到其他狀態(tài)
狀態(tài)模式和策略模式都是為具有多種可能情形設(shè)計(jì)的模式,把不同的處理情形抽象為一個(gè)相同的接口,符合開(kāi)閉原則。但是狀態(tài)模式將各個(gè)狀態(tài)對(duì)應(yīng)的操作分離開(kāi)來(lái),即不同的狀態(tài)由不同的子類(lèi)實(shí)現(xiàn)具體操作,狀態(tài)切換由子類(lèi)實(shí)現(xiàn),當(dāng)發(fā)現(xiàn)傳入?yún)?shù)不是自己這個(gè)狀態(tài)所對(duì)應(yīng)的參數(shù),則自己給Context類(lèi)切換狀態(tài),也就是說(shuō)客戶(hù)端并不知曉狀態(tài);而策略模式是直接依賴(lài)注入到Context類(lèi)的參數(shù)進(jìn)行選擇策略,不存在切換狀態(tài)的操作,也就是說(shuō)狀態(tài)和策略是由客戶(hù)端自己定的。
回到本例,發(fā)布/訂閱本身就是觀(guān)察者模式(定義對(duì)象間一種一對(duì)多的依賴(lài)關(guān)系,使得每當(dāng)一個(gè)對(duì)象改變狀態(tài),則所有依賴(lài)于它的對(duì)象都會(huì)得到通知并被自動(dòng)更新)的運(yùn)用。
圖:
可以結(jié)合Redis設(shè)計(jì)與實(shí)現(xiàn)查看redis實(shí)現(xiàn)發(fā)布訂閱的原理。本例中,JedisPubSub是抽象觀(guān)察者,MySubListener 是具體觀(guān)察者,抽象主題沒(méi)有顯式定義,但是我們知道它的標(biāo)準(zhǔn)就是能夠添加、刪除、通知觀(guān)察者(如調(diào)用onMessage方法),具體主題就是redis里面包含"channel"這個(gè)模式的頻道。這就把消息生產(chǎn)者和消費(fèi)者解耦了,消費(fèi)者不用管生產(chǎn)者如何產(chǎn)生消息,生產(chǎn)者不用管消費(fèi)者如何處理消息,兩者直接是松耦合的,也就是說(shuō)兩者僅依賴(lài)于通知機(jī)制進(jìn)行交互而不知道對(duì)方的實(shí)現(xiàn)細(xì)節(jié),這樣只要保持通知機(jī)制,雙方都可以隨意擴(kuò)展。
請(qǐng)注意上面代碼的Jedis jedis = RedisClient.getJedis()是一個(gè)靜態(tài)工廠(chǎng)方法模式或者說(shuō)簡(jiǎn)單工廠(chǎng)模式(通過(guò)專(zhuān)門(mén)定義一個(gè)類(lèi)使用靜態(tài)方法來(lái)負(fù)責(zé)創(chuàng)建其他類(lèi)的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類(lèi)或者父接口)的使用。
圖:
RedisClient主要代碼如下:
public final class RedisClient { private static JedisPool jedisPool; public static void construct(Properties p){ try { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(Integer.parseInt(p.getProperty("jedis.pool.maxTotal"))); jedisPool = new JedisPool(config,p.getProperty("redis.host"), Integer.parseInt(p.getProperty("redis.port")), Integer.parseInt(p.getProperty("redis.timeOut")),p.getProperty("redis.auth"), Integer.parseInt(p.getProperty("redis.db"))); } } public static Jedis getJedis(){ return jedisPool.getResource(); } public static void destruct(){ jedisPool.close(); } }
本例中類(lèi)RedisClient就是Creator,返回的redis客戶(hù)端Jedis就是ConcreateProduct,由于目前只用了 jedis 這一種 java redis client 所以沒(méi)有設(shè)置抽象的Product,如果有多種client那么就要設(shè)置抽象的Product(這些Product都要有set、hset等redis通用操作),然后再在getJedis函數(shù)中去根據(jù)需要產(chǎn)生不同的client(if else 或者 switch case)。
靜態(tài)工廠(chǎng)方法的好處在于:增加新的Product類(lèi)(比如新的java redis client)的時(shí)候老的類(lèi)不需要改變,調(diào)用者由于只依賴(lài)于接口(抽象的Product)也不用改變,亦即把變化封裝到工廠(chǎng)內(nèi)部了;可讀性更強(qiáng)(比如getJedis你就知道他要干啥,而不是使用不知所以的構(gòu)造函數(shù));緩存增強(qiáng)性能(比如上面的jedisPool就一直存在著,避免每次獲取連接時(shí)新創(chuàng)建連接池);代碼簡(jiǎn)潔等等。
靜態(tài)工廠(chǎng)方法模式的缺點(diǎn)就是新加入一個(gè)Product類(lèi)的時(shí)候,其工廠(chǎng)方法本身需要改變(比如多一個(gè)判斷的case分支),解決辦法就是采用每種具體Product對(duì)應(yīng)一個(gè)具體工廠(chǎng)的工廠(chǎng)模式(定義一個(gè)用于創(chuàng)建對(duì)象的接口, 讓子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)。 工廠(chǎng)方法使一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi))
圖:
工廠(chǎng)模式把每一種 product 類(lèi)的實(shí)例化過(guò)程都封裝到了一個(gè)對(duì)應(yīng)的工廠(chǎng)類(lèi)中,新加入product的時(shí)候不需要改任何的舊代碼,只需要同時(shí)添加對(duì)應(yīng)的具體工廠(chǎng)類(lèi)即可。高層模塊只需要知道產(chǎn)品的抽象類(lèi),其他的具體實(shí)現(xiàn)類(lèi)都不需要關(guān)心,符合迪米特法則,依賴(lài)倒置原則,里氏替換原則
然后就不得不說(shuō)說(shuō)抽象層次更高、更具一般性的抽象工廠(chǎng)模式(為創(chuàng)建一組相關(guān)或相互依賴(lài)的對(duì)象提供一個(gè)接口, 而且無(wú)須指定它們的具體類(lèi))了
圖:
抽象工廠(chǎng)與工廠(chǎng)方法的區(qū)別就是可能有多個(gè)抽象Product,也就是說(shuō)每一個(gè)具體工廠(chǎng)能夠生產(chǎn)一個(gè)產(chǎn)品族而不只是一個(gè)產(chǎn)品,可以把抽象工廠(chǎng)簡(jiǎn)單理解為工廠(chǎng)方法+簡(jiǎn)單工廠(chǎng),每一個(gè)具體工廠(chǎng)都是一個(gè)簡(jiǎn)單工廠(chǎng)。
說(shuō)到工廠(chǎng)模式就不得不說(shuō)原型模式(用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi), 并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象)
圖:
原型模式的核心是Override Object 類(lèi)的 clone 方法,通過(guò)該方法進(jìn)行對(duì)象的拷貝,由于是內(nèi)存二進(jìn)制流的拷貝,所以比直接new性能好很多,特別是要在一個(gè)循環(huán)體內(nèi)產(chǎn)生大量的對(duì)象時(shí),原型模式可以更好地體現(xiàn)其優(yōu)點(diǎn)。在實(shí)際項(xiàng)目中,原型模式很少多帶帶出現(xiàn),一般是和工廠(chǎng)方法模式一起出現(xiàn),通過(guò)clone的方法創(chuàng)建一個(gè)對(duì)象, 然后由工廠(chǎng)方法提供給調(diào)用者。
繼續(xù)觀(guān)察上面的RedisClient類(lèi),我們知道,連接池jedisPool在整個(gè)應(yīng)用中是只需要一個(gè)實(shí)例的,也就是說(shuō)我們要使用單例模式(確保某一個(gè)類(lèi)只有一個(gè)實(shí)例, 而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例)
圖:
所以我們的代碼要修改一下:
public final class RedisClient { private static volatile JedisPool jedisPool; public static void construct(Properties p){ try { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(Integer.parseInt(p.getProperty("jedis.pool.maxTotal"))); if(jedisPool==null){ synchronized (RedisClient.class){ if (jedisPool==null){ jedisPool = new JedisPool(config,p.getProperty("redis.host"), Integer.parseInt(p.getProperty("redis.port")), Integer.parseInt(p.getProperty("redis.timeOut")),p.getProperty("redis.auth"), Integer.parseInt(p.getProperty("redis.db"))); } } } } } }
這里用volatile+雙重檢查來(lái)實(shí)現(xiàn)單例模式,和標(biāo)準(zhǔn)的單例模式區(qū)別是,本例并不需要返回jedisPool實(shí)例,而是返回了一個(gè)jedis連接。
上面的JedisPool用到了享元模式(使用共享對(duì)象來(lái)有效地支持大量的細(xì)粒度的對(duì)象)
圖:
JedisPool就是FlyweightFactory,jedis 就是 ConcreteFlyweight,抽象的Flyweight在本例沒(méi)有設(shè)置,但是我們知道它肯定是封裝了常見(jiàn)的redis操作接口的,UNsharedConcreteFactory也沒(méi)有對(duì)應(yīng)設(shè)置,因?yàn)閖edis對(duì)客戶(hù)端都是一樣的,所以所有部分都不是不可分享的。通過(guò)池操作,使得固定數(shù)量N(甚至更少)的jedis對(duì)象可以服務(wù)于遠(yuǎn)超N個(gè)的客戶(hù)端對(duì)象,達(dá)到共享和復(fù)用的目的。
Netty的應(yīng)用我們啟動(dòng)Netty服務(wù)器的時(shí)候,服務(wù)端使用ServerBootstrap,一般而言代碼如下:
NioEventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(group) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new EchoServerHandler()); } }); ChannelFuture f = b.bind().sync();
這里使用了建造者模式(將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離, 使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示)
圖:
本例中ServerBootstrap是具體建造者,其繼承的AbstractBootstrap是抽象建造者,返回的Product是ChannelFuture,我們的調(diào)用代碼是Director。通過(guò)建造者模式,我們?cè)跇?gòu)建復(fù)雜對(duì)象的時(shí)候不必一次性確定全部參數(shù)(碩大的構(gòu)造函數(shù)),而是根據(jù)需要一步一步構(gòu)建一個(gè)完整的對(duì)象(也比一個(gè)一個(gè)調(diào)用setter的方式節(jié)省代碼而且美觀(guān)),所以每一個(gè)構(gòu)造過(guò)程函數(shù)都要返回一個(gè)完整的對(duì)象this。說(shuō)到這就不得不說(shuō)一說(shuō)裝飾器模式(動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō), 裝飾模式相比生成子類(lèi)更為靈活)
圖:
裝飾器模式的ConcreateDecorator可以在不改變ConcreateComponent給其添加一些新功能或者新特性,就像建造者模式,每一步建造過(guò)程都在給自身添加新功能或者新特性,也就是說(shuō)如果看做裝飾器模式,那么ConcreateDecorator和ConcreateComponent都是Builder自身,而且添加過(guò)程和得到的結(jié)果都相對(duì)穩(wěn)定,所以建造者模式是一種特殊的裝飾器模式。裝飾器模式在java的IO類(lèi)中應(yīng)用廣泛。
與裝飾器模式非常相似的模式有適配器模式(將一個(gè)類(lèi)的接口變換成客戶(hù)端所期待的另一種接口, 從而使原本因接口不匹配而無(wú)法在一起工作的兩個(gè)類(lèi)能夠在一起工作)
圖:
和代理模式(為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn))
圖:
和外觀(guān)模式(要求一個(gè)子系統(tǒng)的外部與其內(nèi)部的通信必須通過(guò)一個(gè)統(tǒng)一的對(duì)象進(jìn)行外觀(guān)模式提供一個(gè)高層次的接口,使得子系統(tǒng)更易于使用)
圖:
這4個(gè)模式都是將原本的對(duì)象進(jìn)行包裝轉(zhuǎn)換實(shí)現(xiàn)另一些功能,不同的是:
裝飾器模式關(guān)注于在一個(gè)對(duì)象上動(dòng)態(tài)的添加方法,增加新的行為,實(shí)現(xiàn)新功能
適配器模式關(guān)注于將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶(hù)希望的另外一個(gè)不同的接口,使得原本接口不兼容而不能一起工作的那些類(lèi)可以兼容
代理模式關(guān)注于為其他對(duì)象提供一種代理以實(shí)現(xiàn)對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn)控制,代理與被代理對(duì)象實(shí)現(xiàn)相同的接口
外觀(guān)模式關(guān)注于為子系統(tǒng)中的一組接口提供一個(gè)一致的界面,此模式簡(jiǎn)化接口,使得子系統(tǒng)更加容易使用
netty處理與客戶(hù)端之間的消息往來(lái)使用的ChannelPipeline與ChannelHandler模型是一個(gè)典型的命令模式(將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而讓你使用不同的請(qǐng)求把客戶(hù)端參數(shù)化,對(duì)請(qǐng)求、排隊(duì)或者記錄請(qǐng)求日志,還提供命令的撤銷(xiāo)和恢復(fù)功能)的使用
圖:
在java中,常常將ConcreteCommand和Receiver合并為一個(gè)對(duì)象,這樣每個(gè)命令都完成一個(gè)職責(zé),而不是根據(jù)接收者的不同完成不同的職責(zé),client調(diào)用時(shí)就不用考慮接收者是誰(shuí)。模式如下:
//非儉省模式定義 接收者 和 命令 Receiver receiver = new ConcreteReciver1(); Command command = new ConcreteCommand1(receiver); //儉省模式 只定義一個(gè)發(fā)送給接收者的具體命令 Command command = new ConcreteCommand1(); //首先聲明調(diào)用者Invoker Invoker invoker = new Invoker(); //把命令交給調(diào)用者 invoker.setCommand(command); //執(zhí)行 invoker.action();
java Runable就是一個(gè)絕佳的示例:
//定義一個(gè)具體的命令賦值給抽象的命令引用,里面的run可以理解為receiver Runnable runnable = new Runnable() { @Override public void run() { System.out.println(2); } }; //聲明調(diào)用者Invoker并把命令交給調(diào)用者 Thread thread = new Thread(runnable); //執(zhí)行 thread.start();
netty中一個(gè)ChannelInboundHandler收到消息后調(diào)用ChannelHandlerContext(繼承了ChannelInboundInvoker)的fireChannelRead去調(diào)用下一個(gè)ChannelInboundHandler的channelRead方法。
實(shí)際做法中,ChannelInboundInvoker是抽象的Invoker,而AbstractChannelHandlerContext才是真正的具體Invoker,其 static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) 方法(也就是執(zhí)行方法)調(diào)用了下一個(gè)(next)ChannelInboundHandler(也就是receiver)的channelRead方法(也就是具體命令)
ChannelPipeline與ChannelHandler模型也是一個(gè)標(biāo)準(zhǔn)的責(zé)任鏈模式(使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免了請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有對(duì)象處理它為止)
圖:
Handler就是netty中的ChannelHandler接口,消息處理的每一個(gè)ConcreteHandler(一般由我們自己實(shí)現(xiàn))都會(huì)去調(diào)用下一個(gè)ConcreteHandler。
ChannelPipeline與ChannelHandler模型實(shí)際上還是一個(gè)非典型的模板方法模式(定義一個(gè)操作中的算法的框架, 而將一些步驟延遲到子類(lèi)中,使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟)
圖:
也就是說(shuō),netty規(guī)定了處理客戶(hù)端的連接的算法是先用一些列抽象的ChannelInboundHandler處理(比如解碼、解密),然后再由一系列抽象的ChannelOutboundHandler處理(比如編碼、加密),但是具體的Handler實(shí)現(xiàn)是我們自己加入的,如上面代碼改一下:
ch.pipeline().addLast(new DecodeHandler()); ch.pipeline().addLast(new EncodeHandler()); ch.pipeline().addLast(new BusinessHandler());
說(shuō)他非典型主要是模板方法模式的算法的框架是確定的(比如確定了要解碼、存儲(chǔ)、編碼三個(gè)步驟),不確定的只是細(xì)節(jié),但是在netty中不僅細(xì)節(jié),算法框架本身我們都可以自己修改(可以加入很多的Handler)。
其他橋接模式(抽象和實(shí)現(xiàn)解耦,使得兩者可以獨(dú)立地變化)
圖:
Abstraction的主要職責(zé)是定義出該角色的行為,同時(shí)保存一個(gè)對(duì)Implementor的引用,該角色一般是抽象類(lèi);
Implementor是接口或者抽象類(lèi),定義角色必需的行為和屬性;
RefinedAbstraction引用Implementor對(duì)Abstraction進(jìn)行修正;
ConcreteImplementor實(shí)現(xiàn)接口或抽象類(lèi)定義的方法和屬性。
所謂將抽象和實(shí)現(xiàn)解耦就是說(shuō)抽象與實(shí)現(xiàn)不是直接通過(guò)繼承來(lái)強(qiáng)耦合,而是通過(guò)對(duì)象組合構(gòu)成的一座橋來(lái)實(shí)現(xiàn)弱耦合。
最經(jīng)典的橋接模式就是JDBC,JDBC為所有的數(shù)據(jù)庫(kù)提供通用的接口(Abstraction), 一個(gè)應(yīng)用程序可以根據(jù)需要選擇的驅(qū)動(dòng)程序(Implementor), 通過(guò)具體的驅(qū)動(dòng)程序(ConcreteImplementor)向的數(shù)據(jù)庫(kù)發(fā)起請(qǐng)求. 這個(gè)過(guò)程就是Abstraction把行為委托給Implementor的過(guò)程,這樣一來(lái)應(yīng)用程序和具體的驅(qū)動(dòng)程序都可以獨(dú)立變化
中介模式(用一個(gè)中介對(duì)象封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯示地相互作用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互 )
圖:
Mediator 定義統(tǒng)一的接口,用于各Colleague之間的通信;
ConcreteMediator 通過(guò)協(xié)調(diào)各Colleague實(shí)現(xiàn)協(xié)作行為,因此它必須依賴(lài)于各個(gè)Colleague;
Colleague 都知道Mediator,而且與其他的Colleague的時(shí)候,都通過(guò)Mediator協(xié)作。
中介者模式的優(yōu)點(diǎn)就是減少類(lèi)間的依賴(lài),把原有的一對(duì)多的依賴(lài)變成了一對(duì)一的依賴(lài),Colleague只依賴(lài)Mediator,降低了類(lèi)間的耦合。
最經(jīng)典的中介模式是MVC框架的運(yùn)用,其中的C就是一個(gè)中介者,把M和V隔離開(kāi),協(xié)調(diào)M和V協(xié)同工作,把M運(yùn)行的結(jié)果和V代表的視圖融合成一個(gè)前端可以展示的頁(yè)面,減少M(fèi)和V的依賴(lài)關(guān)系
備忘錄模式(在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),這樣以后就可將該對(duì)象恢復(fù)到原先保存的態(tài))
圖:
Originator 記錄當(dāng)前時(shí)刻的內(nèi)部狀態(tài),負(fù)責(zé)定義哪些屬于備份范圍的狀態(tài),負(fù)責(zé)創(chuàng)建和恢復(fù)備忘錄數(shù)據(jù);
Memento 負(fù)責(zé)存儲(chǔ)Originator發(fā)起人對(duì)象的內(nèi)部狀態(tài),在需要的時(shí)候提供發(fā)起人需要的內(nèi)部狀態(tài);
Caretaker 對(duì)備忘錄進(jìn)行管理、保存和提供備忘錄。
最經(jīng)典的備忘錄模式就是jdbc的事務(wù)功能,因?yàn)橐峁┗貪L,所以必然要用備忘錄模式。
訪(fǎng)問(wèn)者模式(封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作, 它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作)
圖:
Visitor 是抽象類(lèi)或者接口,聲明訪(fǎng)問(wèn)者可以訪(fǎng)問(wèn)哪些元素,具體到程序中就是visit方法的參數(shù)定義哪些對(duì)象是可以被訪(fǎng)問(wèn)的;
ConcreteVisitor 影響訪(fǎng)問(wèn)者訪(fǎng)問(wèn)到一個(gè)類(lèi)后該怎么干,要做什么事情;
Element 接口或者抽象類(lèi),聲明接受哪一類(lèi)訪(fǎng)問(wèn)者訪(fǎng)問(wèn),程序上是通過(guò)accept方法中的參數(shù)來(lái)定義的;
ConcreteElement 實(shí)現(xiàn)accept方法,通常是visitor.visit(this),基本上都形成了一種模式了;
ObjectStruture Element產(chǎn)生者,一般容納在多個(gè)不同類(lèi)、不同接口的容器,如List、 Set、 Map等,在項(xiàng)目中,一般很少抽象出這個(gè)角色。
訪(fǎng)問(wèn)者模式可以將數(shù)據(jù)的構(gòu)成與使用方法解耦,擴(kuò)展性很好。
組合模式說(shuō)白了就是個(gè)樹(shù)形結(jié)構(gòu);
迭代器模式基本沒(méi)有人會(huì)自己實(shí)現(xiàn)了;
解釋器模式使用的很少;
所有的設(shè)計(jì)模式無(wú)非都是這幾個(gè)原則的體現(xiàn)(當(dāng)然有些會(huì)違背),這些原則指導(dǎo)著我們寫(xiě)出更健壯、穩(wěn)定、易維護(hù)的程序。
單一職責(zé)原則:應(yīng)該有且僅有一個(gè)原因引起類(lèi)的變更,但是這“一個(gè)原因”怎么定義需要我們根據(jù)業(yè)務(wù)自己拿捏
里氏替換原則:所有引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象,記住要確實(shí)有is-a的關(guān)系才用繼承,否則就使用依賴(lài)、聚集、組合的方式
依賴(lài)倒置原則:高層模塊不應(yīng)該依賴(lài)低層模塊(原子邏輯), 兩者都應(yīng)該依賴(lài)其抽象,抽象(接口)不應(yīng)該依賴(lài)細(xì)節(jié)(實(shí)現(xiàn)類(lèi)),細(xì)節(jié)應(yīng)該依賴(lài)抽象,更加精簡(jiǎn)的說(shuō)法就是“面向接口編程”
接口隔離原則:類(lèi)間的依賴(lài)關(guān)系應(yīng)該建立在最小的接口上,也就是說(shuō)接口盡量細(xì)化
迪米特法則:也稱(chēng)最少知識(shí)原則,一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解,知道的越多耦合就越高就越不容易修改
開(kāi)閉原則:一個(gè)軟件實(shí)體如類(lèi)、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,就是說(shuō)我們的功能變化要通過(guò)擴(kuò)展來(lái)實(shí)現(xiàn)而不是通過(guò)修改已有代碼實(shí)現(xiàn),這樣系統(tǒng)穩(wěn)定性才更高,也更靈活
感謝設(shè)計(jì)模式之禪與HeadFirst設(shè)計(jì)模式,這兩本書(shū)隨便選一本看完都可以。
閱讀原文,作者:MageekChiu
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/68768.html
摘要:本文是作者自己對(duì)中線(xiàn)程的狀態(tài)線(xiàn)程間協(xié)作相關(guān)使用的理解與總結(jié),不對(duì)之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機(jī)制講得很透徹,深入淺出。 一小時(shí)搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個(gè)接口,程序可以通過(guò)...
摘要:語(yǔ)料庫(kù)是由文本構(gòu)成的數(shù)據(jù)集通過(guò)提供現(xiàn)成的文本數(shù)據(jù)來(lái)輔助文本處理。那么可以用來(lái)做什么呢我自己是一名從事是不錯(cuò)的入門(mén)選項(xiàng)。大數(shù)據(jù)和人工智能是機(jī)器學(xué)習(xí)和的主要開(kāi)發(fā)語(yǔ)言。 Python培訓(xùn)有哪些內(nèi)容?很多零基礎(chǔ)學(xué)員不知道Python軟件是干什么用的?Python軟件是Python工程師編寫(xiě)代碼時(shí)所需...
摘要:接下來(lái)的不同采樣格式都是在一張圖像所有像素的轉(zhuǎn)換到基礎(chǔ)上進(jìn)行的。采樣采樣,意味著分量是分量采樣的一半,分量和分量按照的比例采樣。基于采樣的格式基于采樣的格式主要有和兩種類(lèi)型,每個(gè)類(lèi)型又對(duì)應(yīng)其他具體格式。YUV 是一種顏色編碼方法,和它等同的還有 RGB 顏色編碼方法。 RGB 顏色編碼 RGB 三個(gè)字母分別代表了 紅(Red)、綠(Green)、藍(lán)(Blue),這三種顏色稱(chēng)為 三原色,將它們...
閱讀 2536·2021-10-12 10:11
閱讀 1287·2021-10-11 10:58
閱讀 3353·2019-08-30 15:54
閱讀 783·2019-08-30 13:59
閱讀 730·2019-08-29 13:07
閱讀 1473·2019-08-26 11:55
閱讀 2206·2019-08-26 10:44
閱讀 2770·2019-08-23 18:25