設計模式學習筆記

設計模式

做者:Greyhtml

原文地址:java

Githubnode

語雀git

博客園github

UML和代碼

UML圖算法

代碼數據庫

單例模式

UML

餓漢式

類加載的時候就會初始化這個實例, JVM保證惟一實例,線程安全, 可是能夠經過反射破壞編程

方式一設計模式

public class Singleton1 {
    private final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

方式二安全

public class Singleton2 {
    private static final Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

懶漢式

雖然能夠實現按需初始化,可是線程不安全, 由於在判斷INSTANCE == null的時候,若是是多個線程操做的話, 一個線程尚未把INSTANCE初始化好,另一個線程判斷INSTANCE==null 獲得true,就會繼續初始化

public class Singleton3 {
    private static Singleton3 INSTANCE;

    private Singleton3() {

    }

    public static Singleton3 getInstance() {
        if (INSTANCE == null) {
            // 模擬初始化對象須要的耗時操做
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
}

爲了防止線程不安全,能夠在getInstance方法上加鎖,這樣既實現了按需初始化,又保證了線程安全,可是加鎖可能會致使一些性能的問題

public class Singleton4 {
    private static Singleton4 INSTANCE;

    private Singleton4() {
    }

    public static synchronized Singleton4 getInstance() {
        if (INSTANCE == null) {
            // 模擬初始化對象須要的耗時操做
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }
}

爲了提高一點點性能,能夠不給getInstance整個方法加鎖,而是對INSTANCE判空這段代碼加鎖, 可是又帶來了線程不安全的問題

public class Singleton5 {
    private static Singleton5 INSTANCE;

    private Singleton5() {
    }

    public static Singleton5 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton5.class) {
                // 模擬初始化對象須要的耗時操做
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Singleton5();
            }
        }
        return INSTANCE;
    }
}

Double Check Locking模式,就是雙加鎖檢查模式

這種方式中,Volatile是必需的,目的爲了防止指令重排,生成一個半初始化的的實例,致使生成兩個實例

具體可參考 雙重檢索(DCL)的思考: 爲何要加volatile?
說了這個問題

public class Singleton6 {
    private volatile static Singleton6 INSTANCE;

    private Singleton6() {
    }

    public static Singleton6 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton6.class) {
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton6();
                }
            }
        }
        return INSTANCE;
    }
}

如下兩種更爲優雅的方式,既保證了線程安全,又實現了按需加載

方式一:靜態內部類方式,JVM保證單例,加載外部類時不會加載內部類,這樣能夠實現懶加載

public class Singleton7 {
    private Singleton7() {
    }

    public static Singleton7 getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final Singleton7 INSTANCE = new Singleton7();
    }

}

方式二: 使用枚舉, 這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止屢次實例化,這種方式是 Effective Java 做者 Josh Bloch
提倡的方式,它不只能避免多線程同步問題,並且還自動支持序列化機制,防止反序列化從新建立新的對象,絕對防止屢次實例化。

public enum Singleton8 {
    INSTANCE;
}

策略模式

策略模式

實例: 假設咱們有一個貓類,這個類裏面有體重和身高這兩個屬性,給你一個貓的集合,而後須要你按貓的體重從小到大排序

思路: 咱們能夠把體重從小到大這個當作是一個策略,後續可能衍生其餘的策略,好比: 按身高從高到低 按體重從小到大,體重同樣的身高從高到低

以身高從低到高排序這個策略爲例

public class CatSortStrategy implements Comparator<Cat> {

    @Override
    public int compare(Cat o1, Cat o2) {
        return o1.getHeight() - o2.getHeight();
    }
}

假設咱們定義貓排序的方法是: sort 那麼這個方法必然須要傳入一個排序策略的參數(不然我怎麼知道要怎麼排序貓?) 因此定義的sort方法能夠是:

public class Sorter {

    public Cat[] sort(Cat[] items, Comparator<Cat> strategy) {
        int length = items.length;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if (strategy.compare(items[i], items[j]) > 0) {
                    Cat tmp = items[i];
                    items[i] = items[j];
                    items[j] = tmp;
                }
            }
        }
        return items;
    }
}

進一步抽象,若是我想讓Sorter這個工具類不只能夠對貓進行各類策略的排序(基於比較的排序算法),還能夠對狗進行各類策略的排序(基於比較排序算法),能夠將Sorter定義成泛型

public class Sorter<T> {

