saeki’s blog

The limits of my code mean the limits of my world.

GoFデザインパターン ~Compositeパターン~

Compositeパターンとは

ファイルシステムのような、中身(ファイル)とその入れ子(ディレクトリ)を再帰的に処理するためのパターン。容器と中身を同一視し、再帰的な構造を作る

Compositeパターンにおける役割

name description
Leaf 容器の中身を表すもの。ファイルシステムの例ではファイルがこの役割。
Composite 容器を表すもの。ファイルシステムの例ではディレクトリ。
Component LeafとCompositeを同一視するためのもの。今回書いたサンプルコードにおけるEntryインターフェースがこれ。
Client Compositeパターンの利用者。

コード例

Entryインターフェース

package main

type Entry interface {
    GetName() string
    GetSize() int64
    Add(Entry) error
}

ファイルとディレクトリを同一のものとして扱えるようにするためのインターフェース。ファイルクラスとディレクトリクラスがそれぞれこのインターフェースを実装するが、この方法のデメリットは、ファイルクラスでもAdd(Entry) errorのようなディレクトリクラスのためのメソッドを実装しなくてはいけなくなる(ファイルクラスにおけるAdd(Entry) errorはただエラーを返すためだけのメソッド)。

File

package main

import "github.com/pkg/errors"

type file struct {
    name string
    size int64
}

func NewFile(name string, size int64) Entry {
    return &file{
        name: name,
        size: size,
    }
}

func (f *file) GetName() string {
    return f.name
}

func (f *file) GetSize() int64 {
    return f.size
}

func (f *file) Add(Entry) error {
    return errors.New("This is not directory")
}

ファイルを表す構造体。Add() はディレクトリにおいてのみ必要だが、Entryインターフェースを満たすためにここでも実装する

Directory

package main

type directory struct {
    name    string
    size    int64
    entries []Entry
}

func NewDirectory(name string, size int64) Entry {
    return &directory{
        name: name,
        size: size,
    }
}

func (d *directory) GetName() string {
    return d.name
}

func (d *directory) GetSize() int64 {
    return d.size
}

func (d *directory) Add(entry Entry) error {
    d.entries = append(d.entries, entry)
    return nil
}

ディレクトリを表す構造体。

Client

package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    os.Exit(run())
}

func run() int {
    flag.Parse()

    for _, v := range walk(getPath()) {
        fmt.Printf("%#v", v)
    }
    return 0
}

func walk(path string) []Entry {
    files, err := ioutil.ReadDir(path)
    if err != nil {
        panic(err)
    }

    var entries []Entry
    for _, file := range files {
        var entry Entry
        if file.IsDir() {
            entry := NewDirectory(file.Name(), file.Size())
            entries = append(entries, walk(filepath.Join(path, file.Name()))...)
            for _, entryInDir := range entries {
                err := entry.Add(entryInDir)
                if err != nil {
                    panic("panic!")
                }
            }
        } else {
            entry = NewFile(file.Name(), file.Size())
        }

        entries = append(entries, entry)
    }
    return entries
}

func getPath() string {
    var path string
    if args := flag.Args(); len(args) > 0 {
        path = args[0]
        if !strings.HasSuffix(path, "/") {
            path = path + "/"
        }
    } else {
        path = "./"
    }
    return path
}

Compositeを利用するClientの実装。walk() はディレクトリを探索するための関数で、ディレクトリに対しては walk() の中で walk()を再帰的に呼び出す。

github.com