您可於此 design_pattern repo 下載 Design Pattern 系列程式碼。

需求

我們收到了一個需求:實作一個檔案系統,其目錄可以包含檔案或子目錄,並且需要提供統一的操作介面來列出目錄內容。此系統應支援以下功能:

  • 支援樹狀結構的表示。
  • 可操作單一檔案和目錄。
  • 新增檔案或目錄時無需大幅修改現有程式碼。

物件導向分析 (OOA)

理解需求後,讓我們來快速實作物件導向分析吧!

composite_pattern_uml_1

察覺 Forces

在未使用設計模式的情況下,上述需求可能會遇到以下問題:

  1. 高耦合性 (Tight Coupling)
    • 單一檔案和目錄集合的操作邏輯分散在多個類別中,導致系統維護困難。
  2. 重複代碼 (Code Duplication)

    • 每次操作目錄內容時,需分別處理檔案與子目錄,導致相似邏輯多處重複。
  3. 難以擴展 (Difficulty in Extending)

    • 新增檔案或目錄類型時,需大幅修改程式碼,影響系統穩定性。
  4. 靈活性差 (Lack of Flexibility)
    • 操作層需清楚區分單一檔案與目錄集合,增加程式碼複雜度。

套用 Composite Pattern ( Solution ) 得到新的 Context ( Resulting Context )

做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Composite Pattern 解決這個問題。

先來看一下 Composite Pattern 的 UML:

composite_pattern_uml_2

  • Component (組件介面):定義統一的操作介面,單一檔案與目錄都需實作此介面。
  • Leaf (葉子節點):表示檔案,不能再包含子節點。
  • Composite (組合節點):表示目錄,可包含子節點(檔案或子目錄),並實作遞迴操作的邏輯。

將 Composite Pattern 套用到我們的應用吧

composite_pattern_uml_3

物件導向程式設計 (OOP)

[Component: FileSystemComponent]

abstract class FileSystemComponent(val name: String) {
    open fun display(indent: String = "") {
        println("$indent$name")
    }

    open fun add(component: FileSystemComponent) {
        throw UnsupportedOperationException("Cannot add component to a leaf.")
    }

    open fun remove(component: FileSystemComponent) {
        throw UnsupportedOperationException("Cannot remove component from a leaf.")
    }
}

[Leaf: File]

class File(name: String) : FileSystemComponent(name) {
    override fun display(indent: String) {
        println("$indent- File: $name")
    }
}

[Composite: Directory]

class Directory(name: String) : FileSystemComponent(name) {
    private val children = mutableListOf<FileSystemComponent>()

    override fun add(component: FileSystemComponent) {
        children.add(component)
    }

    override fun remove(component: FileSystemComponent) {
        children.remove(component)
    }

    override fun display(indent: String) {
        println("$indent+ Directory: $name")
        children.forEach { it.display("$indent  ") }
    }
}

[Client]

fun main() {
    // Build Directories and files
    val root = Directory("Root")
    val folder1 = Directory("Folder1")
    val folder2 = Directory("Folder2")

    val file1 = File("File1.txt")
    val file2 = File("File2.txt")
    val file3 = File("File3.txt")

    // Add files & directories into directories
    root.add(folder1)
    root.add(file1)

    folder1.add(folder2)
    folder1.add(file2)

    folder2.add(file3)

    // display file structure
    root.display()
}

[Output]

+ Directory: Root
  + Directory: Folder1
    + Directory: Folder2
      - File: File3.txt
    - File: File2.txt
  - File: File1.txt

結論

通過套用 Composite Pattern,我們成功實現了單一檔案與目錄集合的統一操作。有效降低了系統的耦合性,並且提供了高效的擴展性,當需要新增新的檔案類型或目錄結構時,無需大幅修改現有程式碼。透過此模式,開發者能夠以簡潔且一致的方式處理樹狀結構的邏輯,提升了程式的靈活性與可維護性。

Leave a comment