[Scala] - day 11 : Object, Case Class, và Trait

Phần trước ta đã cùng nhau tìm hiểu về class và như các bạn đã biết class định nghĩa 1 lần nhưng tạo ra không giới hạn những instance từ nó. Hôm nay ta sẽ cùng tìm hiểu về 3 component object mới để hoàn thiện và hiểu rõ hơn về class, nó giúp cho thiết kế object oriented của bạn linh động và tốt hơn.

Object

Một Object được hiểu là 1 class mà chỉ có 1 instance, được hiểu như singleton trong thiết kế hướng đối tượng.Vì nó đã là object rồi,bạn không cần khởi tạo và có thể gọi trực tiếp từ tên object. Một object được tự động khởi tạo khi nó lần đầu được gọi đến, cũng có nghĩa là nó sẽ không được khởi tạo khi định nghĩa.
Object và class không hoàn toàn tách rời. Một object có thể extend class, nhưng không thể làm điều ngược lại, object không thể extend lẫn nhau.
Để define 1 object ta dùng từ khoá object, 1 object không nhận bất kì parameter nào, nhưng bạn có thể define field, method và internal class với cấu trúc của class.
Syntax: Define một Object 
object <identifier> [extends <identifier>] [{ fields, methods, and classes }]
Ví dụ đầu tiên, hãy xem object tự khởi tạo như nào:
scala> object Hello { println("in Hello"); def hi = "hi" }
defined object Hello 
scala> println(Hello.hi)
in Hello
hi 
scala> println(Hello.hi)
hi
Bạn thấy rằng lần đầu object Hello được gọi đến nó lập tức in ra "in Hello" ,khi khởi tạo và lần gọi sau chúng ta không thấy điều này.

Apply Method và Companion Object

Chúng ta đã biết về apply method cho class, object có feature tương tự như vậy, chính điều đó giúp chúng ta có thể gọi đến object bằng name.
Thực tế, đây chính là cách mà List hoạt động trong Scala,List object have apply() method, nó nhận vào những argument và trả về cho chúng ta collection.Tương tự với kiểu collection khác, như Option trông phần về Monadic Collection, apply() method của Option object nhận vào 1 single value và trả về Some[A] hoặc nếu không có gì nó trả về None. Điều này được gọi là factory trong thiết kế hướng đối tượng.
Đặc biệt, factory pattern là 1 cách phổ biến để tạo mới các instance của class từ companion object. Một companion object là một object mà có cùng tên với class và được define ở cùng 1 file với class. Companion objects và class được coi là 1 khi gọi đến, chúng chia sẻ với nhau toàn bộ private và protect field cũng như method.
Ok,hãy làm rõ hơn với ví dụ:
scala> :paste
// Entering paste mode (ctrl-D to finish)

class Multiplier(val x: Int) { def product(y: Int) = x * y }
object Multiplier { def apply(x: Int) = new Multiplier(x) }

// Exiting paste mode, now interpreting.

defined class Multiplier
defined object Multiplier   
scala> val tripler = Multiplier(3)
tripler: Multiplier = Multiplier@5af28b27 
scala> val result = tripler.product(13)
result: Int = 39
OK, giờ bạn hãy tạo thêm các private từ object và class  Multiplier và thử gọi qua lại.

Command-Line Applications với Object