    public T[] sort(T[] items, Comparator<T> strategy) {
        int length = items.length;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if (strategy.compare(items[i], items[j]) > 0) {
                    T tmp = items[i];
                    items[i] = items[j];
                    items[j] = tmp;
                }
            }
        }
        return items;
    }
}

調用的時候, 泛型版本的Sorter能夠對貓和狗都進行基於特定排序策略的排序。

Sorter<Cat> sorter=new Sorter<>();
        Cat[]sortedCats=sorter.sort(cats,new CatSortStrategy());

        Sorter<Dog> sorter=new Sorter<>();
        Dog[]sortedCats=sorter.sort(dogs,new DogSortStrategy());

工廠模式

  • 簡單工廠
  • 靜態工廠--單例模式
  • 抽象工廠
  • 工廠方法
  • Spring IOC DI
  • Hibernate 換數據庫只需換方言和驅動就能夠

門面模式

[TODO] 能夠有更好的例子

假設咱們有一個需求,是在畫板上畫圓形和矩形,「暴力」寫法就是:

public static void main(String[]args){
        Circle circle=new Sharp();
        Rectangle rectangle=new Sharp();
        circle.draw();
        rectangle.draw();
        }

若是後續要改變畫圓和畫矩形邏輯,咱們就須要動到這個主方法, 用門面模式的方式,咱們能夠經過一個SharpMarker來完成畫圓形和矩形的邏輯(看成外部調用的門面),那麼在改變畫圓和畫矩形邏輯的時候,就不須要改動主方法了

public class SharpMarker {
    private Sharp circle;
    private Sharp rectangle;

    public SharpMarker(Sharp circle, Sharp rectangle) {
        this.circle = circle;
        this.rectangle = rectangle;
    }

    public void draw() {
        circle.draw();
        rectangle.draw();
    }
}

主方法只須要:

public class Main {
    public static void main(String[] args) {
        SharpMarker marker = new SharpMarker(new Rectangle(), new Circle());
        marker.draw();
    }
}

門面模式的UML圖以下

門面模式

  • 應用
    • 消息中間件

調停者/中介模式

舉個簡單的例子,若是一個聊天室裏面的用戶1和用戶2要聊天,聊天室就至關於中介的地位,用戶1和用戶2只管調用發消息方法,聊天室便可把消息給對方

public class ChatRoom {
    public static void showMessage(User user, String content) {
        System.out.println("user :" + user.getName() + " send a message, content is " + content);
    }
}

以上代碼表示,聊天室將user說的content展現出來

主方法只須要以下調用便可:

public class Main {
    public static void main(String[] args) {
        User user = new User("Peter");
        user.sendMessage("Hello ");
        user = new User("Harry");
        user.sendMessage("Hi");
    }
}

User中的sendMessage方法

public void sendMessage(String content){
        ChatRoom.showMessage(this,content);
        }

UML

責任鏈模式

有一段文本須要過濾敏感字,咱們能夠經過責任鏈模式來設計這個功能,假設文本是:scripts Hell World! 996

咱們有多個過濾規則,好比第一個規則是:過濾 scripts 這個關鍵字(實際的規則可能很複雜,目前只是舉這個簡單例子來講明狀況)
第二個規則是:過濾 996 這個關鍵字

咱們能夠抽象一個Filter接口,各類過濾規則無非就是實現這個接口便可

public interface Filter {
    boolean doFilter(Msg msg);
}

過濾 996 的規則:

public class SensitiveFilter implements Filter {
    @Override
    public boolean doFilter(Msg msg) {
        msg.setContent(msg.getContent().replace("996", ""));
        return true;
    }
}

過濾 scripts 的規則:

public class HTMLFilter implements Filter {
    @Override
    public boolean doFilter(Msg msg) {
        msg.setContent(msg.getContent().replace("scripts", ""));
        return true;
    }
}

主方法調用的時候,就直接New 相應的Filter來處理便可:

Msg msg=new Msg();
        msg.setContent("scripts Hell World! 996");
        System.out.println("before filter , the content is : "+msg.getContent());
        Filter html=new HTMLFilter();
        Filter sensitive=new SensitiveFilter();
        html.doFilter(msg);
        sensitive.doFilter(msg);
        System.out.println("after filter , the content is : "+msg.getContent());

不過,更爲優雅的一種方式是設計一個FilterChain,咱們把全部的Filter都加入到這個FilterChain裏面,對於Msg直接去調用FilterChain的過濾方法便可把FilterChain中的全部Filter都執行(
並且還能夠很靈活指定Filter順序)

