国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

一文理清21種設計模式:用實例分析和對比

PrototypeZ / 829人閱讀

摘要:設計模式無論是對于最底層的的編碼實現還是較高層的架構設計都有著重要的指導作用。所謂光說不練假把式,今天我就把項目中常見的應用場景涉及到的主要設計模式及其相關設計模式總結一下,用實例分析和對比的方式在一片文章中就把最常見的種設計模式梳理清楚。

設計模式無論是對于最底層的的編碼實現還是較高層的架構設計都有著重要的指導作用。所謂光說不練假把式,今天我就把項目中常見的應用場景涉及到的主要設計模式及其相關設計模式總結一下,用實例分析和對比的方式在一片文章中就把最常見的21種設計模式梳理清楚。

Redis發布訂閱

在項目中常常使用redis的發布/訂閱功能,用來實現進程間通信甚至IM等業務。
使用 jedis 實現頻道訂閱的模式一般如下:

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);
    }
    // 初始化訂閱時候的處理
    public void onSubscribe(String channel, int subscribedChannels) {
        logger.info("訂閱:{},總數:{}",channel,subscribedChannels);
    }
    // 取消訂閱時候的處理
    public void onUnsubscribe(String channel, int subscribedChannels) {
        logger.info("取消訂閱:{},總數:{}",channel,subscribedChannels);
    }
}

這里使用了策略模式對算法的封裝,把使用算法的責任和算法本身分隔開,委派給不同的對象管理。策略模式通常把一系列的算法包裝到一系列的策略類里面,作為抽象策略類的子類)。
圖:

本例中,JedisPubSub抽象策略類,定義不同事件發生時的響應模式亦即所支持的算法的公共接口;MySubListener是一個具體策略類定義了一種具體的事件響應方式(簡單的打印);jedis是就是Context,負責維護調用者與策略之間的聯系。這樣不同的調用者只需要傳入不同的事件響應具體算法如MySubListener1、2、3等即可(而不是去修改已有算法),實現了對擴展開放,對修改關閉的開閉原則
jedis 發布事件的代碼如下:

try {
    jedis.publish("channel","message to be published");
}

說到這就不得不說說狀態模式當一個對象內在狀態改變時允許其改變行為, 這個對象看起來像改變了其類
圖:

Context定義客戶端需要的接口, 并且負責具體狀態的切換
State接口或抽象類,負責對象狀態定義,并且封裝Context以實現狀態切換;
ConcreteState每一個具體狀態必須完成兩個職責:就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態
狀態模式和策略模式都是為具有多種可能情形設計的模式,把不同的處理情形抽象為一個相同的接口,符合開閉原則。但是狀態模式將各個狀態對應的操作分離開來,即不同的狀態由不同的子類實現具體操作,狀態切換由子類實現,當發現傳入參數不是自己這個狀態所對應的參數,則自己給Context類切換狀態,也就是說客戶端并不知曉狀態;而策略模式是直接依賴注入到Context類的參數進行選擇策略,不存在切換狀態的操作,也就是說狀態和策略是由客戶端自己定的

回到本例,發布/訂閱本身就是觀察者模式定義對象間一種一對多的依賴關系,使得每當一個對象改變狀態,則所有依賴于它的對象都會得到通知并被自動更新)的運用。
圖:

可以結合Redis設計與實現查看redis實現發布訂閱的原理。本例中,JedisPubSub抽象觀察者MySubListener具體觀察者抽象主題沒有顯式定義,但是我們知道它的標準就是能夠添加、刪除、通知觀察者(如調用onMessage方法),具體主題就是redis里面包含"channel"這個模式的頻道。這就把消息生產者和消費者解耦了,消費者不用管生產者如何產生消息,生產者不用管消費者如何處理消息,兩者直接是松耦合的,也就是說兩者僅依賴于通知機制進行交互而不知道對方的實現細節,這樣只要保持通知機制,雙方都可以隨意擴展。

請注意上面代碼的Jedis jedis = RedisClient.getJedis()是一個靜態工廠方法模式或者說簡單工廠模式通過專門定義一個類使用靜態方法來負責創建其他類的實例,被創建的實例通常都具有共同的父類或者父接口)的使用。
圖:

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();
    }
}

本例中類RedisClient就是Creator,返回的redis客戶端Jedis就是ConcreateProduct,由于目前只用了 jedis 這一種 java redis client 所以沒有設置抽象的Product,如果有多種client那么就要設置抽象的Product(這些Product都要有set、hset等redis通用操作),然后再在getJedis函數中去根據需要產生不同的client(if else 或者 switch case)。
靜態工廠方法的好處在于:增加新的Product類(比如新的java redis client)的時候老的類不需要改變,調用者由于只依賴于接口(抽象的Product)也不用改變,亦即把變化封裝到工廠內部了;可讀性更強(比如getJedis你就知道他要干啥,而不是使用不知所以的構造函數);緩存增強性能(比如上面的jedisPool就一直存在著,避免每次獲取連接時新創建連接池);代碼簡潔等等。

