2012-09-04 34 views
12

Có lẽ một chuyên gia về Scala với một cảm giác tốt về phong cách và sự thanh lịch có thể giúp tôi tìm ra một cách tốt hơn để cấu trúc mã sau đây, trong đó có một vấn đề khởi tạo "đẩy ra".Thiết kế Modale Scala: làm cách nào để tránh một bản thiết kế "push-out" của nhà xây dựng?

Chúng tôi bắt đầu với một lớp cơ sở đơn giản:

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) 

} 

Đối với mục đích loại kiểm tra trong một ứng dụng phức tạp, tôi yêu cầu một sub-class mà không cần bất kỳ nhà nước bổ sung:

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

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

} 

Khi đứng , khi tôi thêm hai Thanh, tôi chỉ lấy lại một Foo:

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

với câu trả lời sau trong REPL:

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

Làm cách nào để có hai Thanh để cùng nhau tạo thành một Thanh khác (chứ không phải là Foo) một cách thanh lịch mà không phải sao chép và dán phương thức thêm của Foo như dưới đây?

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) 

} 

Tôi có nhiều thanh như vậy (tất cả bản sao của Foo, nhưng rất quan trọng để kiểm tra loại), giải pháp cắt và dán sẽ trả cổ tức.

Cảm ơn!

Trả lời

12

Tôi cố gắng tránh thừa kế càng nhiều càng tốt. Vì vậy, đây là một cách tiếp cận thay thế.

Lớp Bar có cùng một hàm tạo giống với Foo và cả hai đều là trạng thái không trạng thái. Nếu bạn muốn có một số loại phụ, chỉ để truyền tải bất kỳ thông tin bổ sung nào, bạn có thể sử dụng thông số chung làm "nhãn". Ví dụ:

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) 

Hiện tại add là loại an toàn. Sau đó bạn có thể sử dụng một lớp loại để có được toString để hiển thị Kind.

+0

Tôi thích ý tưởng "tham số như nhãn". Cảm ơn bạn! –

5

Mở rộng trên @ câu trả lời mẫu mực, nếu bạn muốn có thể để hỗ trợ các hoạt động cụ thể cho từng Bar (ví dụ khác nhau toString), bạn có thể đi một bước xa hơn và làm cho Kind một typeclass.

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") 

Nó chỉ là as type an toàn như trước:

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) 

Đối với bất kỳ tài sản mà bạn muốn được phụ thuộc vào thẻ, khai báo trừu tượng trong Kind và cung cấp triển khai trong các đối tượng tiềm ẩn.

+0

Thú vị. Lợi thế của phương pháp "ngầm" của bạn là gì khi khai báo phương thức 'def name = "Foo"' bên trong Foo, và có Bar và Biz ghi đè nó? –

+1

Nó không thực sự là một lợi thế, tôi đã chỉ cố gắng để cho thấy rằng bạn có thể đạt được chính xác mà ngay cả trong một thiết lập không có thừa kế, tương tự như một đề xuất trong câu trả lời được chấp nhận. – Philippe

2

Cách tiếp cận với loại parametrisation có một giới hạn ở chỗ nó không cho phép bạn mở rộng chức năng một cách tự nhiên là thừa kế. Vì vậy, một phương pháp thay thế có thể là một phương pháp nhà máy có thể ghi đè (mà chỉ đơn giản là một đại biểu cho nhà xây dựng):

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")) 
Các vấn đề liên quan