設計模式---組合模式

来源:https://www.cnblogs.com/buzuweiqi/archive/2022/09/27/16729556.html
-Advertisement-
Play Games

簡述 類型:結構型 目的:將對象集合組合成樹形結構,使客戶端可以以一致的方式處理單個對象(葉子節點)和組合對象(根節點) 話不多說,上優化案例。 優化案例 最初版v0 不使用組合模式。 現有一個文件和目錄的管理模塊。如樣例。 public class File { // 文件類 private St ...


簡述

  • 類型:結構型
  • 目的:將對象集合組合成樹形結構,使客戶端可以以一致的方式處理單個對象(葉子節點)組合對象(根節點)

話不多說,上優化案例。

優化案例

最初版v0

不使用組合模式。
現有一個文件和目錄的管理模塊。如樣例。

public class File { // 文件類
    private String path;
    private Directory parent;
    public File(Directory dir, String path) {
        if (dir == null)
            throw new RuntimeException("輸入的dir不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.parent = dir;
        this.path = dir.getPath() + path;
        dir.add(this);
    }
    public String getPath() {
        return this.path;
    }
}
public class Directory { // 目錄類
    private String path;
    private List<Directory> dirs = new ArrayList<>();
    private List<File> files = new ArrayList<>();
    public Directory(String path) {
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = path;
    }
    public Directory(Directory parent, String path) {
        if (parent == null)
            throw new RuntimeException("輸入的parent不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = parent.getPath() + path;
        parent.add(this);
    }
    public boolean add(File target) {
        for (File file : files)
            // 不能創建同名文件
            if (target.getPath().equals(file.getPath())) return false;
        files.add(target);
        return true;
    }
    public boolean add(Directory target) {
        for (Directory dir : dirs)
            // 不能創建同名目錄
            if (target.getPath().equals(dir.getPath())) return false;
        dirs.add(target);
        return true;
    }
    public boolean remove(Directory target) {
        for (Directory dir : dirs)
            if (target.getPath().equals(dir.getPath())) {
                dirs.remove(dir);
                return true;
            }
        return false;
    }
    public boolean remove(File target) {
        for (File file : files)
            if (target.getPath().equals(file.getPath())) {
                files.remove(file);
                return true;
            }
        return false;
    }
    public String getPath() {
        return this.path;
    }
    public List<Directory> getDirs() {
        return this.dirs;
    }
    public List<File> getFiles() {
        return this.files;
    }
}

不使用組合模式,我們來看看客戶端的使用。

public class Client { // 客戶端
    public static void main(String[] args) {
        // 創建各級目錄
        Directory root = new Directory("/root");
        Directory home = new Directory(root, "/home");
        Directory user1 = new Directory(home, "/user1");
        Directory text = new Directory(user1, "/text");
        Directory image = new Directory(user1, "/image");
        Directory png = new Directory(image, "/png");
        Directory gif = new Directory(image, "/gif");
        // 創建文件
        File f1 = new File(text, "/f1.txt");
        File f2 = new File(text, "/f2.txt");
        File f3 = new File(png, "/f3.png");
        File f4 = new File(gif, "/f4.gif");
        File f5 = new File(png, "/f5.png");
        // 輸出root下的文件或者目錄路徑
        print(root);
    }
    // 前序遍歷目錄下路徑
    public static void print(Directory root) {
        System.out.println(root.getPath());
        List<Directory> dirs = root.getDirs();
        List<File> files = root.getFiles();
        for (int i = 0; i < dirs.size(); i ++) {
            print(dirs.get(i));
        }
        for (int i = 0; i < files.size(); i ++) {
            System.out.println(files.get(i).getPath());
        }
    }
}

可以看到print方法的實現比較複雜,因為FileDirectory是完全不同類型,所以只能對其分別處理。

如何讓客戶端對於FileDirectory採用一致的處理方式?用組合模式啊!!!

修改版v1(透明組合模式)

public interface Node { // 從File和Directory中抽象出Node類
    boolean add(Node node);
    boolean remove(Node node);
    List<Node> getChildren();
    String getPath();
}
public class File implements Node {
    private String path;
    private Node parent;
    public File(Node parent, String path) {
        if (parent == null)
            throw new RuntimeException("輸入的dir不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.parent = parent;
        this.path = parent.getPath() + path;
        parent.add(this);
    }
    public boolean add(Node node) { // 因為不是容器,所以重寫這個方法無意義
        throw new RuntimeException("不支持此方法!");
    }
    public boolean remove(Node node) { // 同上
        throw new RuntimeException("不支持此方法!");
    }
    public List<Node> getChildren() { // 同上
        throw new RuntimeException("不支持此方法!");
    }
    public String getPath() {
        return this.path;
    }
}
public class Directory implements Node {
    private String path;
    private List<Node> children = new ArrayList<>();
    public Directory(String path) {
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = path;
    }
    public Directory(Node parent, String path) {
        if (parent == null)
            throw new RuntimeException("輸入的parent不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = parent.getPath() + path;
        parent.add(this);
    }
    public boolean add(Node target) {
        for (Node node : children)
            // 不能創建同名文件
            if (target.getPath().equals(node.getPath())) return false;
        children.add(target);
        return true;
    }
    public boolean remove(Node target) {
        for (Node node : children)
            if (target.getPath().equals(node.getPath())) {
                children.remove(node);
                return true;
            }
        return false;
    }
    public String getPath() {
        return this.path;
    }
    public List<Node> getChildren() {
        return this.children;
    }
}

通過在FileDirectory的高層新增Node介面,面向介面編程加上FileDirectory形成的樹形結構使得客戶端可以很自然地一致處理FileDirectory。來看看客戶端代碼。

public class Client {
    public static void main(String[] args) {
        // 創建各級目錄
        Node root = new Directory("/root");
        Node home = new Directory(root, "/home");
        Node user1 = new Directory(home, "/user1");
        Node text = new Directory(user1, "/text");
        Node image = new Directory(user1, "/image");
        Node png = new Directory(image, "/png");
        Node gif = new Directory(image, "/gif");
        // 創建文件
        Node f1 = new File(text, "/f1.txt");
        Node f2 = new File(text, "/f2.txt");
        Node f3 = new File(png, "/f3.png");
        Node f4 = new File(gif, "/f4.gif");
        Node f5 = new File(png, "/f5.png");
        // 輸出root下的文件或者目錄路徑
        print(root);
    }
    public static void print(Node root) {
        System.out.println(root.getPath());
        List<Node> nodes = root.getChildren();
        for (int i = 0; i < nodes.size(); i ++) {
            Node node = nodes.get(i);
            if (node instanceof File) {
                System.out.println(node.getPath());
                continue;
            }
            print(node);
        }
    }
}

別高興的太早了,雖然我們實現了最初的需求,但是有一處的代碼不是很健康。在File中有三個方法實際上並沒有被實現,有些臃腫。

修改版v2(安全組合模式)

public interface Node { // 從File和Directory中抽象出Node類
    String getPath(); // 刪除累贅的方法
}
public class File implements Node {
    private String path;
    private Node parent;
    public File(Directory parent, String path) {
        if (parent == null)
            throw new RuntimeException("輸入的dir不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.parent = parent;
        this.path = parent.getPath() + path;
        parent.add(this);
    }
    public String getPath() {
        return this.path;
    }
}
public class Directory implements Node {
    private String path;
    private List<Node> children = new ArrayList<>();
    public Directory(String path) {
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = path;
    }
    public Directory(Directory parent, String path) {
        if (parent == null)
            throw new RuntimeException("輸入的parent不正確!");
        if (path == null || path == "")
            throw new RuntimeException("輸入的path不正確!");
        this.path = parent.getPath() + path;
        parent.add(this);
    }
    public boolean add(Node target) {
        for (Node node : children)
            // 不能創建同名文件
            if (target.getPath().equals(node.getPath())) return false;
        children.add(target);
        return true;
    }
    public boolean remove(Node target) {
        for (Node node : children)
            if (target.getPath().equals(node.getPath())) {
                children.remove(node);
                return true;
            }
        return false;
    }
    public String getPath() {
        return this.path;
    }
    public List<Node> getChildren() {
        return this.children;
    }
}

修改Node介面的抽象方法後代碼清爽了很多。客戶端調用需要稍微修改下。

public class Client {
    public static void main(String[] args) {
        // 創建各級目錄
        Directory root = new Directory("/root");
        Directory home = new Directory(root, "/home");
        Directory user1 = new Directory(home, "/user1");
        Directory text = new Directory(user1, "/text");
        Directory image = new Directory(user1, "/image");
        Directory png = new Directory(image, "/png");
        Directory gif = new Directory(image, "/gif");
        // 創建文件
        File f1 = new File(text, "/f1.txt");
        File f2 = new File(text, "/f2.txt");
        File f3 = new File(png, "/f3.png");
        File f4 = new File(gif, "/f4.gif");
        File f5 = new File(png, "/f5.png");
        // 輸出root下的文件或者目錄路徑
        print(root);
    }
    public static void print(Directory root) {
        System.out.println(root.getPath());
        List<Node> nodes = root.getChildren();
        for (int i = 0; i < nodes.size(); i ++) {
            Node node = nodes.get(i);
            if (nodes.get(i) instanceof File) {
                System.out.println(node.getPath());
                continue;
            }
            print((Directory) node); // 增加強轉
        }
    }
}

其實透明組合模式和安全組合模式看著用就好了,其實問題不大的。

總結

優點

  1. 讓客戶端可以一致地處理單一對象和組合對象。

缺點

  1. 局限性太強,只有可以構成樹形結構的對象集合才可以使用。

適用場景

  1. 只有在對象集合可以組合成樹形結構時才可以使用。

本文來自博客園,作者:buzuweiqi,轉載請註明原文鏈接:https://www.cnblogs.com/buzuweiqi/p/16729556.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 CMD 規範介紹 CMD: Common Module Definition, 通用模塊定義。與 AMD 規範類似,也是用於瀏覽器端,非同步載入模塊,一個文件就是一個模塊,當模塊使用時才會載入執行。其語法與 AMD 規範很類似。 1.1 定義模塊 定義模塊使用 define 函數: define( ...
  • uniapp webview h5 通信 window.postMessage 方式 父頁面 <template> <view> <!-- <web-view :webview-styles="webviewStyles" src="https://uniapp.dcloud.io/static/w ...
  • 模塊 HTML 網頁中,瀏覽器通過<script>標簽載入 JavaScript 腳本。 <!-- 頁面內嵌的腳本 --> <script type="application/javascript"> // module code </script> <!-- 外部腳本 --> <script ty ...
  • 命令模式(Command Pattern)是一種數據驅動的設計模式,它屬於行為型模式。請求以命令的形式包裹在對象中,並傳給調用對象。調用對象尋找可以處理該命令的合適的對象,並把該命令傳給相應的對象,該對象執行命令。 ...
  • 橋接模式是一種在日常開發中不是特別常用的設計模式,主要是因為上手難度較大,但是對於理解面向對象設計有非常大的幫助。 ...
  • 在項目編碼中經常會遇到一些新的需求試圖復用已有的功能邏輯進行實現的場景,但是已有的邏輯又不能完全滿足新需求的要求,所以就會出現各種生搬硬套的操作。本篇文檔就一起來聊一聊如何藉助Adapter實現高效復用已有邏輯、讓代碼復用起來更加的得體與優雅。 ...
  • 【1】前言 本篇幅是對 線程池底層原理詳解與源碼分析 的補充,預設你已經看完了上一篇對ThreadPoolExecutor類有了足夠的瞭解。 【2】ScheduledThreadPoolExecutor的介紹 1.ScheduledThreadPoolExecutor繼承自ThreadPoolExe ...
  • 概述 tomcat亂碼問題相信大家肯定都遇見過,本篇將詳細介紹有關Tomcat的各種亂碼問題原因和解決方法😊 原因 首先亂碼問題的原因通俗的講就是讀的編碼格式和寫的解碼格式不一致,比如最常見的兩種中文編碼UTF-8和GBK,UTF-8一個漢字占三個位元組,GBK一個漢字占兩個位元組,所以當編碼與解碼格 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...