31

最近、私は次のSOの質問を読みました:

Scala で Visitor パターンを使用するユースケースはありますか? Java で Visitor パターンを使用するたびに、Scala でパターン マッチングを使用する必要がありますか?

タイトルの質問へのリンク: Visitor Pattern in Scala。受け入れられた答えはで始まります

はい、ビジター パターンの代わりにパターン マッチングから始めるべきでしょう。これを参照して ください http://www.artima.com/scalazine/articles/pattern_matching.html

私の質問 (上記の質問に触発された) は、どの GOF デザイン パターンが Scala でまったく異なる実装を持っているかということです。Scala でプログラミングしている場合、デザイン パターン (Gang of Four) の Java ベースのプログラミング モデルに従わないように注意する必要があるのはどこですか?

創造パターン

  • 抽象工場
  • ビルダー
  • 工場方式
  • プロトタイプ
  • Singleton :オブジェクトを直接作成する (scala)

構造パターン

  • アダプタ
  • 複合
  • デコレータ
  • ファサード
  • フライ級
  • プロキシー

行動パターン

  • 責任の連鎖
  • 指示
  • 通訳者
  • イテレーター
  • メディエーター
  • 記念品
  • 観察者
  • ストラテジー
  • テンプレート方式
  • 訪問者 :パターンマッチング (scala)
4

3 に答える 3

45

これらのほとんどすべてについて、これらのパターンのすべてではなく一部のユースケースをカバーする 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 valmemento パターンの最も単純なアプリケーションの多くにほぼ理想的です。

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 で簡単に実行できます (つまり、任意のメソッドを関数に変換します)。

于 2012-06-20T20:15:43.440 に答える
12

では、これらのパターンを簡単に見てみましょう。私は純粋に関数型プログラミングの観点からこれらすべてのパターンを見ており、OO の観点から Scala が改善できる多くのことを除外しています。Rex Kerr の回答は、私自身の回答に対する興味深い反論を提供します (私は自分の回答を書いた後にのみ彼の回答を読みました)。

それを踏まえて、永続データ構造(機能的に純粋なデータ構造)とモナドを研究することが重要だと言いたいです。深く掘り下げたい場合は、圏論の基本が重要だと思います。圏論は、命令型のものを含むすべてのプログラム構造を形式的に記述することができます。

創造のパターン

コンストラクターは単なる関数です。() => Tたとえば、型 T のパラメーターなしのコンストラクターは、 function にすぎません。実際、関数に対する Scala の構文糖衣は、ケース クラスで利用されます。

case class T(x: Int)

これは次と同等です。

class T(val x: Int) { /* bunch of methods */ }
object T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

TT(n)代わりにインスタンス化できるようにしますnew T(n)。次のように書くこともできます。

object T extends Int => T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

これはT、コードを変更することなく、正式な関数になります。

これは、創造パターンを考える上で留意すべき重要なポイントです。それでは、それらを見てみましょう。

抽象工場

これはあまり変わらないかもしれません。クラスは、密接に関連する関数のグループと考えることができるため、密接に関連する関数のグループはクラスを通じて簡単に実装できます。これは、このパターンがコンストラクターに対して行うことです。

ビルダー

Builder パターンは、カリー化された関数または部分的な関数のアプリケーションに置き換えることができます。

def makeCar: Size => Engine => Luxuries => Car = ???
def makeLargeCars = makeCar(Size.Large) _

def makeCar: (Size, Engine, Luxuries) => Car = ???
def makeLargeCars = makeCar(Size.Large, _: Engine, _: Luxuries)

工場方式

サブクラス化を破棄すると時代遅れになります。

プロトタイプ

変わりません。実際、これは機能的なデータ構造でデータを作成する一般的な方法です。ケースクラスのcopyメソッド、またはコレクションを返すコレクションのすべての変更不可能なメソッドを参照してください。

シングルトン

データが不変の場合、シングルトンは特に有用ではありませんが、Scalaobjectはこのパターンを安全な方法で実装しています。

構造パターン

これは主にデータ構造に関連しており、関数型プログラミングの重要なポイントは、データ構造が通常不変であるということです。これらのパターンを翻訳しようとするよりも、永続的なデータ構造、モナド、および関連する概念を調べた方がよいでしょう。

ここでのいくつかのパターンが関連していないわけではありません。原則として、構造パターンを同等の機能に変換しようとするのではなく、上記のことを検討する必要があると言っているだけです。

アダプタ

このパターンはクラス (名義型付け) に関連しているため、それがある限り重要であり、そうでない場合は関係ありません。

オブジェクト指向アーキテクチャに関連するため、上記と同じです。

複合

レンズとジッパーにたくさん。

デコレータ

Decorator は単なる関数合成です。クラス全体を装飾している場合は、当てはまらない場合があります。ただし、機能を関数として提供する場合、その型を維持しながら関数を構成することはデコレーターです。

ファサード

ブリッジと同じコメント。

フライ級

コンストラクターを関数と考えるなら、flyweight は関数のメモ化と考えてください。また、Flyweight は、永続的なデータ構造の構築方法に関連する固有のものであり、不変性から多くの恩恵を受けます。

プロキシー

アダプターと同じコメント。

行動パターン

これはいたるところにあります。それらのいくつかはまったく役に立たないものですが、他のものは機能的な設定で常に関連しています.

責任の連鎖

Decoratorと同様、関数合成です。

指示

これは関数です。データが不変の場合、元に戻す部分は必要ありません。それ以外の場合は、関数とその逆のペアを保持してください。レンズも参照してください。

通訳者

これはモナドです。

イテレータ

コレクションに関数を渡すだけで廃止できます。実際、それTraversableforeachで行うことです。Iteratee も参照してください。

メディエーター

まだ関連しています。

記念品

不変オブジェクトでは役に立たない。また、そのポイントはカプセル化を維持することですが、これは FP では大きな問題ではありません。

このパターンはシリアライゼーションではないことに注意してください。

観察者

関連しますが、関数型リアクティブ プログラミングを参照してください。

これはモナドです。

ストラテジー

戦略は機能です。

テンプレート方式

これは OO 設計パターンであるため、OO 設計に関連しています。

ビジター

ビジターは関数を受け取る単なるメソッドです。実際、それがTraversable's のforeach機能です。

Scala では、エクストラクタに置き換えることもできます。

于 2012-06-20T21:03:31.647 に答える
2

Command関数型言語ではパターンはまったく必要ないと思います。オブジェクト内のコマンド関数をカプセル化してから適切なオブジェクトを選択する代わりに、適切な関数自体を使用してください。

Flyweight単なるキャッシュであり、ほとんどの関数型言語でデフォルトの実装があります ( clojureのメモ化)

Template methodStrategy、メソッドに適切な関数を渡すだけでState実装できます。

したがって、関数型スタイルを試すときは、デザイン パターンに深入りするのではなく、関数型の概念 (高階関数、遅延、カリー化など) に関する本を読むことをお勧めします。

于 2012-06-20T16:40:57.647 に答える