技能篇:實際開發常用設計模式

潛行前行發表於2021-11-02

建立型

單例模式

單例物件能節約系統資源,一個物件的建立和消亡的開銷可能很小。但是日常的服務介面,就算是一般小公司也有十幾萬的QPS吧。每一次的功能運轉都建立新的物件來響應請求,十幾萬物件的建立和銷燬,想想就是一筆大開銷,所以 spring 管理構造的 bean 物件一般都是單例。而且單例模式可以更好的解決併發的問題,方便實現資料的同步性

  • 優點
    • 在記憶體中只有一個物件,節省記憶體空間
    • 避免頻繁的建立銷燬物件,可以提高效能
    • 避免對共享資源的多重佔用,簡化訪問
    • 為整個系統提供一個全域性訪問點
  • 缺點
    • 不適用於變化頻繁的物件
//餓漢式
private static Singleton singleton = new Singleton();
//懶漢式
private static Singleton singleton;
public static Singleton getSingleton(){
    if (singleton == null) {
        singleton = new Singleton(); //被動建立,在真正需要使用時才去建立
    }
    return singleton;
}
//雙重判斷加鎖機制
private volatile static Singleton instance;
//程式執行時建立一個靜態只讀的程式輔助物件
public static Singleton GetInstance() {
    //先判斷是否存在,不存在再加鎖處理
    if (instance == null){
        synchronized (Singleton.class){
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}
//靜態初始化
private static readonly Singleton instance= new Singleton();
public static Singleton GetInstance(){
    return instance;
}

工廠模式

使用者不關心物件的例項化過程,只關心物件的獲取。工廠模式使得產品的例項化過程和消費者解耦

  • 優點
    • 一個呼叫者想建立一個物件,只需通過其名稱或其他唯一鍵值在工廠獲取
    • 擴充套件性高,如果想增加生產一種型別物件,只要擴充套件工廠類就可以
  • 缺點
    • 工廠類不太理想,因為每增加一產品,都要在工廠類中增加相應的生產判斷邏輯,這是違背開閉原則的
public interface Sender{  public void send();  }  
public class MailSender implements Sender {  
    @Override  
    public void send() {  
        System.out.println("this is mailsender!");  
    }  
}
public class SmsSender implements Sender {  
    @Override  
    public void send() {  
        System.out.println("this is sms sender!");  
    }  
}
public class SendFactory {  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            return null;  
        }  
    } 
    //若還有其他產品 則在工廠里加對應的 produce 方法
}  

建造者模式

主要解決在軟體系統中一個複雜物件的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定

  • 優點
    • 擴充套件性好,物件每一個屬性的構建相互獨立,有利於解耦。
    • 建造者可以對建立過程逐步細化,而不對其它模組產生任何影響,便於控制細節風險
  • 缺點
    • 如果物件建造者發生變化,則建造者也要同步修改,後期維護成本較大
    • 一種建造者對應一種型別建造,一個建造者基本很難建造多種型別物件
@Data
class Product {
    private String name;
    private String price;
    // Product 的建造者  Builder
    public static class Builder{
        public static Builder builder(){
            Builder builder = Builder();
        }
        private Product product = new Product();
        public Builder name(String name){ product.name = name; return this;}
        public Builder price(String price){ product.price = price; return this; }
        //返回產品物件
        public Product build() { return product; }
    }
}

結構型

介面卡模式

連通上下游功能。一般是現有的功能和產品要求的介面不相容,需要做轉換適配。平時見到的 PO,BO,VO,DTO 模型物件之間的相互轉換也是一種適配的過程

  • 優點:提高了類的複用,靈活性好
  • 缺點:過多地使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是 A 介面,其實內部被適配成了 B 介面的實現
//類的介面卡模式
public class Source {  
    public void sayHello() {  
        System.out.println("lwl:hello!");  
    }  
}  
public interface Targetable {  
    /* Source方法相同 */  
    public void sayHello();  
    /* 新增的方法 */  
    public void hi();  
}
// Source 用 Adapter 適配成 Targetable
public class Adapter extends Source implements Targetable {  
    @Override  
    public void hi() {  
        System.out.println("csc:hi!");  
    }  
}
//物件的介面卡模式
public class Source {  
    public void sayHello() {  
        System.out.println("lwl:hello!");  
    }  
}  
public interface Targetable {  
    /* Source方法相同 */  
    public void sayHello();  
    /* 新增的方法 */  
    public void hi();  
}
//  Source的物件適配成 Targetable
public class Adapter implements Targetable {
    private Source source;  
    public Adapter(Source source){ this.source = source; }
    public void sayHello(){ source.sayHello(); }
    @Override  
    public void hi() {  
        System.out.println("csc:hi!");  
    }  
}

