Help us understand the problem. What is going on with this article?

デザインパターンをkotlinで書いてみた Builder編

More than 1 year has passed since last update.

オブジェクト指向で大切になるInterfaceの考え方やオブジェクトの再利用性を学ぶために「Java言語で学ぶデザインパターン入門」について学び、Javaとついでにkotlinで書いてみることにしました。
今回はBuilderについて書いてみます。

Builderとは

構造物を構築する際は、各段階を踏んで組み上げていくように、構造を持ったインスタンスを組み上げていくパターンです。
サンプルプログラムでは文章という構造物を扱い、
文章は以下の段階・構造を持っているとして、各段階を構成するメソッドを定めていきます。

  1. タイトルを一つ含む
  2. 文字列をいくつか含む
  3. 箇条書きの項目をいくつか含む

Builderクラス

文章を構成するためのメソッドを定めたクラスです。

Builder.java
abstract class Builder {
    public abstract void makeTitle(String title);
    public abstract void makeString(String str);
    public abstract void makeItems(String[] items);
    public abstract void close();
}
Builder.kt
abstract class Builder {
    abstract fun makeTitle(title: String)
    abstract fun makeString(str: String)
    abstract fun makeItems(items: Array<String>)
    abstract fun close()
}

Directorクラス

このクラスでは文章を構築するconstructメソッドを実装しています。
Builderクラスがコンストラクタで渡されているが、Builderは抽象クラスの為、実際には継承したサブクラスが入ります。
具体的なサブクラスの宣言がないので依存性がなくなり、
また、サブクラスの入れ替えが可能になることを交換可能性と呼ぶとのこと。

Director.java
class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    public void construct() {
        builder.makeTitle("Greeting");
        builder.makeString("朝から昼にかけて");
        builder.makeItems(new String[] {
                "おはようございます。",
                "こんにちは。"
        });
        builder.makeString("夜に");
        builder.makeItems(new String[] {
                "こんばんは。",
                "おやすみなさい。",
                "さようなら。"
        });
        builder.close();
    }
}
Director.kt
class Director(builder: Builder) {
    private var b = builder
    fun construct(){
        b.makeTitle("Greeting")
        b.makeString("朝から昼かけて")
        b.makeItems(arrayOf("おはようございます。", "こんにちは。"))
        b.makeString("夜に")
        b.makeItems(arrayOf("こんばんは。", "おやすみなさい。", "さようなら。"))
        b.close()
    }
}

TextBuilderクラス

プレーンテキストを使って文書を作成するクラスです。

kotlinでfor文を書いた際に、最初はfor (i: Int in 0..items.size - 1)と書いてしまったのですが、
for (i: Int in 0 until items.size)のようにuntilは末数を数えないのでArrayの操作に向いているとのことです。

参考:KotlinでAndroid開発! 基本構文をおさえよう

kotlinだとString Template(文字列補間)と呼ばれる便利な書き方ができ、
println("$a + $b = ${a + b}") // 2 + 3 = 5のように文字列を埋め込めるとのことです。

参考:JavaプログラマがKotlinで便利だと感じること

TextBuilder.java
class TextBuilder extends Builder{
    private StringBuffer buffer = new StringBuffer();
    public void makeTitle(String title) {
        buffer.append("================================================\n");
        buffer.append(String.format("[%s]\n", title));
        buffer.append("\n");
    }
    public void makeString(String str) {
        buffer.append(String.format("■%s\n", str));
        buffer.append("\n");
    }
    public void makeItems(String[] items) {
        for (int i = 0; i < items.length; i++) {
            buffer.append(String.format(" ・%s\n", items[i]));
        }
        buffer.append("\n");
    }
    public void close() {
        buffer.append("================================================\n");
    }
    public String getResult() {
        return buffer.toString();
    }
}
TextBuilder.kt
class TextBuilder: Builder() {
    private var buffer = StringBuffer()
    override fun makeTitle(title: String) {
        buffer.append("================================================\n")
        buffer.append("[$title]\n")
        buffer.append("\n")
    }
    override fun makeString(str: String) {
        buffer.append("■$str\n")
        buffer.append("\n")
    }
    override fun makeItems(items: Array<String>) {
        for (i: Int in 0 until items.size) buffer.append(" ・${items[i]}\n")
        buffer.append("\n")
    }
    override fun close() {
        buffer.append("================================================\n")
    }
    fun getResult() = buffer.toString()
}

HTMLBuilderクラス

HTMLファイルを使って文書を作成するクラスです。

writerメンバをFileクラスで初期化できずに困ったのですが、lateinitを使用して処理が進むにつれて変更されていく値を遅延させることができるとのことです。
また、lateinitは初期化が行われる前に外部からアクセスできる困るのでprivate推奨とのことです。

参考:Kotlinで初期化を遅延する

printWriterを取得するにはjava.io.File#printWriter()を使用すれば取得でき、ファイルが無ければ作成され、あれば上書きされます。
追記したい場合はappendText()を使用します。

参考:I/O(ファイル,ネットワーク等)