public class FilterChain implements Filter {
    // 這裏存全部須要應用的Filter
    private List<Filter> filters = new ArrayList<>();

    public FilterChain addFilter(Filter filter) {
        filters.add(filter);
        return this;
    }

    @Override
    public boolean doFilter(Msg msg) {
        // 這裏能夠靈活指定Filter的執行順序
        for (Filter filter : filters) {
            if (!filter.doFilter(msg)) {
                return false;
            }
        }
        return true;
    }
}

那麼主方法在調用的時候,能夠直接經過以下的方式:

public class Main {
    public static void main(String[] args) {
        FilterChain filterChain = new FilterChain();
        filterChain.addFilter(new HTMLFilter()).addFilter(new SensitiveFilter());
        Msg msg = new Msg();
        msg.setContent("scripts Hell World! 996");
        System.out.println("before filter , the content is : " + msg.getContent());
        filterChain.doFilter(msg);
        System.out.println("after filter , the content is : " + msg.getContent());
    }
}

應用

  • Servlet filter[TODO]
  • Structs interceptor
  • SpringMVC interceptor

UML

裝飾器模式

顧名思義,就是對某個方法或者對象進行裝飾,舉個簡單的例子,有個圓形類(Circle),我須要把這個圓形的塗上紅色,其實就是新增一個裝飾器來裝飾這個圓形類。
若是要讓裝飾器通用一些,能夠處理圓形類對應的抽象類 Sharpe,那麼對於任意Shape的子類,均可以用紅色裝飾器來塗紅色。

咱們先定義Sharp這個抽象類:

public abstract class Sharp {
    protected abstract void draw();
}

而後咱們定義Sharp的裝飾類:SharpDecorator,這個類是全部裝飾器類的抽象類,後續的裝飾器只須要實現這個抽象類就能夠對Sharp進行各類裝飾了,

public abstract class SharpDecorator extends Sharp {
    protected Sharp decoratedSharp;

    public SharpDecorator(Sharp decoratedSharp) {
        this.decoratedSharp = decoratedSharp;
    }
}

紅色裝飾器實現這個抽象類便可:

public class RedSharpDecorator extends SharpDecorator {

    public RedSharpDecorator(Sharp decoratedSharp) {
        super(decoratedSharp);
    }

    private static void redIt() {
        System.out.println("[RED]");
    }

    @Override
    protected void draw() {
        redIt();
        this.decoratedSharp.draw();
        redIt();
    }
}

主方法調用的時候只須要:

new RedSharpDecorator(new Circle()).draw();

UML圖以下:

UML

裝飾器模式的應用

  • Java中的IO流, Read/InputStream ,Write/OutputStream

觀察者模式

事件處理 每每和責任鏈模式搭配使用

Spring ApplicationEvent

組合模式

組合模式中,最經常使用的一個用法就是目錄層級的遍歷,話很少說,直接上代碼,主方法中

BranchNode root=new BranchNode("root");
BranchNode branch1=new BranchNode("branch1");
BranchNode branch2=new BranchNode("branch2");
branch1.addNode(new LeafNode("leaf1"));
root.addNode(branch1);
root.addNode(branch2);
tree(root,0);

其中,BranchNode爲分支節點,LeafNode是葉子節點 達到的效果就是打印以下的形式

root
--branch1
----leaf1
--branch2

其中BranchNode和LeafNode都實現了Node接口,Node接口(也能夠爲定義抽象類)僅提供了一個屬性(content:標識節點內容)和一個打印方法:

public abstract class Node {
    protected String content;

    protected abstract void print();
}

BranchNode下能夠包含多個Node,由於一個分支下面能夠有多個分支(這個分支能夠是任意的Node子類)

public class BranchNode extends Node {
    private List<Node> nodes = new ArrayList<>();

    public BranchNode(String content) {
        this.content = content;
    }

    @Override
    public void print() {
        System.out.println(content);
    }
    // get..set方法略 
}

組合模式的UML圖以下:

UML

享元模式

String 鏈接池管理

代理模式

  • 靜態代理

  • 動態代理

    • jdk自帶
      • ASM操做二進制碼
      • Java Instrumentation
    • cglib
      • final類不行,代理類的子類 底層也是ASM
  • Spring AOP

迭代器模式

  • 容器和容器遍歷

訪問者模式

