オブジェクト指向で大切になるInterfaceの考え方やオブジェクトの再利用性を学ぶために「Java言語で学ぶデザインパターン入門」について学び、Javaとついでにkotlinで書いてみることにしました。
今回はBuilderについて書いてみます。
##Builderとは
構造物を構築する際は、各段階を踏んで組み上げていくように、構造を持ったインスタンスを組み上げていくパターンです。
サンプルプログラムでは文章という構造物を扱い、
文章は以下の段階・構造を持っているとして、各段階を構成するメソッドを定めていきます。
- タイトルを一つ含む
- 文字列をいくつか含む
- 箇条書きの項目をいくつか含む
##Builderクラス
文章を構成するためのメソッドを定めたクラスです。
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();
}
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は抽象クラスの為、実際には継承したサブクラスが入ります。
具体的なサブクラスの宣言がないので依存性がなくなり、
また、サブクラスの入れ替えが可能になることを__交換可能性__と呼ぶとのこと。
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();
}
}
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だとString Template
(文字列補間)と呼ばれる便利な書き方ができ、
println("$a + $b = ${a + b}") // 2 + 3 = 5
のように文字列を埋め込めるとのことです。
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();
}
}
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
推奨とのことです。
printWriterを取得するにはjava.io.File#printWriter()
を使用すれば取得でき、ファイルが無ければ作成され、あれば上書きされます。
追記したい場合はappendText()
を使用します。
もし、ファイルの有無によって動作を変えたい場合はjava.io.File#createNewFile():Boolean
を使用します。
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;
}
}
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クラス
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ファイルで文書作成");
}
}
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ファイルで文書作成")
}
================================================
[Greeting]
■朝から昼にかけて
・おはようございます。
・こんにちは。
■夜に
・こんばんは。
・おやすみなさい。
・さようなら。
================================================
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>
##所感
- 構造を抽象化し、かつDirectorクラスで具体的なサブクラスの記述がないことで入れ替えことができるメリットを交換可能性と呼ぶことを学んだ。
- またIO操作の処理周りを学んだ。
- Kotlinについては下記の点を学ぶことができた
- kotlinだとString#equalsは
==
で表し、内部でequalsを実行していて、参照の比較を行いたい場合は===
を使用することを学んだ。 - Array等のコレクションをfor文で使用する場合は末数を数えないuntilが向いていることを学んだ。
- また、String Template(文字列補間)や、遅延評価、java.io.File#printWriter()について学んだ。
##参考
下記を参考にさせて頂き、大変読みやすく、理解しやすかったです。
Kotlin – Create File – Examples
KotlinでAndroid開発! 基本構文をおさえよう
JavaプログラマがKotlinで便利だと感じること
Kotlinで初期化を遅延する
I/O(ファイル,ネットワーク等)
Kotlin - toString, equals, hashCode