GoFパターンをなるべく短文で説明を求められたとき用
- Iteratorパターン・・・Iterator[hasNext, next]
- Adapter・・・異なるインターフェースのオブジェクトを、同じインターフェースで扱う
- TemplateMethod・・・abstract method
- FactoryMethod・・・FactoryオブジェクトがProductオブジェクトを作るという話
- Singleton・・・Singleton
- Prototype・・・clone用のメソッドを用意するという話
- Builder・・・オブジェクトのフィールドを埋めながら、buildメソッドを呼び出すとオブジェクトが作れるクラスの話
- AbstractFactory・・・複数の関連するオブジェクトを生成するFactoryを用意する話。インターフェースがAbstract。(FactoryMethodの集まり)
- Bridge・・・ストラテジーパターンオブジェクトに実装を任せてるという話。ストラテジーパターンオブジェクトに委譲の橋をかけるイメージ。
- Strategy・・・多態性の話。キャッシュポリシーなどのポリシーは、Strategyの派生形
- Composite・・・Componentインターフェースを継承したCompositeとLeafを作って、CompositeにComponentのリストを持つようにすれば、ディレクトリとファイルの関係を定義しやすくなる話。
- Decorator・・・すでにあるオブジェクトに付加的なロジックを追加したい場合、同じインターフェースで該当オブジェクトを持つようなDecoratorオブジェクトを作ればいいという話。
- Visitor・・・(処理をするものをVisitor、処理をされるものをAccepterとしておく。) VisitorにAccepterを渡して処理をする時、例えばJavaでは、オーバーロードされたメソッドの呼び出しは、変数の型に依存する。この場合は、Accepterのacceptメソッド経由で実インスタンスのメソッドを呼び出してVisitorのvisitメソッドを呼んでもらえば対応できる。
- Chain of Responsibility・・・Responsibility Chain
- Facade・・・処理を一箇所にまとめる
- Mediator・・・複数のオブジェクトに関してマネージメントするクラスに関する話。
- Observer・・・Observer
- Memento・・・状態の復元
- State・・・状態に関するストラテジー
- Flyweight・・・文字リテラルのように複数生成する必要のないものを内部的にキャッシュし、何度呼び出されても、生成は一度にするという話
- Proxy・・・キャッシュサーバーなどのように代理オブジェクトを用意する話。
- Command・・・1つの処理を1オブジェクトにしておけば、Queueにして逐次実行するなどできる話
- Interpreter・・・CompositeパターンのComponentがCommandだったときの話
Scala標準APIを利用したリファクタリング例
object Main extends App { def containsNeg1(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num < 0) exists = true exists } def containsNeg2(nums: List[Int]): Boolean = nums.exists(_ < 0) def containsOdd1(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num % 2 == 1) exists = true exists } def containsOdd2(nums: List[Int]): Boolean = nums.exists(_ % 2 == 1) }
Closureを利用したリファクタリング例
import java.io.File object Main extends App { } object FileMatcher { private def filesHere: Array[File] = new File(".").listFiles def filesEnding(query: String) = for (file <- filesHere; if file.getName.endsWith(query)) yield file def filesContaining(query: String) = for (file <- filesHere; if file.getName.contains(query)) yield file def filesRegex(query: String) = for (file<-filesHere; if file.getName.matches(query)) yield file def filesMatching(query: String, matcher: (String, String) => Boolean) = for (file<-filesHere; if matcher(file.getName, query)) yield file } object FileMatcherWithBoundFunction { private def filesHere: Array[File] = new File(".").listFiles def filesEnding(query: String) = filesMatching(query, _.endsWith(_)) def filesContaining(query: String) = filesMatching(query, _.contains(_)) def filesRegex(query: String) = filesMatching(query, _.matches(_)) def filesMatching(query: String, matcher: (String, String) => Boolean) = for (file<-filesHere; if matcher(file.getName, query)) yield file } object FileMatcherWithClosure { private def filesHere: Array[File] = new File(".").listFiles def filesEnding(query: String) = filesMatching(query, _.endsWith(query)) def filesContaining(query: String) = filesMatching(query, _.contains(query)) def filesRegex(query: String) = filesMatching(query, _.matches(query)) def filesMatching(query: String, matcher: String => Boolean) = for (file<-filesHere; if matcher(file.getName)) yield file }
末端再帰
- 末端再帰に持ち込めれば、「whileループによる実装と同じパフォーマンスまで最適化」を受けることができる。
- 末端再帰の最適化をオフにするコンパイラオプションは、
-g:notailcalls
- ただし、Scalaの現状の最適化は、「同じメソッドに対して関数オブジェクト等を利用しないで末端再帰した場合のみ」行われる
object Main extends App { // count == 1 try { tailRecursive() } catch { case ex: Exception => val count = ex.getStackTrace .map { _.getMethodName }.count(_.contains("tailRecursive")) println(count) } // count == 11 try { tailRecursiveNo() } catch { case ex: Exception => val count = ex.getStackTrace .map { _.getMethodName }.count(_.contains("tailRecursive")) println(count) } class Marker(count_ : Int) { val count: Int = count_ } def tailRecursive(marker: Marker = new Marker(0)): Marker = { if (marker.count >= 10) { throw new Exception() } else { tailRecursive(new Marker(marker.count + 1)) } } def tailRecursiveNo(marker: Marker = new Marker(0)): Marker = { if (marker.count >= 10) { throw new Exception() } else { val result = tailRecursiveNo(new Marker(marker.count + 1)) result } } }
名前付き引数とデフォルト引数
object Main extends App { def function1(a: Float = 1, b: Float): Float = { a + 2 * b } function1(b = 3, a = 4) function1(b = 3) }
関数の中に関数
- クロージャに相当するものは、変数の実体ではなく、参照をキャプチャーしている。
- 参照もとがライフサイクル上廃棄されることになった場合は、自動的に値等に再配置される。
import scala.io.Source object Main extends App { processFile("src/Main.scala", 30) def processFile(filename: String, width: Int): Unit = { val source = Source.fromFile(filename) for (line <- source.getLines()) processLine(filename, width, line) } private def processLine(filename: String, width: Int, line: String) = { if (line.length > width) { println(filename + ": " + line.trim) } } } object Main extends App { processFile("src/Main.scala", 30) def processFile(filename: String, width: Int): Unit = { def processLine(line: String) = { // メソッドは、変数の実体ではなく、参照をキャプチャーしている。 if (line.length > width) { println(filename + ": " + line.trim) } } val source = Source.fromFile(filename) for (line <- source.getLines()) processLine(line) } }