裝飾器模式

增強物件功能,動態的為一個物件增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪)

public interface Show(){ public void acting(); }
public class Artist implements Show {  
    public void acting(){
        System.out.println("lwl 在唱歌!");  
    }
}  
public class DecoratorArtist implements Show{
    Artist artist;
    DecoratorArt(Artist artist){
        this.artist = artist;
    }
    public void acting(){
        System.out.println("lwl 在彈鋼琴!"); //增強的功能
        this.artist.acting(); 
        System.out.println("表演完畢!"); //增強的功能
    }
}

代理模式

代理類是客戶類和委託類的中介,可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則

  • 和裝飾器模式的區別:代理模式著重於增強類功能,且對面遮蔽原物件的建立過程;裝飾器模式增強的是物件,且裝飾器模式有一個動態傳遞原物件的步驟
  • 和物件的介面卡模式優點像:不過代理模式著重的是對原功能增強,介面卡模式著重的是對新功能的相容
  • 優點-1、職責清晰。 2、高擴充套件性
public class Artist implements Show {  
    public void acting(){
        System.out.println("lwl 在唱歌!");  
    }
}
public class ProxyArtist implements Show{
    Artist artist;
    ProxyArtist(){ 
        this.artist = new Artist();//遮蔽了 artist 物件的建立
    }
    public void acting(){
        System.out.println("lwl 在彈鋼琴!"); //增強的功能
        this.artist.acting(); 
        System.out.println("表演完畢!"); //增強的功能
    }
}
public class Demo {
    public static void main(String[] arg){
        Show show = new ProxyArtist();
        show.acting();
    }
}

橋接模式

橋接模式側重於功能的抽象,從而基於這些抽象介面構建上層功能。一般的java 專案都會將介面和實現分離原因,就是基於橋接模式。提高了系統的擴充套件能力,當引用的底層邏輯有不同的設計實現時,繼承抽象介面重新實現一套即可,舊的不變,符合程式碼設計的開閉原則

  • jdbc 的驅動:常用的JDBC 和 DriverManager,JDBC進行連線資料庫的時候,在各個資料庫之間進行切換,基本不需要動太多的程式碼,原因就是JDBC提供統一介面,每個資料庫提供各自的實現,用一個叫做資料庫驅動的程式來橋接
  • Unix 的檔案系統:VFS(virtual File System)使得 Unix 系統可以在不同物理介質上的不同檔案系統進行讀寫
public interface FileSystem(){ 
  public void open(int file); 
  public String loading(int file); 
  public void store(int file, String data); 
}
//網路上的檔案系統
public class NetFileSystem implements FileSystem {  
  public void open(int file){ System.out.println(" netfile opening...."); }
  public String loading(int file) {System.out.println(" net loading ...."); }  
  public void store(int file, String data) {System.out.println(" send to network ...."); }  
}
//磁碟檔案系統
public class DiskFileSystem implements FileSystem{
  public void open(int file){ System.out.println(" disk opening...."); }
  public String loading(int file) {System.out.println(" disk loading ...."); } 
  public void store(int file, String data) {System.out.println(" write back disk ...."); }   
}
public class Linux {
    FileSystem fileSystem; 
    //底層功能提供介面,橋接模式:功能和具體實現分離
    //可以橋接 NetFileSystem 或者 DiskFileSystem 作為檔案系統
    public void set(FileSystem fileSystem){ this.fileSystem = fileSystem; }
    //上層功能讀資料
    public String read(int file){
        fileSystem.open(file);
        ... // Linux 自己的系統功能
        fileSystem.loading(file);
        ... 
    }
    //上層功能寫資料
    public String write(int file, String data){
        fileSystem.open(file);
        ....
        fileSystem.store(file,data);
    }
}
  • 可配合介面卡模式使用

享元模式

多個物件共享某些屬性。在建立有大量物件時,可能會造成記憶體溢位,把其中共同的部分抽象出來,如果有相同的請求,直接返回在記憶體中同一份屬性,避免重新建立

  • 如 jdbc 連線池的連線物件,它們會共享池物件的 url、driverClassName、username、password 等屬性
