これらのほとんどすべてについて、これらのパターンのすべてではなく一部のユースケースをカバーする Scala の代替手段があります。もちろん、これはすべてIMOですが、次のとおりです。
創造のパターン
ビルダー
Scala はこれを Java よりもジェネリック型でよりエレガントに行うことができますが、一般的な考え方は同じです。Scala では、パターンは次のように最も単純に実装されます。
trait Status
trait Done extends Status
trait Need extends Status
case class Built(a: Int, b: String) {}
class Builder[A <: Status, B <: Status] private () {
private var built = Built(0,"")
def setA(a0: Int) = { built = built.copy(a = a0); this.asInstanceOf[Builder[Done,B]] }
def setB(b0: String) = { built = built.copy(b = b0); this.asInstanceOf[Builder[A,Done]] }
def result(implicit ev: Builder[A,B] <:< Builder[Done,Done]) = built
}
object Builder {
def apply() = new Builder[Need, Need]
}
(これを REPL で試す場合は、クラスとオブジェクト Builder が同じブロックで定義されていることを確認してください。つまり、を使用します。) 、ジェネリック型引数、およびケース クラスの copy メソッドを使用した型:paste
のチェックの組み合わせにより、非常に強力な<:<
組み合わせ。
ファクトリ メソッド (および抽象ファクトリ メソッド)
ファクトリ メソッドの主な用途は、型をまっすぐにすることです。それ以外の場合は、コンストラクターを使用することもできます。Scala の強力な型システムを使用すると、型をまっすぐに維持するための助けが必要ないためapply
、クラスのコンパニオン オブジェクトでコンストラクターまたはメソッドを使用して、そのように作成することもできます。特にコンパニオン オブジェクトの場合、そのインターフェイスの一貫性を維持することは、ファクトリ オブジェクトのインターフェイスの一貫性を維持することよりも難しくありません。したがって、ファクトリ オブジェクトのほとんどの動機はなくなりました。
同様に、抽象ファクトリ メソッドの多くのケースは、コンパニオン オブジェクトに適切なトレイトを継承させることで置き換えることができます。
プロトタイプ
もちろん、オーバーライドされたメソッドなどは Scala でその役割を果たします。ただし、Design Patterns Web サイトで Prototype パターンに使用されている例は、Scala (または Java IMO) ではあまりお勧めできません。ただし、サブクラスに自分で決定させるのではなく、サブクラスに基づいてスーパークラスにアクションを選択させたい場合は、不格好なテストmatch
ではなく使用する必要があります。instanceof
シングルトン
Scala はこれらをobject
. それらはシングルトンです-使用して楽しんでください!
構造パターン
アダプタ
ここではScala のtrait
方がはるかに強力です。たとえば、インターフェイスを実装するクラスを作成するよりも、インターフェイスの一部のみを実装するトレイトを作成して、残りを定義することができます。たとえば、 java.awt.event.MouseMotionListener
次の 2 つのメソッドを入力する必要があります。
def mouseDragged(me: java.awt.event.MouseEvent)
def mouseMoved(me: java.awt.event.MouseEvent)
ドラッグを無視したいかもしれません。次に、次のように記述しますtrait
。
trait MouseMoveListener extends java.awt.event.MouseMotionListener {
def mouseDragged(me: java.awt.event.MouseEvent) {}
}
mouseMoved
これを継承した場合のみ実装できるようになりました。つまり、同様のパターンですが、Scala を使用するとさらに強力になります。
橋
ブリッジは Scala で記述できます。Java ほど悪くはありませんが、大量のボイラープレートです。これを抽象化の方法として日常的に使用することはお勧めしません。最初にインターフェースについて慎重に検討してください。トレイトの力が増したことで、ブリッジを書きたくなる場所でより精巧なインターフェイスを単純化するためにそれらを使用できることが多いことに注意してください。
場合によっては、Java ブリッジ パターンの代わりにインターフェイス トランスフォーマーを記述したい場合があります。たとえば、マウスのドラッグと移動を同じインターフェイスを使用して扱い、それらを区別するブーリアン フラグのみを使用したい場合があります。その後、次のことができます
trait MouseMotioner extends java.awt.event.MouseMotionListener {
def mouseMotion(me: java.awt.event.MouseEvent, drag: Boolean): Unit
def mouseMoved(me: java.awt.event.MouseEvent) { mouseMotion(me, false) }
def mouseDragged(me: java.awt.event.MouseEvent) { mouseMotion(me, true) }
}
これにより、ブリッジ パターンのボイラープレートの大部分をスキップしながら、高度な実装の独立性を実現しながら、クラスを元のインターフェイスに従わせることができます (したがって、それらをラップおよびアンラップし続ける必要はありません)。
複合
複合パターンは、ケース クラスを使用すると特に簡単に実現できますが、更新を行うのはかなり困難です。Scala と Java で同じように価値があります。
デコレータ
デコレータは厄介です。通常、継承が必要な場合とは異なる場合、別のクラスで同じメソッドを使用することは望ましくありません。あなたが本当に欲しいのは、デフォルトの代わりにあなたが望むことをする同じクラスの別のメソッドです。多くの場合、 enrich-my-library パターンは優れた代替手段です。
ファサード
Facade は Java よりも Scala の方がうまく機能します。これは、トレイトに部分的な実装を持たせることができるため、それらを組み合わせるときに自分ですべての作業を行う必要がないからです。
フライ級
Flyweight のアイデアは Java と同様に Scala でも有効ですが、それを実装するために自由に使えるツールが他にもいくつかlazy val
ありby-name parameters
ます。関数が実際にその値を使用する場合、関数の引数を作成するために必要な作業。とはいえ、場合によっては Java パターンが変更されないこともあります。
プロキシー
Scala で Java と同じように動作します。
行動パターン
責任の連鎖
責任者を順番にリストできる場合は、次のことができます。
xs.find(_.handleMessage(m))
メッセージが処理された場合にhandleMessage
戻るメソッドを全員が持っていると仮定します。true
メッセージを途中で変更したい場合は、代わりに折り畳みを使用してください。
Buffer
責任のある関係者を何らかの形に落とし込むのは簡単なので、Java ソリューションで使用される精巧なフレームワークが Scala で使用されることはめったにありません。
指示
このパターンは、ほぼ完全に関数に取って代わられています。たとえば、すべての代わりに
public interface ChangeListener extends EventListener {
void stateChanged(ChangeEvent e)
}
...
void addChangeListener(ChangeListener listener) { ... }
あなたは単に
def onChange(f: ChangeEvent => Unit)
通訳者
Scala は、デザイン パターンとして提案されている単純なインタープリターよりも劇的に強力なパーサー コンビネーターを提供します。
イテレータ
Scala はIterator
その標準ライブラリに組み込まれています。Iterator
独自のクラスをorに拡張するのはほとんど簡単Iterable
です。再利用が簡単になるため、通常は後者の方が適しています。確かに良いアイデアですが、単純すぎてパターンとは言えません。
メディエーター
これは Scala では正常に機能しますが、一般的に可変データには有用であり、注意して使用しないと、メディエーターでさえ競合状態などに陥る可能性があります。代わりに、可能な場合は、関連するデータをすべて 1 つの不変コレクション、ケース クラスなどに格納するようにしてください。また、調整された変更が必要な更新を行う場合は、すべてを同時に変更してください。これは とのインターフェースには役立ちませんjavax.swing
が、それ以外の場合は広く適用できます。
case class Entry(s: String, d: Double, notes: Option[String]) {}
def parse(s0: String, old: Entry) = {
try { old.copy(s = s0, d = s0.toDouble) }
catch { case e: Exception => old }
}
複数の異なるリレーションシップ (それぞれに 1 つのメディエーター) を処理する必要がある場合、または変更可能なデータがある場合に備えて、メディエーター パターンを保存します。
記念品
lazy val
memento パターンの最も単純なアプリケーションの多くにほぼ理想的です。
class OneRandom {
lazy val value = scala.util.Random.nextInt
}
val r = new OneRandom
r.value // Evaluated here
r.value // Same value returned again
遅延評価専用の小さなクラスを作成したい場合があります。
class Lazily[A](a: => A) {
lazy val value = a
}
val r = Lazily(scala.util.Random.nextInt)
// not actually called until/unless we ask for r.value
観察者
これはせいぜい壊れやすいパターンです。可能な限り、不変の状態を維持するか (Mediator を参照)、状態の変化に関して 1 つのアクターが他のすべてのアクターにメッセージを送信するが、各アクターが時代遅れに対処できるアクターを使用することを優先します。
州
これは Scala でも同様に有用であり、メソッドレス トレイトに適用される場合、実際に列挙を作成する方法として好まれています。
sealed trait DayOfWeek
final trait Sunday extends DayOfWeek
...
final trait Saturday extends DayOfWeek
(多くの場合、この定型文の量を正当化するために平日に何かをしたいと思うでしょう)。
ストラテジー
これは、戦略を実装する関数をメソッドに持たせ、選択できる関数を提供することでほぼ完全に置き換えられます。
def printElapsedTime(t: Long, rounding: Double => Long = math.round) {
println(rounding(t*0.001))
}
printElapsedTime(1700, math.floor) // Change strategy
テンプレート方式
特性はここで非常に多くの可能性を提供するので、別のパターンと考えるのが最善です. 抽象化のレベルで、できるだけ多くの情報からできるだけ多くのコードを埋めることができます。私はそれを同じものとは呼びたくありません。
ビジター
構造型付けと暗黙の変換の間で、Scala は Java の典型的なビジター パターンよりも驚くほど多くの機能を備えています。元のパターンを使用しても意味がありません。正しい方法から気が散ってしまうだけです。例の多くは、実際には、訪問されているものに定義された関数があればいいのにと思っています。これは、Scala で簡単に実行できます (つまり、任意のメソッドを関数に変換します)。