靜態工廠方法模式的缺點就是新加入一個Product類的時候,其工廠方法本身需要改變(比如多一個判斷的case分支),解決辦法就是采用每種具體Product對應一個具體工廠的工廠模式定義一個用于創建對象的接口, 讓子類決定實例化哪一個類。 工廠方法使一個類的實例化延遲到其子類
圖:

工廠模式把每一種 product 類的實例化過程都封裝到了一個對應的工廠類中,新加入product的時候不需要改任何的舊代碼,只需要同時添加對應的具體工廠類即可。高層模塊只需要知道產品的抽象類,其他的具體實現類都不需要關心,符合迪米特法則,依賴倒置原則,里氏替換原則

然后就不得不說說抽象層次更高、更具一般性的抽象工廠模式為創建一組相關或相互依賴的對象提供一個接口, 而且無須指定它們的具體類)了
圖:

抽象工廠與工廠方法的區別就是可能有多個抽象Product,也就是說每一個具體工廠能夠生產一個產品族而不只是一個產品,可以把抽象工廠簡單理解為工廠方法+簡單工廠,每一個具體工廠都是一個簡單工廠。

說到工廠模式就不得不說原型模式用原型實例指定創建對象的種類, 并且通過拷貝這些原型創建新的對象
圖:

原型模式的核心是Override Object 類的 clone 方法,通過該方法進行對象的拷貝,由于是內存二進制流的拷貝,所以比直接new性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。在實際項目中,原型模式很少多帶帶出現,一般是和工廠方法模式一起出現,通過clone的方法創建一個對象, 然后由工廠方法提供給調用者。

繼續觀察上面的RedisClient類,我們知道,連接池jedisPool在整個應用中是只需要一個實例的,也就是說我們要使用單例模式確保某一個類只有一個實例, 而且自行實例化并向整個系統提供這個實例
圖:

所以我們的代碼要修改一下:

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+雙重檢查來實現單例模式,和標準的單例模式區別是,本例并不需要返回jedisPool實例,而是返回了一個jedis連接。

上面的JedisPool用到了享元模式使用共享對象來有效地支持大量的細粒度的對象
圖:

JedisPool就是FlyweightFactoryjedis 就是 ConcreteFlyweight抽象的Flyweight在本例沒有設置,但是我們知道它肯定是封裝了常見的redis操作接口的,UNsharedConcreteFactory也沒有對應設置,因為jedis對客戶端都是一樣的,所以所有部分都不是不可分享的。通過池操作,使得固定數量N(甚至更少)的jedis對象可以服務于遠超N個的客戶端對象,達到共享和復用的目的。

Netty的應用

我們啟動Netty服務器的時候,服務端使用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();

這里使用了建造者模式將一個復雜對象的構建與它的表示分離, 使得同樣的構建過程可以創建不同的表示
圖:

本例中ServerBootstrap具體建造者,其繼承的AbstractBootstrap抽象建造者,返回的ProductChannelFuture,我們的調用代碼是Director。通過建造者模式,我們在構建復雜對象的時候不必一次性確定全部參數(碩大的構造函數),而是根據需要一步一步構建一個完整的對象(也比一個一個調用setter的方式節省代碼而且美觀),所以每一個構造過程函數都要返回一個完整的對象this。說到這就不得不說一說裝飾器模式動態地給一個對象添加一些額外的職責。就增加功能來說, 裝飾模式相比生成子類更為靈活
圖:

裝飾器模式的ConcreateDecorator可以在不改變ConcreateComponent給其添加一些新功能或者新特性,就像建造者模式,每一步建造過程都在給自身添加新功能或者新特性,也就是說如果看做裝飾器模式,那么ConcreateDecoratorConcreateComponent都是Builder自身,而且添加過程和得到的結果都相對穩定,所以建造者模式是一種特殊的裝飾器模式。裝飾器模式在java的IO類中應用廣泛。
與裝飾器模式非常相似的模式有適配器模式將一個類的接口變換成客戶端所期待的另一種接口, 從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作
圖:

代理模式為其他對象提供一種代理以控制對這個對象的訪問
圖:

外觀模式要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行外觀模式提供一個高層次的接口,使得子系統更易于使用
圖:

這4個模式都是將原本的對象進行包裝轉換實現另一些功能,不同的是:

裝飾器模式關注于在一個對象上動態的添加方法,增加新的行為,實現新功能