もし、ファイルの有無によって動作を変えたい場合はjava.io.File#createNewFile():Booleanを使用します。

参考:Kotlin – Create File – Examples

HTMLBuilder.java
import java.io.*;

class HTMLBuilder extends Builder{
    private String filename;
    private PrintWriter writer;
    public void makeTitle(String title) {
        filename = title + ".html";
        try {
            writer = new PrintWriter(filename);
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.println(String.format("<html><head><title>%s</title></head><body>", title));
        writer.println(String.format("<h1>%s</h1>", title));
    }
    public void makeString(String str) {
        writer.println(String.format("<p>%s</p>", str));
    }
    public void makeItems(String[] items) {
        writer.println("<ul>");
        for (int i = 0; i < items.length; i++) {
            writer.println(String.format("<li>%s</li>", items[i]));
        }
        writer.println("</ul>");
    }
    public void close() {
        writer.println("</body></html>");
        writer.close();
    }
    public String getResult() {
        return filename;
    }
}
HTMLBuilder.kt
import java.io.File

class HTMLBuilder: Builder() {
    private var filename = String()
    private lateinit var writer:File
    override fun makeTitle(title: String) {
        filename = "$title.html"
        writer = File(filename)
        writer.printWriter().use { it.println("<html><head><title>$filename</title></head><body>") }
        writer.appendText("<h1>$title</h1>\n")
    }
    override fun makeString(str: String) {
        writer.appendText("<p>$str</p>\n")
    }
    override fun makeItems(items: Array<String>) {
        writer.appendText("<ul>\n")
        for (i: Int in 0 until items.size) writer.appendText("<li>${items[i]}</li>\n")
        writer.appendText("</ul>\n")
    }
    override fun close() {
        writer.appendText("</body></html>\n")
    }
    fun getResult() = filename
}

Mainクラス

BuilderSample.java
public class BuilderSample {
    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }
        if (args[0].equals("plain")) {
            TextBuilder textbuilder = new TextBuilder();
            Director director = new Director(textbuilder);
            director.construct();
            String result = textbuilder.getResult();
            System.out.println(result);
        } else if (args[0].equals("html")) {
            HTMLBuilder htmlbuilder = new HTMLBuilder();
            Director director = new Director(htmlbuilder);
            director.construct();
            String filename = htmlbuilder.getResult();
            System.out.println(String.format("%sが作成されました。", filename));
        } else {
            usage();
            System.exit(0);
        }
    }
    public static void usage() {
        System.out.println("Usage: java Main plain プレーンテキストで文書作成");
        System.out.println("Usage: java Main html  HTMLファイルで文書作成");
    }
}
BuilderSample.kt
fun main(args: Array<String>) {
    if (args.size != 1){
        usage()
        System.exit(0)
    }
    if (args[0] == "plain"){
        val textbuilder = TextBuilder()
        val director = Director(textbuilder)
        director.construct()
        val result = textbuilder.getResult()
        println(result)
    } else if (args[0] == "html") {
        val htmlbuilder = HTMLBuilder()
        val director = Director(htmlbuilder)
        director.construct()
        val filename = htmlbuilder.getResult()
        println(filename)
    } else {
        usage()
        System.exit(0)
    }

}
fun usage() {
    println("Usage: java Main plain プレーンテキストで文書作成")
    println("Usage: java Main html  HTMLファイルで文書作成")
}
plain実行結果
================================================
[Greeting]

■朝から昼にかけて

 ・おはようございます。
 ・こんにちは。

■夜に

 ・こんばんは。
 ・おやすみなさい。
 ・さようなら。

================================================
html実行結果
Greeting.htmlが作成されました。
Greeting.html
<html><head><title>Greeting</title></head><body>
<h1>Greeting</h1>
<p>朝から昼にかけて</p>
<ul>
<li>おはようございます。</li>
<li>こんにちは。</li>
</ul>
<p>夜に</p>
<ul>
<li>こんばんは。</li>
<li>おやすみなさい。</li>
<li>さようなら。</li>
</ul>
</body></html>

クラス図

image.png

所感

  • 構造を抽象化し、かつDirectorクラスで具体的なサブクラスの記述がないことで入れ替えことができるメリットを交換可能性と呼ぶことを学んだ。
  • またIO操作の処理周りを学んだ。
  • Kotlinについては下記の点を学ぶことができた
  1. kotlinだとString#equalsは==で表し、内部でequalsを実行していて、参照の比較を行いたい場合は===を使用することを学んだ。
  2. Array等のコレクションをfor文で使用する場合は末数を数えないuntilが向いていることを学んだ。
  3. また、String Template(文字列補間)や、遅延評価、java.io.File#printWriter()について学んだ。

参考

下記を参考にさせて頂き、大変読みやすく、理解しやすかったです。

Kotlin – Create File – Examples
KotlinでAndroid開発! 基本構文をおさえよう
JavaプログラマがKotlinで便利だと感じること
Kotlinで初期化を遅延する
I/O(ファイル,ネットワーク等)
Kotlin - toString, equals, hashCode

Takunawa
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away