Hầu hết các ngôn ngữ đều có thể tạo các command line application, những application có thể execute từ shell. Cơ bản nhất là đọc các input,  như là từ màn hình, và sau đó in ra. Phức tạp hơn có thể là làm việc với các tập tin hay cơ sở dữ liệu, truy cập máy tính khác qua mạng, hoặc khởi động các ứng dụng mới.
Scala cũng cung cấp cho ta feature tương tự, nó tự đọc "main" method trong object như 1 enty point cho application.Vậy để tạo command-line application trong Scala, ta thêm “main” method nhận vào 1 input là 1 array string, sau đó để chạy command-line application bạn chỉ đơn giản chạy Scala command với tên object. 
Ví dụ:
Hoais-Mac:~ HoaiPT$ cat > Greeting.scala
object Greeting {
    def main(args: Array[String]) {
        println("hello : " + args(0))
    }
Hoais-Mac:~ HoaiPT$ scalac Greeting.scala 
Hoais-Mac:~ HoaiPT$ scala Greeting "HoaiPT"
hello : HoaiPT
***) theo cách viết trên của tôi nếu tôi không truyền vào bất cứ biến gì thì sao?
OK, hãy tạo command-line application của riêng bạn với những kiến thức chúng ta đã đi qua và share cho mọi người command-line application của bạn bằng cách comment bên dưới nhé.

Case Class

Case class là class mặc định có sẵn hàm khởi tạo, bao gồm cả companion object cũng được sinh tự động.
Case class là 1 lựa chọn hàng đầu cho những class được định nghĩa để lưu trữ data bởi định nghĩa là class nhưng được dùng như object.
Syntax: Defining Case Class 
case class <identifier> ([var] <identifier>: <type>[, ... ])
                                     [extends <identifier>(<input parameters>)]
                                     [{ fields and methods }]
Từ khoá val là không cần thiết vớiCase Class,  mặc định, case class sẽ convert parameter thành value fields nên từ khoá val là không cần. Bạn có thể dùng từ khoá var nếu bạn muốn tạo variable field.
Một số hàm được khởi tạo cùng case class :
  • apply : như các bạn đã biết,là factory method để khởi taọ case class
  • copy : trả về copy của instance, parameter là class field bạn muốn thay đổi.
  • equals : cái tên quá rõ ràng về mục đích của method này, dùng để so sánh 2 instance.
  • unapply : extract instance thành 1 tuple chứa field của case class.
OK,ví dụ nào:
scala> case class Character(name: String, isThief: Boolean)
defined class Character 
scala> val h = Character("Hadrian", true)
h: Character = Character(Hadrian,true) 
scala> val r = h.copy(name = "Royce")
r: Character = Character(Royce,true) 
scala> h == r
res0: Boolean = false 
scala> h match {
        |        case Character(x, true) => s"$x is a thief"
        |        case Character(x, false) => s"$x is not a thief"
        |}
res1: String = Hadrian is a thief

Trait

Trait là môt kiểu class mà bạn có thể đa kế thừa. Class, case class, object và cả trait đều không thể extend quá 1 class nhưng có thể extend từ rất nhiều trait cùng lúc.Và không như những kiểu trên trait không thể được khởi tạo.
Trait giống Object là nó không thể nhận vào parameter.Để define 1 trait ta sử dụng từ khoá trait.
Syntax: Define Trait
trait <identifier> [extends <identifier>] [{ fields, methods, and classes }]
Thử vài ví dụ với Trait.
scala> trait A {def hi: String = "Hi A"}
defined trait A

scala> trait B {def hello: String = "Hello B"}
defined trait B

scala> class C extends A with B { def greeting : String = hi + " " + hello }
defined class C

scala> (new C).greeting
res2: String = Hi A Hello B
Bạn nghĩ sao nếu A và B cùng có method là hi ? khi đó bạn cần override nó ở C, như sau :
scala> trait A {def hi: String = "Hi A"}
defined trait A

scala> trait B {def hi: String = "Hello B"}
defined trait B

scala> class C extends A with B { override def hi : String = super.hi }
defined class C

scala> (new C).hi
res3: String = Hello B
Bạn hãy tự thử vài ví dụ khác cho mình.

Self Type

