3

次のクラス階層があります。

sealed abstract class A
case class B extends A
case class C extends A

fooここで、クラス、、、およびにメソッドをA追加BしますC。しかし、私は決してクラスを変更したくありません。そこで、次のようにライブラリパターンをポン引きに適用します。

abstract class RichA(a: A) {
    def foo
}
class RichB(b: B) extends RichA(b){
    def foo = { println("B"); //do something with b}
}
class RichC(c: C) extends RichA(c) {
    def foo = { println("C"); //do something with c}
}
/////////////
implicit def A2RichA(a: A) = {
    a match {
    case a: B => new RichB(a)
    case a: C => new RichC(a)
    }
}
implicit def B2RichB(b: B) = new RichB(b)
implicit def C2RichC(c: C) = new RichC(c)
/////////////
def test() = {
    def printA(a: A) = {
        a.foo
    }
    val obj = new C
    printA(obj)
}
test() //This prints "C"

これはA2RichA機能していますが、Aの各サブクラスのcaseステートメントが含まれているため、暗黙関数の実装は少し醜いように見えます。これをよりエレガントな方法で実行できますか?基本的な要件は、タイプのオブジェクトを呼び出す場合foo、オブジェクトの動的タイプ内またはそれに応じてA適切なメソッドを呼び出す必要があることです。fooBC

4

2 に答える 2

1

A2RichAコンパイラが正しい変換を提供できるようにすることで、メソッドをもう少しエレガントにすることができます (視点によっては不可解になります) 。

implicit def A2RichA(a: A): RichA = 
   a match {
      case b: B => b // the compiler turns this into B2RichB(b)
      case c: C => c // the compiler turns this into C2RichC(c)
   }

implicit def B2RichB(b: B): RichA = new RichB(b)

implicit def C2RichC(c: C): RichA = new RichC(c)

ただし、基本的な問題を回避するためのより簡単な方法はないと思います。引数の動的な型に基づいて変換を提供する必要があります。暗黙的な検索はコンパイル時に発生するため、静的型に基づく変換のみを提供できます。

実行時に反射的に変換を検索することもできますが、これは単純でもエレガントでもありません (そして、このような小さな階層にはお勧めできません)。

階層は封印されているため、新しいクラスを階層に追加するときに変換を提供し忘れると、コンパイラは警告を発します。

于 2012-07-18T18:55:58.733 に答える
0

最初のアプローチ: -OPによると不十分-

fooこれは、単一の実装が階層内のすべてのクラスに適していると仮定すると、次のようになります。

sealed abstract class A
case class B() extends A
case class C() extends A

class RichA(a: A) {
  def foo() { println(this.a.getClass.getSimpleName) }
}

implicit def a2RichA(a: A): RichA = new RichA(a)

(new C).foo() /* anon$1$C */


2番目のアプローチ: -動的ディスパッチがない-

私の2番目のアプローチは、 Scalaコレクションライブラリの内部に触発されました。ただし、動的ディスパッチがないため、問題は解決しません。

sealed abstract class A
case class B() extends A
case class C() extends A

abstract class RichA(a: A) {
  def foo(): Unit
}

class RichB(b: B) extends RichA(b) {
  def foo() { println("Doing B-specific stuff") }
}

class RichC(c: C) extends RichA(c) {
  def foo() { println("Doing C-specific stuff") }
}

sealed trait RichBuilder[T <: A] {
  def apply(t: T): RichA
}

implicit object RichB extends RichBuilder[B] {
  def apply(b: B) = new RichB(b)
}

implicit object RichC extends RichBuilder[C] {
  def apply(c: C) = new RichC(c)
}

implicit def a2RichA[T <: A](t: T)
                            (implicit rb: RichBuilder[T])
                            : RichA = {

  rb(t)
}

(new B).foo() /* B-specific */
(new C).foo() /* C-specific */
// (new C).asInstanceOf[A].foo() /* ERROR: Can't find an implicit */


請求:

オブジェクトの実行時のタイプに依存する動作が必要な場合は、次の2つの可能性があります。

  1. パターンマッチング
  2. ダイナミックディスパッチ

元のコードで使用されているパターンマッチングは、マッチングが実行されるコード内の1つの場所がある状況につながるようです。したがって、クラス階層の拡張には、その場所への変更が伴います。たとえば、階層がクライアントによって拡張されている場合、これは不可能な場合があります。

動的ディスパッチにはこの欠点はありません。これが、このアプローチがScalaコレクションで使用される理由である可能性があります(この記事で簡単に説明されています)。したがって、ダブルディスパッチの使用を試みることができます(ビジターパターンを参照)。ただし、これには、基本クラスが対応するメソッドをすでに宣言している必要があるという欠点があります。これはA、Pimp MyLibraryPatternの目標をいくらか無効にします。ここではScala2.10の新しい動的機能が役立つかもしれませんが、経験が不足しているため、コメントできません。

于 2012-07-18T08:46:30.537 に答える