2012-09-04 4 views
12

おそらく、スタイルと優雅さの良さを備えたScalaのエキスパートが、コンストラクタの「プッシュアウト」問題を持つ次のコードを構造化するためのより良い方法を見つけられるでしょう。モジュラ型スカラデザイン:どのようにしてコンストラクタの "プッシュアウト"定型文を避けるのですか?

私たちは、簡単な基​​底クラスで始まる:

class Foo(val i: Int, val d: Double, val s: String) { 

    def add(f: Foo) = new Foo(i + f.i, d + f.d, s + f.s) 
    override def toString = "Foo(%d,%f,%s)".format(i,d,s) 

} 

複雑なアプリケーションでの型チェックの目的のために、私は、追加の状態ずにサブクラスが必要です。

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) { 

    override def toString = "Bar(%d,%f,%s)".format(i,d,s) 

} 

現状では2つのバーを追加すると、私はFooを返すだけです。

val x = new Bar(1,2.3,"x") 
val y = new Bar(4,5.6,"y") 
val xy = x.add(y) 

REPL:

x : Bar = Bar(1,2.300000,x) 
y : Bar = Bar(4,5.600000,y) 
xy : Foo = Foo(5,7.900000,xy) 

は、どのように私は以下のように、Fooののaddメソッドをコピー&ペーストすることなく、エレガントな方法では、(むしろフーよりも)別のバーを形成するために一緒に追加するには2つのバーを得るのですか?

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) { 

    // ugly copy-and-paste from Foo: 
    def add(b: Bar) = new Bar(i + b.i, d + b.d, s + b.s) 
    override def toString = "Bar(%d,%f,%s)".format(i,d,s) 

} 

私はそのような多くのバー(すべてのFooの本質的コピーが、型チェックのために非常に重要な)を持って、カットアンドペーストを含まない溶液は、配当金を支払うことになります。

ありがとうございます!

答えて

12

できるだけ継承を避けようとしています。それでは別のアプローチがあります。

クラスBarは、Fooとまったく同じコンストラクタを持ち、どちらもステートレスです。追加情報を伝えるために複数のサブタイプが必要な場合は、汎用パラメータを「ラベル」として使用できます。たとえば、

trait Kind 
trait Bar extends Kind 

class Foo[T<:Kind](val i: Int, val d: Double, val s: String) { 
    def add(f: Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s) 
    override def toString = "Foo(%d,%f,%s)".format(i,d,s) 
} 


scala> val b1 = new Foo[Bar](2,3.0,"hello") 
b1: Foo[Bar] = Foo(2,3.000000,hello) 

scala> val b2 = new Foo[Bar](3,1.0," world") 
b2: Foo[Bar] = Foo(3,1.000000, world) 

scala> b1 add b2 
res1: Foo[Bar] = Foo(5,4.000000,hello world) 

addはタイプセーフです。次に、型クラスを使用してtoStringKindを表示させることができます。あなたは、各Bar(例えば異なるtoString)に固有の操作をサポートすることができるようにしたい場合は

+0

私は型の安全性のための「パラメータラベルとして」アイデアを、好き。ありがとうございました! –

5

、のパラダイム答え@上の拡大、あなたはさらに一歩進み、Kind型クラスを作ることができます。

trait Kind[T] { def name : String } 
trait Bar 
implicit object BarHasKind extends Kind[Bar] { val name = "Bar" } 

class Foo[T : Kind](val i : Int, val d : Double, val s : String) { 
    def add(f : Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s) 
    override def toString = implicitly[Kind[T]].name + "(%d,%f,%s)".format(i,d,s) 
} 

scala> val b1 = new Foo[Bar](2, 3.0, "hello") 
b1: Foo[Bar] = Bar(2,3.000000,hello) 

trait Biz 
implicit object BizHasKind extends Kind[Biz] { val name = "Biz" } 

scala> val b2 = new Foo[Biz](1, 1.0, "One") 

それはちょうど前のように安全なタイプの通りです:あなたは、タグに依存することKindで抽象的に宣言すると、暗黙オブジェクトでの実装を提供したい任意のプロパティのために

scala> b1 add b2 
<console>:16: error: type mismatch; 
    found : Foo[Biz] 
    required: Foo[Bar] 

scala> b2 add b2 
resN: Foo[Biz] = Biz(2,2.000000,OneOne) 

+0

興味深い。 Fooの内部でdef name = "Foo"メソッドを宣言し、BarとBizにそれをオーバーライドさせることに対するあなたの "暗黙的"なアプローチの利点は何ですか? –

+1

それは本当に有利ではありません、私はあなたが受け入れられた答えに提案されたものと同様に、継承のない環境でも正確に達成できることを示すことを試みていました。 – Philippe

2

タイプパラメータ化のアプローチには、継承である自然な方法で機能を拡張することができないという制限があります。だから、別のアプローチは、(単にコンストラクタにデリゲートである)オーバーライド可能なファクトリメソッドのようになります。

class Foo(val i: Int, val d: Double, val s: String) { 

    protected def create(i: Int, d: Double, s: String) = new Foo(i, d, s) 

    def add[A <: Foo](f: A) = create(i + f.i, d + f.d, s + f.s) 

    override def toString = "Foo(%d,%f,%s)".format(i,d,s) 
} 

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) { 

    protected override def create(i: Int, d: Double, s: String) = new Bar(i, d, s) 

    override def toString = "Bar(%d,%f,%s)".format(i,d,s) 

    // additional methods... 

} 

println(new Foo(10, 10.0, "10") add new Bar(10, 10.0, "10")) 
println(new Bar(10, 10.0, "10") add new Foo(10, 10.0, "10")) 
関連する問題