public class ConnectionPool {  
    private Vector<Connection> pool;  
    /*公有屬性*/  
    private String url = "jdbc:mysql://localhost:3306/test";  
    private String username = "root";  
    private String password = "root";  
    private String driverClassName = "com.mysql.jdbc.Driver";  
    public ConnectionPool() {  
        pool = new Vector<Connection>(poolSize);  
        for (int i = 0; i < poolSize; i++) {  
            Class.forName(driverClassName);  
                // 每一個 conn 共享了 driverClassName ,url, username, password 等屬性
                Connection conn = DriverManager.getConnection(url, username, password);  
                pool.add(conn);
            }
    }
    ....
}

外觀模式

  • 用多個不同的物件實現一組更復雜的功能。使得類與類之間的關係解耦。如 spring 將使用各個簡單的 component、dao 實現複雜的service,就是一種外觀模式
  • 功能的組合,組合優於繼承
public class DAO { 
    public void queryData(){
        System.out.print(" query data ")
    }
}
public class Deal {  
    public void dealData(){
        System.out.print(" dealing data ")
    }
}
public class Sender {  
    public void send(){
        System.out.print(" send data ")
    }
}
public class Service(){
    private DAO dao;  
    private Deal deal;  
    private Sender sender;
    //封裝 DAO,Deal,Sender 的功能,統一對外提供服務
    public void reponse(){
        dao.queryData();
        deal.dealData();
        sender.send();
    }
}

行為型

策略模式

策略模式側重於不同的場景使用不同的策略。在有多種演算法相似的情況下,解決 if...else 所帶來的複雜和難以維護

  • 和橋接模式的區別:而橋接模式是結構型模式,側重於分離底層功能的抽象和實現,底層只有一種實現也可以
// 上學的策略
abstract class Strategy{
    private static final Map<Integer,Strategy> strategyMap = new ConcurrentHashMap<>();
    public Strategy(){
        strategyMap.put(getType(), this);
    }
    public static Strategy routing(int type){
        return strategyMap.get(type);
    }
    abstract int getType();
    abstract void method(); //留待子類實現差異
}
//跑路去學校
class RunningStrategy extends Strategy{
    int getType() { return 0; }
    void method() { System.out.println(" Run to school "); }
}
//公交去學校
class BusStrategy extends Strategy{
    int getType() { return 1; }
    void method() { System.out.println(" Go to school by bus "); }
}
//飛去學校
class FlyStrategy extends Strategy{
    int getType() { return 2; }
    void method() { System.out.println(" Fly to school "); }
}
class Context{
    //使用不同的策略
    void method(int strategy){
        Strategy.routing(strategy).method();
    }
}

模板方法

和享元模式有一定的相似處,享元模式側重於屬性的共享,而且是結構上的引用,不一定需要繼承;而模板方法是共享相同行為,一定有繼承行為

  • 區別於策略模式是它有能抽象出來的共同行為,每一個子類再實現有差異細節
abstract class AbstractHandler{
    // handle是抽象出來的共同邏輯
    void handle(String data){
        System.out.println("通用邏輯1...");
        stepOne(data);
        System.out.println("通用邏輯2...");
        stepTwo(data);
        System.out.println("通用邏輯3...");
    }
    abstract void stepOne(String data); //留待子類實現差異
    abstract void stepTwo(String data); //留待子類實現差異
}
class HelloHandler extends AbstractHandler{
    @Override
    void stepOne(String data) {
        System.out.println("hello: "+data);
    }
    @Override
    void stepTwo(String data) {
        System.out.println("hi: "+data);
    }
}

迭代子模式

迴圈處理多個相同物件,用來遍歷集合或者陣列

//迭代的抽象介面
public interface Iterator {  
    //前移  
    public Object previous();  
    //後移  
    public Object next();  
    public boolean hasNext();   
} 
// 陣列的迭代類
public class ArrayIterator implements Iterator {  
    private Object[] datas; 
    private int cur = 0;  
    public ArrayIterator(Object[] datas){  
        this.datas = datas;  
    }  
    public String previous() {  
        if(cur > 0){ cur--;}  
        return datas[cur];  
    }  
    public Object next() {  
        if(cur < datas.length-1){ cur++;}  
        return datas[cur];  
    }  
    public boolean hasNext() {  
        return pos < datas.length-1 ? true :false;
    }  
}  

責任鏈模式