Self Type là một chú thích cho Trait để khẳng định rằng nó chỉ có thể được mix (không phải extend) với 1 kiểu cụ thể. Một Trait với self type sẽ không thể được mix vào 1 class hay trait mà class hay trait đó không mang kiểu được định nghĩa (self type). Với cách này, nó sẽ đảm bảo rằng Trait sẽ luôn luôn được mix bởi Class hay Trait mang type đó.
Một cách sử dụng phổ biến của Self Type là thêm chức năng hay override từ Trait cho Class có input parameter. Một Trait không thể extend một Class có input parameter, bởi vì Trait giống Object là nó không thể nhận vào parameter. Tuy nhiên, nó có thể tuyên bố mình là một subtype của lớp cha mẹ mang kiểu cố định và sau đó thêm các chức năng hay override nếu cần thiết.
Syntax: Self Type
trait ..... { <identifier>: <type> => .... }
Identifier tiêu chuẩn dùng trong self types là “self,” ,ngoài ra tất nhiên còn 1 vài kiểu khác nữa. Việc sử dụng identifier là “self ” như common giúp cho code của bạn dễ đọc và thân thiện hơn với các lập trình viên Scala khác.
scala> class A { def hi = "hi" }
defined class A

scala> trait B { self: A =>
             | override def toString = "B: " + hi
             |}
defined trait B

scala> class C extends B
<console>:9: error: illegal inheritance;
 self-type C does not conform to B's selftype B with A
        class C extends B
                                  ^
scala> class C extends A with B
defined class C

scala> new C()
res1: C = B: hi

Instantiation với Trait

Phần trước chúng ta đã tìm hiểu về Trait và sử dụng nó khi extend vào class.Khi 1 class extend 1 trait, class đó sẽ thừa hưởng những method cũng như field từ Trait hoặc kế thừa từ subtype.
Một phương pháp thay thế cho việc sử dụng Trait là add nó vào Class khi class đó được khởi tạo.
Khi khởi tạo class bạn có thể thêm vào 1 hay nhiều Trait bằng cách sử dụng từ khoá with (không thể sử dụng extend).
Ví dụ :

scala> class A
defined class A

scala> trait B { self: A => }
defined trait B

scala> val a = new A with B
a: A with B = $anon$1@26a7b76d
Nhìn vào kết quả bạn thấy instance a được gán cho class name là $anon$1, tên này có nghĩa là “anonymous” (vô danh).Vậy là thực sự Scala vẫn compile cho bạn 1 class và khởi tạo nó lên sau đó gán vào a, khi này a không là instance của 1 class không có tên.

Importing Instance Members

Ở phần về Packaging ta đã biết sử dụng từ khoá import để thêm vào các class bên ngoài, ngoài ra từ khoá import còn được dùng để mà import member của class hay object và namespace hiện tại, sau khi import ta có thể access chúng trực tiếp mà không cần chỉ rõ Class hay Object.
Ví dụ :
scala> case class Receipt(id: Int, amount: Double, who: String, title: String)
defined class Receipt

scala> {
        | val latteReceipt = Receipt(123, 4.12, "fred", "Medium Latte")
        | import latteReceipt._
        | println(s"Sold a $title for $amount to $who")
        |}
Sold a Medium Latte for 4.12 to fred
Điều này cũng hoạt động tương tự khi bạn import 1 class hay object bên ngoài.
scala> import util.Random._
import util.Random._

scala> val letters = alphanumeric.take(20).toList.mkString
letters: String = MwDR3EyHa1cr0JqsP9Tf

scala> val numbers = shuffle(1 to 20)
numbers: scala.collection.immutable.IndexedSeq[Int] = Vector(5, 10, 18, 1, 16, 8, 20, 14, 19, 11, 17, 3, 15, 7, 4, 9, 6, 12, 13, 2)
Bạn thấy đấy ta có thể sử dụng trực tiếp alphanumeric(): Stream và shuffle(Traversable) là những member của object util.Random mà không cần gọi thông qua Random.

OK, hôm nay có vẻ rất dài nhưng thật khó để tách ra khi nó có quan hệ mật thiết với nhau. Đã đến lúc chúng ta viết 1 application hoàn thiện, hẹn các bạn ngày hôm sau với chia sẻ về :  Configuring Your First Scala Project with SBT.



Comments