Hướng đối tượng trong Scala (Scala phần 2)
Bài đăng này đã không được cập nhật trong 8 năm
Sau khi kết thúc các kiến thức căn bản phần 1, chúng ta sẽ tiếp tục với lý thuyết hướng đối tượng trong Scala.
Class
Class là một thiết kế chi tiết nhằm tạo ra các đối tượng và các thể hiện của class. Một class bao gồm các khai báo biến và phương thức. Khai báo class cũng giống như hầu hết các ngôn ngữ khác
//<phạm vi> class <tên class>
public class Book()
new Book()
- Phạm vi: là các từ khóa public, protected, private
- Tên class: bắt đầu bằng chữ ....
- Khởi tạo một class bằng từ khóa new
Constructer:
Một class bao gồm các biến và phương thức, biến lưu giữ trạng thái của đối tượng và được khai bao bằng từ khóa val hoặc var. Phương thức thực hiện các xử lý và tính toán của đối tượng, được khai báo bằng từ khóa def.
Constructer với tham số:
- val: nếu contructer khai báo bằng từ khóa val, Scala sẽ chỉ sinh phương thức getter.
- var: sinh ra cả 2 phương thức getter và setter.
- private val, private var: phương thức getter và setter sẽ được tạo ra như 2 trường hợp ở trên, tuy nhiên, biến chỉ có thể truy cập từ thành viên của lớp.
- không sử dụng val hoặc var: getter, setter sẽ không được sinh ra và không thể truy cập được biến.
//sử dụng val
scala> class Book(val title: String)
defined class Book
scala> val book = new Book("Begining Scala")
book: Book = Book@39ed3c8d
scala> book.title
res0: String = Begining Scala
scala> book.title = "Try to change title"
<console>:13: error: reassignment to val
book.title = "Try to change title"
^
//Sử dụng var
scala> val person = new Person("Nguyen Linh")
person: Person = Person@29647f75
scala> person.name
res0: String = Nguyen Linh
scala> person.name = "Ngoc Hoang"
person.name: String = Ngoc Hoang
scala> person.name
res1: String = Ngoc Hoang
///Sử dụng private
scala> class Car(private var company: String) {def showCompany (car: Car) {println(car.company)}}
defined class Car
scala> val car = new Car("ferrari")
car: Car = Car@4b45a2f5
scala> car.company
<console>:14: error: variable company in class Car cannot be accessed in Car
car.company
^
scala> car.showCompany(car)
ferrari
//Không dùng val hoặc var
scala> class Lol(champion: String) {def showChampion(lol: Lol) {println(lol.champion)}}
<console>:13: error: value champion is not a member of Lol
class Lol(champion: String) {def showChampion(lol: Lol) {println(lol.champion)}}
//Lỗi do không thể truy cập vào constructer champion
Auxilary constructer
- Có thể định nghĩa một hoặc nhiều auxiliary constructor cho class, và nó cung cấp thêm những cách khác nhau để khởi tạo class.
- Auxiliary constructor được định nghĩa bằng việc khai báo một phương thức có tên this. Chúng ta có thể khai báo nhiều phương thức this, tuy nhiên hãy đảm bảo chúng có chữ ký khác nhau. Ví dụ
scala> class Book (var title: String, var ISBN: Int){
| def this(title: String){
| this(title, 1111)
| }
| def this(){
| this("Begining Scala")
| this.ISBN = 2222
| }
| override def toString = s"$title ISBN- $ISBN"
| }
defined class Book
//Khởi tạo không có tham số
scala> var book1 = new Book
book1: Book = Begining Scala ISBN- 2222
//Khởi tạo truyền vào title
scala> var book2 = new Book("Begining Java")
book2: Book = Begining Java ISBN- 1111
//Khởi tạo truyền vào title và ISBN
scala> var book2 = new Book("Begining Ruby", 3333)
book2: Book = Begining Ruby ISBN- 3333
Khai báo phương thức
- Một phương thức sẽ bao gồm các thành phần như phía dưới. Trong đó: từ khóa def, tên phương thức là bắt buộc.
def <tên phương thức>(<danh sách tham số>)<kiểu trả về> = <thân phương thức>
hoặc
def <tên phương thức>(<danh sách tham số>) {<thân phương thức>}
...
//Ví dụ
def methodName(a: Int): String = a.toString
- Gọi phương thức: Scala cũng thực hiện gọi phương thức bằng cách <thể hiện của lớp>.<tên phương thức>(). Tuy nhiên khi phương thức không có tham số thì ta có thể bỏ cặp ngoặc tròn <thể hiện của lớp>.<tên phương thức>. Nếu phương thức viết bên ngoài class thì gọi trực tiếp tên và truyền các tham số tương ứng vào.
scala> def test(a: Int) = println(a.toString)
test: (a: Int)Unit
scala> test(100)
100
Object
Singleton Object
Scala không có từ khóa static. Thay vào đó Scala có singleton object. Singleton object giống như một class bình thường, tuy nhiên nó thay từ khóa class bằng object. Singleton là một class chỉ có thể có duy nhất một instance. Không như class, singleton không thể có tham số.
scala> object Car { def drive { println("drive car") } }
defined object Car
//Giống cách gọi phương thức static bên Java
scala> Car.drive
drive car
Companion Objects
Với Scala, object và class có thể có cùng tên. Khi đó chúng được gọi là companion object hoặc companion class. Một companion object có cùng tên và nằm cùng file với class hoặc trait khác. Trait có thể nhìn nhận như Java interface, chúng ta sẽ tìm hiểu sau. Sử dụng cách tiếp cận này chúng ta có thể tạo các thành phần static trong một class. Companion object rất hữu ích cho việc impliment các phương thức helper và factory. Thử ví dụ kinh điển về tính diện tích hình học sau
trait Shape {
def area: Double
}
object Shape {
private class Circle(radius: Double) extends Shape {
override def area: Double = 3.14 * radius * radius
}
private class Rectangle(height: Double, length: Double) extends Shape {
override def area: Double = height * length
}
def apply(heigh: Double, length: Double): Shape = new Rectangle(heigh, length)
def apply(radius: Double): Shape = new Circle(radius)
}
Kết quả khi chạy
scala> val circle = Shape(5)
circle: Shape = Shape$Circle@4563e9ab
scala> circle.area
res0: Double = 78.5
scala> val rectangle = Shape(3,7)
rectangle: Shape = Shape$Rectangle@11531931
scala> rectangle.area
res2: Double = 21.0
Kế thừa
Scala hỗ trợ kế thừa đơn. Một class có một và chỉ một lớp cha, ngoại trừ lớp Any là lớp gốc nên không có class cha. Khai báo class cũng giống như Java, tuy nhiên Scala có thể có thêm tham số. Ví dụ:
scala> class Vehicle(speed: Int){
| val mph: Int = speed
| def race() = println("Racing")
| }
defined class Vehicle
scala> val vehicle = new Vehicle(100)
vehicle: Vehicle = Vehicle@6fdb1f78
scala> vehicle.race
Racing
scala> val vehicle = new Vehicle()
<console>:12: error: not enough arguments for constructor Vehicle: (speed: Int)Vehicle.
Unspecified value parameter speed.
val vehicle = new Vehicle()
^
scala>
Extends Class
Extends từ một class giống như Java, để ghi đè cacs phương thức lớp cha ta cần sử dụng từ khoá override trước phương thức muốn ghi đè
class Car(speed: Int) extends Vehicle(speed) {
override val mph: Int = speed
override def race() = println("Racing Car")
}
Trait
Tiếp tục với ví dụ ở trên, giả sử ta muốn có class Batmobile. Batmobile có thể có các phương thức race, glide, fly. Nhưng ta không thể thêm các phương thức trên vào class Vehicle bởi vì nó không phải là đặc tính thực tế thể hiện cho một chiếc xe. Và chúng ta có trait. Ở trên mình đã có nói trait giống interface trong Java. Trong Scala, một class có thể kế thừa nhiều trait và sở hữu toàn bộ mã nguồn của trait đó. Xem ví dụ sau:
trait flying {
def fly() = println("flying")
}
trait gliding {
def gliding() = println("gliding")
}
//Tạo Batmobile class extends Vehicle class với trait flying và gliding
class Batmobile(speed: Int) extends Vehicle(speed) with flying with gliding {
override val mph: Int = speed
override def race() = println("Racing Batmobile")
override def fly() = println("Flying Batmobile")
override def gliding() = println("Gliding Batmobile")
}
//Kết qủa khi chạy
scala> val vehicle= new Batmobile(300)
vehicle3: Batmobile = Batmobile@4563e9ab
scala> vehicle.fly()
Flying Batmobile
Case class
Scala có một cơ chế để khởi tạo class mà các thành phần như constructer, companion object... sẽ được tự động sinh ra, đó là case class. Case class cung cấp các thành phần như một clas bình thường, tuy nhiên trình biên dịch sẽ tự động sinh ra các phương thức toString, hashCode, equals (có thể ghi đè). Case class có thể được khởi tạo không thông qua từ khóa new, bởi vì mặc định tất cả tham số constructer của case class sẽ trở thành các thuộc tính của case class. Ví dụ
scala> case class Stuff(name:String, age: Int)
defined class Stuff
scala> val s = Stuff("Nguyen Linh", 45)
s: Stuff = Stuff(David, 45)
scala> s.toString
res2: String = Stuff(Nguyen Linh,45)
scala> s.name
res3: String = Nguyen Linh
Value Class
Ở bài trước, mình đã nói về cây phân cấp có class Any có 2 class con là AnyRef và AnyVal. Các class chúng ta định nghĩa đều kế thừa từ AnyRef (cả Scala và Java). Với value class, Scala cho phép lập trình viên định nghĩa class kế thừa AnyVal. value class là một cơ chế mới trong Scala dùng để chống lại việc phân phối thời gian thực thi object (avoid allocating runtime objects). Value class cho phép bổ sung các phương thức vào một kiểu giá trị mà không mất chi phí thời gian so với việc tạo mới một instance. ví dụ:
scala> class UserID(val value: Int) extends AnyVal
defined class UserId
//khi gọi new UserId, trình biên dịch sẽ tự động biên dịch class UserId thành giá trị Int nguyên thủy thay vì khởi tạo một object mới
scala> val userId = new UserId(9)
userId: UserId = UserId@9
scala> userId.value
res1: Int = 9
Vậy tại sao khai báo luôn val userId: Int = 9 cho nhanh mà phải lằng nhằng như vậy. Đơn giản là để cho dễ hiểu hơn khi đọc code, khi nhìn kiểu UserId người ta sẽ dễ dàng hiểu ra ngay hơn là việc sử dụng chung chung như là Int. Phương pháp này thường được sử dụng với kỹ thuật entity object, khi ta muốn tạo ra những class chỉ chứa giá trị đơn giản.
Scala vs Java vs Ruby
Class và Instance
- Scala và Ruby đều là các ngôn ngữ chuẩn hướng đối tượng. Tất cả mọi thứ đều là một thể hiện (instance) của class. Trong java, primitive và static là các khái niệm nằm ngoài nguyên mẫu hướng đối tượng.
- Với Scala và Ruby, mọi hoạt động trong thực thể đều được xử lý thông qua gọi phương thức, Java thì có cách xử lý khác mà không sử dụng gọi phương thức.
- Với Scala, lập trình viên không cần thực hiện các kiểm tra quá chi tiết khi làm việc với các kiểu dữ liệu nguyên thủy (int, char, long...).
- Ở cấp độ ngôn ngữ, Scala và Ruby đều có cách diễn đạt gần với ngôn ngữ tự nhiên mà con người thường sử dụng. Về hiệu năng, Scala sử dụng JVM giống Java và có tốc độ biên dịch gần bằng Java.
Trait, Interface và Mixin
- Mọi Java class ngoại trừ Object đều có một superclass. Java class có thể impliment một hoặc nhiều interface. Mỗi interface là một bản thiết kế của lớp, chứa các phương thức mà lớp cần. Một class có thể không có, có một hoặc có nhiều interface. Nó cung cấp cơ chế tuyệt vời để định nghĩa nguyên mẫu mà class sẽ triển khai.
- Scala có Trait. Trait cung cấp tất cả các tính như như interface của java. Tuy nhiên, Trait có thể chứa các khai báo biến và triển khai các phương thức, điều này thật tuyệt vời vì ta chỉ cần triển khai một lần ở Trait và thoải mái sử dụng ở các class kế thừa nó.
- Ruby có mixin, nó là một tập các phương thức có thể sử dụng trong bất kỳ class nào. Bởi vì Ruby không có static typing, do đó không có cách nào để định nghĩa kiểu của các tham số, và sẽ không thể sử dụng mixin để định nghĩa một nguyên mẫu như interface. Ruby mixin cung cấp một cơ chế để đưa code vào class, nhưng không thể làm điều đó với các tham số.
Object, Static, Singletons
- Với Java, một lớp có thể chứa các phương thức static và dữ liệu. Bằng cách này, lập trình viên có thể truy cập vào phương thức static mà không cần thiết phải khởi tạo một instance.
- Scala cung cấp một cơ chế tương tự dưới cái tên Object. Chúng ta đã trình bày ở phía trên.
- Ruby có singleton mixin cung cấp singleton pattern trong các chương trình Ruby. Ngoài ra, Ruby cũng có các phương thức class-level. Lập trình viên có thể thêm phương thức và thuộc tính vào class và chúng sẽ trở thành các thành phần toàn cục, có thể gọi mà không cần khởi tạo instance của class. Đó là một cơ chế hoàn toàn khác để chia sẻ các trạng thái toàn cục.
Tổng kết: Scala có nhiều điểm chung với ruby về nguyên mẫu hướng đối tượng. Scala có nhiều điểm chung với Java về việc truy cập, có thể dùng chung thư viện và sử dụng static typing. Do đó, Scala có thể là một đúng đắn cho lập trình viên nếu muốn phát triển thêm một ngôn ngữ lập trình mới.
Tham khảo : http://docs.scala-lang.org/overviews/
All rights reserved