適配器模式關注于將一個類的接口轉換成客戶希望的另外一個不同的接口,使得原本接口不兼容而不能一起工作的那些類可以兼容

代理模式關注于為其他對象提供一種代理以實現對這個對象的訪問控制,代理與被代理對象實現相同的接口

外觀模式關注于為子系統中的一組接口提供一個一致的界面,此模式簡化接口,使得子系統更加容易使用

netty處理與客戶端之間的消息往來使用的ChannelPipelineChannelHandler模型是一個典型的命令模式將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化,對請求、排隊或者記錄請求日志,還提供命令的撤銷和恢復功能)的使用
圖:

在java中,常常將ConcreteCommandReceiver合并為一個對象,這樣每個命令都完成一個職責,而不是根據接收者的不同完成不同的職責,client調用時就不用考慮接收者是誰。模式如下:

//非儉省模式定義 接收者 和 命令
Receiver receiver = new ConcreteReciver1(); Command command = new ConcreteCommand1(receiver);
//儉省模式 只定義一個發送給接收者的具體命令
Command command = new ConcreteCommand1();
//首先聲明調用者Invoker
Invoker invoker = new Invoker();
//把命令交給調用者
invoker.setCommand(command);
//執行
invoker.action();

java Runable就是一個絕佳的示例:

//定義一個具體的命令賦值給抽象的命令引用,里面的run可以理解為receiver
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println(2);
    }
};
//聲明調用者Invoker并把命令交給調用者
Thread thread = new Thread(runnable);
//執行
thread.start();    

