はじめに
本記事はJava言語で学ぶデザインパターン入門を参考にしながら
JavaではなくKotlinで実装してみようというものです。
調べてみる
Template Methodについて調べてみます。
WikpediaではTemplate Methodは次のように説明されています。
Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、
そのアルゴリズムの具体的な設計をサブクラスに任せることである。
そのため、システムのフレームワークを構築するための手段としてよく活用される
毎回思うのですがWikipediaはあぁそうなんだ〜で終わる説明ですね。
Template Methodの理解を深めるためには実装したほうが早そうですので実装していきます。
実装してみる
次の順序でファイルにPersonを出力する処理をアルゴリズムとします。
このアルゴリズムをTemplate Methodパターンを用いて実装したいと思います。
処理1. ファイルをオープンする
処理2. ファイルにPersonを書き込む
処理3. ファイルをクローズする
Person
Personについてですが次のように定義しました。
特にデータにはこだわりありませんのでFirstNameとLastNameを持つ単純なデータクラスとしました。
data class Person(val firstName : String, val lastName : String)
AbstractWriter
PersonAbstractWriterという抽象クラスのwriteメソッドにアルゴリズムを定義します。
処理1はOpen、処理2はprocess、処理3はcloseというメソッドで処理をします。
この3つのメソッドはabstractとなっていますのでサブクラスで具体的な処理を定義します。
このようにTemplate Methodパターンではまず、
ある処理の大まかなアルゴリズムを定義した抽象クラスを作成します。
abstract class AbstractWriter() {
abstract fun open()
abstract fun process(data : Person)
abstract fun close()
fun write(data : List<Person>)
{
// 処理1. ファイルをオープンする
open()
// 処理2. ファイルにデータを書き込む
data.forEach {
process(it)
}
// 処理3. ファイルをクローズする
close()
}
}
ConcreteWriter
次に抽象クラスのAbstractWriterを継承したサブクラスを定義し、
abstractで宣言していたメソッドをOverrideし具体的にどのように出力するか処理を実装していきます。
今回はPersonをCSV形式またはテキスト形式で書き込めるように処理を実装していきたいと思います。
CsvWriter
CSV形式での出力では次のようにヘッダーを出力したいです。
なのでファイルをオープンするときにヘッダーを書き込んでおきます。
あとはCSV形式ですのでカンマ区切りで各プロパティを書き込むようにします。
lastName,firstName
姫路,太郎
榊,真喜子
喜々津,優輔
class CsvWriter(name : String) : AbstractWriter()
{
private val name : String = name
private val header : String = "lastName,firstName\n"
private var file : FileWriter? = null
override fun open() {
file = FileWriter(name)
file?.write(header)
}
override fun process(data: Person) {
val csv = "${data.lastName},${data.firstName}\n"
file?.write(csv)
}
override fun close() {
file?.close()
}
}
TextWriter
テキスト形式ですのでPersonをそのまま書き込むだけなのですが、
次のように名簿のようにしたいと思いましたのであわせて番号も書き込むことにしました。
1 姫路 太郎
2 榊 真喜子
3 喜々津 優輔
class TextWriter(name : String) : AbstractWriter()
{
private val name : String = name
private var count : Int = 0
private var file : FileWriter? = null
override fun open() {
file = FileWriter(name)
}
override fun process(data: Person) {
count++
var text = "${this.count} ${data.lastName} ${data.firstName}\n"
file?.write(text)
}
override fun close() {
file?.close()
count = 0
}
}
Main
サブクラスの定義が終わりましたのでMainから呼び出します。
fun main(args : Array<String>) {
val persons = listOf<Person>(Person("太郎", "姫路"), Person("真喜子", "榊"), Person("優輔", "喜々津"))
val csvWriter = CsvWriter( "persons.csv")
val textWriter = TextWriter("persons.txt")
csvWriter.write(persons)
textWriter.write(persons)
}
Mainを実行しますと次のようなファイルが次の内容で出力されます。
lastName,firstName
姫路,太郎
榊,真喜子
喜々津,優輔
1 姫路 太郎
2 榊 真喜子
3 喜々津 優輔
おわりに
結論としてTemplate Methodパターンでやりたいことは処理の共通化です。
- インタフェースの共通化
- 処理順序の共通化
特に2の処理順序の共通化がTemplate Methodパターンの利点だと思います。
今回の例ですとオープンはいつクローズはいつと順序が決められているため、
サブクラスごとにオープンやクローズのタイミングが違うとかは絶対に起きなくなります。
このようにプログラマによって指針が違うような事柄が含まれる場合は、
Template Methodパターンを使って処理を共通化しておくとサブクラスがより管理しやすくなると思います。