LoginSignup
5
5

More than 5 years have passed since last update.

JavaエンジニアがRubyでデザインパターンを学ぶ - Composite Pattern

Posted at

概要

「Rubyによるデザインパターン」を読んでデザインパターンを勉強中。
Javaをやっていた人間としての目線で情報を整理してみます。

他のデザインパターン

Template Method Pattern
Strategy Pattern
Observer Pattern

Composite Pattern

  • 全体と部分を同一視する、というパターン
  • 入れ物(Composite)と中身(Leaf または Composite)が同じインターフェースを持つことでツリーのような再帰的な構造を簡潔に表現できる

png-2.png

実装例

HTML文書を表すクラスを考えてみました。

<html>
  <head>
    <title>hoge</title>
  </head>
</html>

HTMLは <html><head> などのタグの中にさらに別のタグ、もしくはテキスト(この場合 <title> タグの中の "hoge")が含まれる、というような再帰的な構造になっているので、タグを Composite 、テキストを Leaf クラスとしています。

Java での実装

Component

HtmlElement.java
public abstract class HtmlElement {
  final String INDENT = "  ";

  // depth 分の空白文字を生成します
  protected String indent(int depth) {
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<depth; i++) {
      sb.append(INDENT);
    }
    return sb.toString();
  }

  public abstract void print(int depth);
}

print を抽象メソッドとして定義し、共通のインターフェースとしています。

Composite

Tag.java
import java.util.List;
import java.util.ArrayList;
import java.io.PrintStream;

public class Tag extends HtmlElement {
  private String name;
  private List<HtmlElement> children; // 子要素は HtmlElement 型

  public Tag(String name) {
    this.name = name;
    this.children = new ArrayList<HtmlElement>();
  }

  public Tag addChild(HtmlElement child) {
    children.add(child);
    return this;
  }

  // 開始タグ、終了タグで囲んでその間に子要素を出力します
  // このとき子要素が Tag なのか Text なのか を意識しなくて済む、というところがポイント
  @Override
  public void print(int depth) {
    System.out.printf("%s<%s>%n", indent(depth), name);
    for (HtmlElement child : children) {
      child.print(depth + 1);
    }
    System.out.printf("%s</%s>%n", indent(depth), name);
  }
}

Leaf

Text.java
import java.io.PrintStream;

public class Text extends HtmlElement {
  private String text;
  public Text(String text) {
    this.text = text;
  }

  @Override
  public void print(int depth) {
    System.out.println(indent(depth) + text);
  }
}

呼び出し

Main.java
public class Main {
  public static void main(String[] args) {
    Tag root = new Tag("html");

    Tag head = new Tag("head");
    Tag title = new Tag("title").addChild(new Text("Sample code for Composite Pattern"));
    head.addChild(title);
    root.addChild(head);

    Tag body = new Tag("body");
    Tag h1 = new Tag("h1").addChild(new Text("What is Composite Pattern?"));
    Tag p = new Tag("p")
    .addChild(new Text("In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. "));

    body.addChild(h1);
    body.addChild(p);
    root.addChild(body);

    root.print(0);
  }
}

実行結果

<html>
  <head>
    <title>
      Sample code for Composite Pattern
    </title>
  </head>
  <body>
    <h1>
      What is Composite Pattern?
    </h1>
    <p>
      In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object.
    </p>
  </body>
</html>

Ruby での実装

Component

class HtmlElement
  INDENT = "  ";

  def indent(depth)
    INDENT * depth
  end
end

Java で抽象メソッドとしていたメソッドを Component に置くべきか?は悩みどころ。デフォルトの挙動が無ければ不要かもしれません。

Composite

class Tag < HtmlElement
  def initialize(name)
    @name = name
    @children = []
  end

  def add_child(child)
    @children << child
    self
  end

  def print(depth=0)
    puts indent(depth) + "<#{@name}>"
    @children.each do |child|
      child.print(depth + 1)
    end
    puts indent(depth) + "</#{@name}>"
  end
end

Leaf

class Text < HtmlElement
  def initialize(text)
    @text = text
  end

  def print(depth=0)
    puts indent(depth) + @text
  end
end

呼び出し

root = Tag.new("html")

head = Tag.new("head")
title = Tag.new("title").add_child(Text.new("Sample code for Composite Pattern"))
head.add_child(title)
root.add_child(head)

body = Tag.new("body")
h1 = Tag.new("h1").add_child(Text.new("What is Composite Pattern?"))
p = Tag.new("p").add_child(Text.new("In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. "))

body.add_child(h1)
body.add_child(p)
root.add_child(body)

root.print

実行結果

<html>
  <head>
    <title>
      Sample code for Composite Pattern
    </title>
  </head>
  <body>
    <h1>
      What is Composite Pattern?
    </h1>
    <p>
      In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object.
    </p>
  </body>
</html>

参考

Olsen, R. 2007. Design Patterns in Ruby

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5