Variance định nghĩa quan hệ kế thừa của các kiểu được tham số hoá (Parameterized Types). Variance là tất cả về sub-typing.
Nào hãy xem ảnh dưới đây để hiểu rõ hơn Type Parameter là gì.
Ở đây, T có thể hiểu là Type Parameter và List[T] được hiểu như là Generic
Ví dụ List[T], nó có thể là List[Int], List[AnyVal]… thì những List[Int], List[AnyVal] được biết như là Parameterized Types
Variance định nghĩa quan hệ kế thừa giữa các kiểu được tham số hoá (Parameterized Types) như ở trên
Ưu điểm của Variance
Ưu điểm chính của Variance trong Scala:
- Variance giúp cho Collection của Scala trở nên Type-Safe hơn
- Variance giúp linh động hơn khi phát triển
- Variace mang đến cho chúng ta một kỹ thuật giúp phát triển các ứng dụng đáng tin cậy
Các kiểu của Variance
Scala hỗ trợ 3 kiểu Variance:
- Covariant
- Invariant
- Contravariant
Covariant
Nếu S là subtype của T thì List[S] là subtype của List[T]
Mối quan hệ kiểu này giữa 2 Parameterized Types được biết như là Convariant
Cú pháp Covariance
Để miêu tả mối quan hệ của Covariance giữa 2 Parameterized Types, Scala sử dụng cú pháp sau đây:
Dấu + được thêm đằng trước kiểu tham số dùng để định nghĩa Covariance trong Scala
T ở đây là kiểu tham số, còn dấu + định nghĩa Covariance của Scala
Chú ý rằng để đơn giản nên tôi sử dụng List ở đây trong ví dụ. Tuy nhiên nó có thể là bất cứ kiểu gì trong Scala như là Set[+T], Ordered[+T] …
Ví dụ
Sau đây là một ví dụ về sử dụng Covariance trong Scala.
class Animal[+T](val animial:T) class Dog class Puppy extends Dog class AnimalCarer(val dog:Animal[Dog]) object ScalaCovarianceTest{ def main(args: Array[String]) { val puppy = new Puppy val dog = new Dog val puppyAnimal:Animal[Puppy] = new Animal[Puppy](puppy) val dogAnimal:Animal[Dog] = new Animal[Dog](dog) val dogCarer = new AnimalCarer(dogAnimal) val puppyCarer = new AnimalCarer(puppyAnimal) println("Done.") } }
Animal class được định nghĩa sử dụng Variance Annotation như ở trên là +T, do đó chúng ta có thể truyền vào dogAnimal hoặc subtype của nó puppyAnimal để tạo một AnimalCarer object.
Nếu chúng ta bỏ Variance Annotation khi định nghĩa class Animal như bên dưới đây.
class Animal[T](val animial:T) // Đoạn Code còn lại sẻ được giữ nguyên như ở trên.
Nó sẽ không được biên dịch. Chúng ta sẽ nhận được thông báo lỗi như bên dưới đây.
Type mismatch, expected: Animal[Dog], found: Animal[Puppy]
Để giải quyết lỗi bên trên thì chúng ta phải sử dụng Covience của Scala.
Chúng ta có thể giải thích lại ví dụ theo Covience của Scala như sau
Puppy là một loại của Dog, Animal[Puppy] là một loại của Animal[Dog]. Chúng ta có thể sử dụng Animal[Puppy] ở bất cứ chỗ nào chúng ta cần sử dụng Animal[Dog]. Điều này được hiểu là Covience trong Scala.
Contravariant
Nếu S là subtype của T thì List[T] là subtype của List[S]
Mối quan hệ kiểu này giữa 2 Parameterized Types được biết như là Contravariant
Cú pháp Contravariance
Để miêu tả mối quan hệ của Contravariance giữa 2 Parameterized Types, Scala sử dụng cú pháp sau đây:
Dấu – được thêm đằng trước kiểu tham số dùng để định nghĩa Contravariance trong Scala
Ví dụ
Sau đây là một ví dụ về sử dụng Contravariance trong Scala.
abstract class Type [-T]{ def typeName : Unit } class SuperType extends Type[AnyVal]{ override def typeName: Unit = { println("SuperType") } } class SubType extends Type[Int]{ override def typeName: Unit = { println("SubType") } } class TypeCarer{ def display(t: Type[Int]){ t.typeName } } object ScalaContravarianceTest { def main(args: Array[String]) { val superType = new SuperType val subType = new SubType val typeCarer = new TypeCarer typeCarer.display(subType) typeCarer.display(superType) } }
Như chúng ta định nghĩa Contravariance trong Type[-T], đoạn code trên chạy tốt như mong muốn. TypeCarer.display() được định nghĩa với Type[Int] ví dụ ở trên là Subtype, nhưng nó vẫn chấp nhận Type[AnyVal] bởi vì chúng ta sử dụng Contravariance
Nếu chúng ta bỏ dấu “-” thành Type[T], thì chúng ta sẽ gặp lỗi khi biên dịch.
Invariant
Nếu S là subtype của T thì List[S] và List[T] sẽ không có quan hệ kế thừa hay sub-typing. Nghĩa là cả 2 đều không có quan hệ gì với nhau.
Kiểu quan hệ này giữa 2 Parameterized Types được biết như là Invariant hay Non-Variant
Trong Scala, mặc định Generic Type là Non-Variant, không có quan hệ kế thừa. Nếu chúng ta định nghĩa Parameterized Types không sử dụng dấu “+” và “-“, thì chúng được hiểu là Invariant.
Variance Annotation là gì?
Variance Annotation nghĩa là định nghĩa dấu “+” hoặc “-” trước Type Parameter.
Ví dụ +T và -T được biết như là Variance Annotation trong Scala
Kết luận
Chúng ta có thể tóm tắt lại cả 3 khái niệm trên như sau
Kiểu Variance | Cú pháp | Miêu tả |
Covariant | [+T] | Nếu S là subtype của T, thì List[S] cũng là subtype của List[T] |
Contravariant | [-T] | Nếu S là subtype của T, thì List[T] cũng là subtype của List[S] |
Invariant | [T] | Nếu S là subtype của T, thì List[S] và List[T] không có liên hệ |