こんにちは。長期出張でへとへとになってしまったたーせるです。おひさしぶりーふ。
6月3日の未明、Appleが「Swift」という新しいプログラミング言語を発表しました。
スローガンはObjective-C without C。ぼくはわくわくが止まりません。
さっそく Xcode 6 beta をダウンロードして、新生言語 Swift をいじってみました。
人生初 Swift です。
Hello World
まずはお決まりの儀式*1から。
main.swift
import Foundation
println("Hello, World!")
main()関数もセミコロンもいらないあたりがチャームポイントですな。
勢い優先で Xcode 6 をインストールしたものの、取り立てて作りたいものを思いつかなかったので(おい)、ちょいと言語に慣れるためにいくつかデザインパターンを実装してみました。
参考にした本は「Rubyによるデザインパターン」です。Twitter のタイムラインで「 Swift は Ruby に似ている」ってつぶやいている人もいましたし。
TemplateMethodパターン
おなじみ、TemplateMethodパターンです。
TemplateMethodパターンの特徴は、手続きの順序だけを基底クラスで規定して(←シャレ)実際のアルゴリズムは派生クラスに記述することです*2。
このような設計を行う理由は、「不変なもの」と「可変なもの」をプログラム上で分離するためです。極端な話、TemplateMethodは、手続きの順序だけは何があっても変わらない場合に有効です。
お題
ある月次報告を、HTML と プレーンテキスト形式で出力する「レポートジェネレータ」を作ることを想定してみましょう。まぁこの題材は「Rubyによるデザインパターン」の パクr 参考なのですが。
HTMLバージョン
<html> <head> <title>月次報告</title> </head> <body> <p>順調</p> <p>最高の調子</p> </body> </html>
プレーンテキストバージョン
**** 月次報告 **** 順調 最高の調子
どちらも、タイトルを出力して、本文を出力して……といった具合に、手続きが固定化されています。これは(ちょっと強引だが) TemplateMethod の予感!!
まずは、固定化された手続きを基底クラス「Report」に記述します。
Reportクラス
class Report {
var title : String
var text : String[]
// コンストラクタみたいなやつです
init() {
title = "月次報告"
text = ["順調", "最高の調子"]
}
func outputReport() {
outputStart()
outputHead()
outputBodyStart()
outputBody()
outputBodyEnd()
outputEnd()
}
func outputBody() {
for line in text {
outputLine(line)
}
}
func outputStart() { }
func outputHead() { }
func outputBodyStart() { }
func outputLine(line: String) { }
func outputBodyEnd() { }
func outputEnd() { }
}outputReport()の中に、たくさんのメソッド呼び出しが順序立てて書かれています。それぞれのメソッドはだいたいからっぽです。
まるで処理の順序をテンプレ化しているように見えます。これがTemplateMethodと呼ばれるゆえん。
次に、Reportクラスを継承したHTMLReportクラスを書きましょう。派生クラスでは、規定クラスに書いた各種手続きメソッドをオーバーライドして、具体的な処理を実装していきます。
HTMLReportクラス
class HTMLReport : Report {
override func outputStart() {
println("<html>")
}
override func outputHead() {
println(" <head>")
println(" <title>\(title)</title>")
println(" </head>")
}
override func outputBodyStart() {
println(" <body>")
}
override func outputLine(line : String) {
println(" <p>\(line)</p>")
}
override func outputBodyEnd() {
println(" </body>")
}
override func outputEnd() {
println("</html>")
}
}そして、プレーンテキストバージョンも同様に基底クラスをオーバーライドします。
PlainTextReportクラス
class PlainTextReport : Report {
override func outputHead() {
println("**** \(title) ****")
}
override func outputLine(line : String) {
println(line)
}
}これでよし。
あとは、実際にReportクラスを使う処理を書きます。
var report : Report report = HTMLReport() report.outputReport() report = PlainTextReport() report.outputReport()
実行結果
<html> <head> <title>月次報告</title> </head> <body> <p>順調</p> <p>最高の調子</p> </body> </html> **** 月次報告 **** 順調 最高の調子 Program ended with exit code: 0
これ、いろんな利点がありますが、だいたい以下2点は多くの方に同意していただけるのではないでしょうか。
- 月次報告を新たに別のフォーマットで出力する必要が生じたときに、対応するのがラク(Reportを継承して、必要な処理を埋めるだけだから)
- レポートクラスを使う側もラク(フォーマットクラスによらず、使い方が一貫しているから)
一方で、TemplateMethodパターンには欠点もあります。
継承ベースの設計では、派生クラスが基底クラスに依存するため、たとえば現行のテンプレートに必ずしも基づかないフォーマットで月次報告を出力したくなったときに、かなりの修正が必要になるかも知れません。
次は、この問題点を解決するために別の切り口から考えてみます。
Strategyパターン
Strategyパターンは、継承ではなく委譲ベースでアルゴリズムをスイッチするのが特徴です。
class Report {
var title: String
var text: String[]
var formatter: Formatter
init(formatter : Formatter) {
title = "月次報告"
text = ["順調", "最高の調子"]
self.formatter = formatter
}
func outputReport() {
formatter.outputReport(self)
}
}
Formatterプロトコル
protocol Formatter {
func outputReport(context: Report)
}
HTMLFormatterクラス
class HTMLFormatter : Formatter {
func outputReport(context: Report) {
println("<html>")
println(" <head>")
println(" <title>\(context.title)</title>")
println(" </head>")
println(" <body>")
for line in context.text {
println(" <p>\(line)</p>")
}
println(" </body>")
println("</html>")
}
}
PlainTextFormatterクラス
class PlainTextFormatter : Formatter {
func outputReport(context: Report) {
println("**** \(context.title) ****")
for line in context.text {
println("\(line)")
}
}
}使い方はさっきと少し違います。
var report : Report report = Report(formatter: HTMLFormatter()) report.outputReport() report = Report(formatter: PlainTextFormatter()) report.outputReport()
Reportクラスに、Formatterを採用したオブジェクトを渡してやります。Strategyでは、委譲メインの設計になったこともあり、継承の静的な依存関係から解放されました。
余談ですが、僕がデザインパターンを学んだときに初めて目にしたのが、このStrategyパターンでした。
とはいえ、TemplateMethodと較べてStrategyが絶対的に優れているわけではなく、あくまで切り口が違うだけだと思っています。お仕事でプログラムを書いていると、むしろTemplateMethodを適用したソースをよく目にしますし。
実行結果は先程と同じなので割愛します。
おまけ:クロージャでお手軽Strategy
ちなみに、Swiftではクロージャが使えるようになりました。
邪道かも知れませんが、その場限りのコンテキストならクラスである必要はなく、クロージャで記述することも可能です。
ちょっと丁寧に書くとこんな感じでしょうか。
main.swift
// Reportクラス
class Report {
var title: String
var text: String[]
var formatter: (context: Report) -> ()
init(formatter: (context: Report) -> ()) {
title = "月次報告"
text = ["順調", "最高の調子"]
self.formatter = formatter
}
func outputReport() {
formatter(context: self)
}
}
// クロージャで作るストラテジ
var formatter = { (context: Report) -> () in
println("<html>")
println(" <head>")
println(" <title>\(context.title)</title>")
println(" </head>")
println(" <body>")
for line in context.text {
println(" <p>\(line)</p>")
}
println(" </body>")
println("</html>")
}
// ためすよ
var report : Report
report = Report(formatter)
report.outputReport()ちなみに、report のイニシャライザに直接クロージャをぶち込むこともできます。なんかちょっと変態的になりました。
var report = Report({ (context: Report) -> () in
println("<html>")
println(" <head>")
println(" <title>\(context.title)</title>")
println(" </head>")
println(" <body>")
for line in context.text {
println(" <p>\(line)</p>")
}
println(" </body>")
println("</html>")
})
report.outputReport()なんか当初の目的からだいぶ逸れた気もします。ごめんなさい。
あと、Swiftの日本語ドキュメントが早く出たらいいなぁと思いました。まる。
*1:新しいプログラム言語を学ぶときのはじめの一歩は、画面に「Hello World」と表示する簡単なプログラムを書くことです。
*2:このタイミングでSwiftに手を出す開発者にとっては釈迦に説法かも知れませんが。