こんにちは。長期出張でへとへとになってしまったたーせるです。おひさしぶりーふ。
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に手を出す開発者にとっては釈迦に説法かも知れませんが。