結構不變的狀況下動態改變對於內部元素的動做 作編譯器的時候,生成AST的時候,進行類型檢查 根據抽象語法樹,生成中間代碼

XML文件解析

構建器模式

咱們在對一個實體類進行屬性的get/set的時候,能夠經過封裝一些經常使用的構造方法來簡化實體類的構造

好比:

public class Person {

    private String name;
    private int age;
    private String address;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }

    private Person() {
    }

    public static class PersonBuilder {
        private Person person = new Person();

        public PersonBuilder basicInfo(String name, int age) {
            person.name = name;
            person.age = age;
            return this;
        }

        public PersonBuilder name(String name) {
            person.name = name;
            return this;
        }

        public PersonBuilder age(int age) {
            person.age = age;
            return this;
        }

        public PersonBuilder address(String address) {
            person.address = address;
            return this;
        }

        public Person build() {
            return person;
        }
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }


}

其中PersonBuilder就是一個內部類,用於構造Person的必要信息,外部調用Person的構造方法時候,能夠這樣使用:

Person person=new Person.PersonBuilder().basicInfo("zhangsan",10).address("xxx").build();

UML

還有一種關於set構造器的編寫方式是每次返回this, 這樣能夠實現「鏈式構造」

public class Person {
    private String name;
    private int age;
    // 省略get方法
    public Person name(String name) {
        this.name = name;
        return this;
    }

    public Person age(int age) {
        this.age = age;
        return this;
    }
}

主方法在調用的時候能夠直接:

Person p=new Person();
p.age(10).name("zhangsan");

實際應用有很是多,不少組件都提供這樣的構造方式,好比OkHttpClient的構造方法:

public static OkHttpClient create(long connectTimeOut) {
        return new OkHttpClient().newBuilder()
                .connectionSpecs(Arrays.asList(
                        ConnectionSpec.MODERN_TLS,
                        ConnectionSpec.COMPATIBLE_TLS,
                        ConnectionSpec.CLEARTEXT))
                .connectTimeout(connectTimeOut, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .connectionPool(CONNECTION_POOL)
                .retryOnConnectionFailure(true)
                .followRedirects(true)
                .followSslRedirects(true)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                })
                .cookieJar(new CookieJar() {
                    private List<Cookie> cookies;

                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        this.cookies = cookies;
                    }

                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        if (cookies != null) {
                            return cookies;
                        }
                        return Collections.emptyList();

                    }
                })
                .build();
    }

適配器模式

java.io jdbc-odbc bridge ASM transformer

橋接模式

抽象和具體的發展單獨分支,抽象中持有一個具體的引用 使用橋接模式: 分離抽象與具體實現,讓他們能夠獨自發展 Gift -> WarmGift ColdGift WildGiftGiftImpl -> Flower Ring Car

命令模式

結合責任鏈模式實現屢次undo 結合組合模式實現宏命令 結合記憶模式實現transaction回滾

原型模式

Object.clone()

備忘錄模式

記錄狀態,記錄快照,瞬時狀態,存盤 Tank的GameModel的load/save方法(實現序列化接口) 便於回滾

模板方法

假設咱們要實現一個遊戲,這個遊戲有初始化,啓動,結束三個方法,咱們能夠定義一個遊戲的模板:

public abstract class Game {
    protected abstract void init();

    protected abstract void start();

    protected abstract void end();

    protected final void play() {
        init();
        start();
        end();
    }
}

每種相似這樣結構(有初始化,啓動,結束)的遊戲均可以繼承這個類來實現這三個方法,好比BasketballGame

public class BasketballGame extends Game {
    @Override
    protected void init() {
        System.out.println("basketball init");
    }

    @Override
    protected void start() {

        System.out.println("basketball start");
    }

    @Override
    protected void end() {

        System.out.println("basketball end");
    }
}

FootballGame

public class FootballGame extends Game {
    @Override
    protected void init() {
        System.out.println("football init");
    }

    @Override
    protected void start() {

        System.out.println("football start");
    }

    @Override
    protected void end() {

        System.out.println("football end");
    }
}

主方法在調用的時候,直接:

Game basketballGame = new BasketballGame();
basketballGame.play();
Game footballGame = new FootballGame();
footballGame.play();

便可

模板方法的UML圖

UML

實際應用場景

  • 鉤子函數

  • RestTemplate /JDBCTemplate

State模式

狀態遷移

解釋器模式

參考資料

相關文章
相關標籤/搜索