netty中一個ChannelInboundHandler收到消息后調用ChannelHandlerContext(繼承了ChannelInboundInvoker)的fireChannelRead去調用下一個ChannelInboundHandlerchannelRead方法。
實際做法中,ChannelInboundInvoker是抽象的Invoker,而AbstractChannelHandlerContext才是真正的具體Invoker,其 static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) 方法(也就是執行方法)調用了下一個(next)ChannelInboundHandler(也就是receiver)的channelRead方法(也就是具體命令

ChannelPipelineChannelHandler模型也是一個標準的責任鏈模式使多個對象都有機會處理請求,從而避免了請求的發送者和接受者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止
圖:

Handler就是netty中的ChannelHandler接口,消息處理的每一個ConcreteHandler(一般由我們自己實現)都會去調用下一個ConcreteHandler。

ChannelPipelineChannelHandler模型實際上還是一個非典型的模板方法模式定義一個操作中的算法的框架, 而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟
圖:

也就是說,netty規定了處理客戶端的連接的算法是先用一些列抽象的ChannelInboundHandler處理(比如解碼、解密),然后再由一系列抽象的ChannelOutboundHandler處理(比如編碼、加密),但是具體的Handler實現是我們自己加入的,如上面代碼改一下:

ch.pipeline().addLast(new DecodeHandler());
ch.pipeline().addLast(new EncodeHandler());
ch.pipeline().addLast(new BusinessHandler());

說他非典型主要是模板方法模式的算法的框架是確定的(比如確定了要解碼、存儲、編碼三個步驟),不確定的只是細節,但是在netty中不僅細節,算法框架本身我們都可以自己修改(可以加入很多的Handler)。

其他

橋接模式抽象和實現解耦,使得兩者可以獨立地變化
圖:

Abstraction的主要職責是定義出該角色的行為,同時保存一個對Implementor的引用,該角色一般是抽象類;
Implementor是接口或者抽象類,定義角色必需的行為和屬性;
RefinedAbstraction引用Implementor對Abstraction進行修正;
ConcreteImplementor實現接口或抽象類定義的方法和屬性。
所謂將抽象和實現解耦就是說抽象與實現不是直接通過繼承來強耦合,而是通過對象組合構成的一座橋來實現弱耦合。
最經典的橋接模式就是JDBC,JDBC為所有的數據庫提供通用的接口(Abstraction), 一個應用程序可以根據需要選擇的驅動程序(Implementor), 通過具體的驅動程序(ConcreteImplementor)向的數據庫發起請求. 這個過程就是Abstraction把行為委托給Implementor的過程,這樣一來應用程序和具體的驅動程序都可以獨立變化

中介模式用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合松散,而且可以獨立地改變它們之間的交互
圖:

Mediator 定義統一的接口,用于各Colleague之間的通信;
ConcreteMediator 通過協調各Colleague實現協作行為,因此它必須依賴于各個Colleague;
Colleague 都知道Mediator,而且與其他的Colleague的時候,都通過Mediator協作。
中介者模式的優點就是減少類間的依賴,把原有的一對多的依賴變成了一對一的依賴,Colleague只依賴Mediator,降低了類間的耦合。
最經典的中介模式是MVC框架的運用,其中的C就是一個中介者,把M和V隔離開,協調M和V協同工作,把M運行的結果和V代表的視圖融合成一個前端可以展示的頁面,減少M和V的依賴關系

備忘錄模式在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,這樣以后就可將該對象恢復到原先保存的態
圖:

Originator 記錄當前時刻的內部狀態,負責定義哪些屬于備份范圍的狀態,負責創建和恢復備忘錄數據;
Memento 負責存儲Originator發起人對象的內部狀態,在需要的時候提供發起人需要的內部狀態;
Caretaker 對備忘錄進行管理、保存和提供備忘錄。
最經典的備忘錄模式就是jdbc的事務功能,因為要提供回滾,所以必然要用備忘錄模式。

訪問者模式封裝一些作用于某種數據結構中的各元素的操作, 它可以在不改變數據結構的前提下定義作用于這些元素的新的操作
圖:

Visitor 是抽象類或者接口,聲明訪問者可以訪問哪些元素,具體到程序中就是visit方法的參數定義哪些對象是可以被訪問的;
ConcreteVisitor 影響訪問者訪問到一個類后該怎么干,要做什么事情;
Element 接口或者抽象類,聲明接受哪一類訪問者訪問,程序上是通過accept方法中的參數來定義的;
ConcreteElement 實現accept方法,通常是visitor.visit(this),基本上都形成了一種模式了;
ObjectStruture Element產生者,一般容納在多個不同類、不同接口的容器,如List、 Set、 Map等,在項目中,一般很少抽象出這個角色。
訪問者模式可以將數據的構成與使用方法解耦,擴展性很好。

省略的設計模式

組合模式說白了就是個樹形結構;
迭代器模式基本沒有人會自己實現了;
解釋器模式使用的很少;

附錄——六大設計模式原則

所有的設計模式無非都是這幾個原則的體現(當然有些會違背),這些原則指導著我們寫出更健壯、穩定、易維護的程序。

單一職責原則:應該有且僅有一個原因引起類的變更,但是這“一個原因”怎么定義需要我們根據業務自己拿捏

里氏替換原則:所有引用基類的地方必須能透明地使用其子類的對象,記住要確實有is-a的關系才用繼承,否則就使用依賴、聚集、組合的方式

依賴倒置原則:高層模塊不應該依賴低層模塊(原子邏輯), 兩者都應該依賴其抽象,抽象(接口)不應該依賴細節(實現類),細節應該依賴抽象,更加精簡的說法就是“面向接口編程”

接口隔離原則:類間的依賴關系應該建立在最小的接口上,也就是說接口盡量細化

迪米特法則:也稱最少知識原則,一個對象應該對其他對象有最少的了解,知道的越多耦合就越高就越不容易修改

開閉原則:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉,就是說我們的功能變化要通過擴展來實現而不是通過修改已有代碼實現,這樣系統穩定性才更高,也更靈活

感謝設計模式之禪與HeadFirst設計模式,這兩本書隨便選一本看完都可以。
閱讀原文,作者:MageekChiu

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/68768.html

相關文章

  • Java相關

    摘要:本文是作者自己對中線程的狀態線程間協作相關使用的理解與總結,不對之處,望指出,共勉。當中的的數目而不是已占用的位置數大于集合番一文通版集合番一文通版垃圾回收機制講得很透徹,深入淺出。 一小時搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關聯任何信息和著任何元數據(metadata)的途徑和方法。Annotion(注解) 是一個接口,程序可以通過...

    wangtdgoodluck 評論0 收藏0
  • python能做什么軟件?Python到底能干嘛,一文看懂

    摘要:語料庫是由文本構成的數據集通過提供現成的文本數據來輔助文本處理。那么可以用來做什么呢我自己是一名從事是不錯的入門選項。大數據和人工智能是機器學習和的主要開發語言。 Python培訓有哪些內容?很多零基礎學員不知道Python軟件是干什么用的?Python軟件是Python工程師編寫代碼時所需...

    YorkChen 評論0 收藏0
  • 一文讀懂 YUV 的采樣與格式

    摘要:接下來的不同采樣格式都是在一張圖像所有像素的轉換到基礎上進行的。采樣采樣,意味著分量是分量采樣的一半,分量和分量按照的比例采樣。基于采樣的格式基于采樣的格式主要有和兩種類型,每個類型又對應其他具體格式。YUV 是一種顏色編碼方法,和它等同的還有 RGB 顏色編碼方法。 RGB 顏色編碼 RGB 三個字母分別代表了 紅(Red)、綠(Green)、藍(Blue),這三種顏色稱為 三原色,將它們...

    tracy 評論0 收藏0

發表評論

0條評論

PrototypeZ

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<