負責處理上游的傳遞下來的物件,並傳遞給下一個處理者

  • 和迭代子模式的區別,責任鏈模式是多個hander處理同一個data,且 hander 處理具有順序性,不用全部 hander 處理,可在某一 hander 中斷,也可繼續傳遞
abstract class Handler<T,R> {
    private Handler<R,?> next;
    abstract R handle(T data);
    public void setNext(Handler<R, ?> next){ this.next = next; }
    public void loopHandle(T data){
        R result = this.handle(data);
        if(next!=null && result!=null ) { next.loopHandle(result); }
    }
}
//負責問候
class HelloHandler extends Handler<String, Integer> {
    Integer handle(String data) {
        System.out.println(data + " hello! ");
        return 10;
    }
}
//負責計數
class CountHandler extends Handler<Integer, Double> {
    Double handle(Integer data) {
        System.out.println(" it is " + data);
        return 2.0;
    }
}
public class demo{
    public static void main(String[] args){
        HelloHandler hello = new HelloHandler();
        CountHandler count = new CountHandler();
        hello.setNext(count);
        hello.loopHandle("lwl");
    }
}

觀察者模式

事件通知: 定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知

  • 優點:觀察者和被觀察者是抽象耦合的
  • 缺點
    • 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間
    • 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰
//觀察者
public abstract class Observer<T> {
    public abstract void update(T data);
}
// 被觀察物件
public class Subject<T> {
    private List<Observer<T>> observers = new ArrayList<>();
    private T state;
    public void deal() { 
        ....// 邏輯處理
        //如果修改了 state,通知觀察者
        if(...) notifyAllObservers();
    }
    //增加一個觀察觀察
    public void observe(Observer<T> observer) {
        observers.add(observer);
    }
    public void notifyAllObservers() {
        for (Observer<T> observer : observers) {
            observer.update(state);
        }
    }
}

狀態機模式

不同的狀態不同的響應,實現狀態之間的轉移

  • 和策略模式的區別
    • 狀態機模式是策略模式的孿生兄弟。策略模式可以讓使用者指定更換的策略演算法,而狀態機模式是狀態在滿足一定條件下的自動更換,使用者無法指定狀態,最多隻能設定初始狀態
    • 狀態機模式重點在各狀態之間的切換,從而做不同的事情;而策略模式更側重於根據具體情況選擇策略,並不涉及切換
interface State<T> {
    //當前狀態進行處理資料,並返回下一個狀態
    abstract State<T> action(T data);
}
@Data
class Context<T>{
    private State<T> state;
    public void invoke(T data){
        state != null ? state = state.action(data) : System.out.println(" nothing " + data);
    }
}
// HelloState -> HiState
class HelloState implements State<String>{
    public State<String> action(String data) {
        System.out.println("hello!" + data);
        return new HiState();
    }
}
// HiState -> FineState
class HiState implements State<String>{
    public State<String> action(String data) {
        System.out.println("how are you ?" + data);
        return new FineState();
    }
}
//最後的狀態
class FineState implements State<String>{
    public State<String> action(String data) {
        System.out.println("I am  fine!");
        return null;
    }
}
public class demo{
    public static void main(String[] args){
        Context<String> context = new Context<>();
        context.setState(new HelloState());
        context.invoke("lwl");
        context.invoke("lwl");
        context.invoke("lwl");
        context.invoke("lwl");
    }
}

備忘錄

記錄上一次的狀態,方便回滾。很多時候我們是需要記錄當前的狀態,這樣做的目的就是為了允許使用者取消不確定或者錯誤的操作,恢復到原先的狀態

  • 缺點:消耗資源。如果類的成員變數過多,勢必會佔用比較大的資源,而且每一次儲存都會消耗一定的記憶體
@Data
public class Memento { 
    private String state;
    public Memento(String state){ this.state = state; }
}
@Data
public class Storage { 
    private String value;  
    public void storeMemento(){
        return new Memento(value);  
    }
    public void restoreMemento(Memento memento){  
        this.value = memento.getValue();  
    }
    public void action(){ System.out.println(" Storage類邏輯執行 ");}
      
}
public class MementoPatternDemo { 
    public static void main(String[] args) {
        Storage storage = new Storage();
        storage.setValue(1);
        storage.storeMemento();//備忘,一下
        storage.action();//....邏輯執行
        restoreMemento(Memento memento);//使用備忘錄的恢復狀態
    }
}

相關文章