Có 3 cách thường thấy để đưa một module vào một class, đó là include
, prepend
và extend
. Trong bài viết này chúng ta sẽ tìm hiểu và so sánh sự giống và khác nhau của 3 khái niệm trên.
include trong Ruby
include
là cách phổ biến nhất để import code từ một module khác vào class, include
thêm các method của một module vào một class như là các instance methods. Điều này có nghĩa là bạn có thể gọi các method này trên các đối tượng của lớp đó.
Ví dụ về include
1
2
3
4
5
6
7
8
9
10
11
12
module Greetings
def greet
puts "Hello world!"
end
end
class Person
include Greetings
end
person = Person.new
person.greet # Output: "Hello world!"
ancestors chain khi include module vào class
Khi ta include một module vào một class, thì module đó sẽ được thêm vào ancestors chain của class đó, vị trí là ngay sau chính class đó và trước super class của class đó.
Khi ta kiểm tra ancestors chain của ví dụ trên thì sẽ thấy như sau:
1
2
Person.ancestors
# => [Person, Greetings, Object, Kernel, BasicObject]
Module Greetings
được thêm vào ancestors chain, ngay sau class Person
và trước super class Object
.
Trường hợp include nhiều module thì ancestors chain sẽ như thế nào?
Ví dụ đoạn code sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module Greetings
def greet
puts "Hello world!"
end
end
module Working
def work
puts "Working..."
end
end
class Person
include Greetings
include Working
end
Lúc này khi ta kiểm tra Person.ancestors
thì sẽ thấy kết quả như sau:
1
[Person, Working, Greetings, Object, Kernel, BasicObject]
Module được include sau thì sẽ gần với class đó hơn, tóm lại, nếu 2 module được include vào có cùng một method, thì method ở module được include sau sẽ được ưu tiên thực hiện. Điều này có nghĩa là thứ tự include rất quan trọng vì nó xác định method nào sẽ được gọi nếu có sự xung đột tên method.
prepend trong Ruby
prepend
có từ Ruby phiên bản 2.0, mặc dù ít được dùng hơn so với include
và extend
.
Về bản chất thì prepend
tương tự include
, điểm khác biệt là ở chỗ prepend
sẽ thêm module đó vào trước class trong ancestors chain. Đồng nghĩa với việc nếu module dược prepend
và class đều có cùng một method giống nhau, thì method ở module được prepend
sau cùng sẽ được ưu tiên thực hiện.
Ví dụ về prepend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module Greetings
def greet
puts "Hello world!"
end
end
module Working
def greet
puts "Working..."
end
end
class Person
prepend Greetings
include Working
def greet
puts "Hi"
end
person = Person.new
person.greet # Output: "Hello world!"
end
Lúc này khi ta kiểm tra Person.ancestors
thì sẽ thấy kết quả như sau:
1
[Greetings, Person, Working, Object, Kernel, BasicObject]
Như các bạn thấy, module được prepend
sẽ xếp đầu tiên, tiếp theo đó là class rồi mới đến module được include
.
extend trong Ruby
extend
khác với include
và prepend
, khi ta extend
module vào một class, thì những method của module đó sẽ được import vào như là class method, chứ không phải là instance method.
extend
module vào class sẽ không thêm module đó vào ancestors chain của class.
Ví dụ về extend
1
2
3
4
5
6
7
8
9
10
11
module Greetings
def greet
"Hello world!"
end
end
class Person
extend Greetings
end
puts Person.greet # Output: "Hello world!"
Ở đây, module Greetings
được extend vào class Person
, và method greet
trở thành một class method của Person
.
Tổng kết
Trong Ruby, ba phương thức include
, extend
, và prepend
được sử dụng để thêm các phương thức của module vào class hoặc đối tượng. Mỗi phương thức có cách hoạt động và mục đích sử dụng riêng. Đây là bảng tóm tắt so sánh về các phương thức này:
Phương thức | Mô tả |
---|---|
include | * Định nghĩa các method của module như là các instance method. * Nếu đã có method cùng tên trong class, method của class sẽ được ưu tiên gọi. * Module được include sau sẽ nằm gần class hơn trong ancestors chain và method của nó sẽ được ưu tiên gọi hơn nếu có xung đột tên method. |
prepend | * Chèn module vào đầu ancestors chain, các method của module được định nghĩa như là các instance method. * Nếu đã có method cùng tên trong class, method của module sẽ được ưu tiên gọi hơn. |
extend | * Các method được định nghĩa trong module có thể được sử dụng như là các class method. * Sẽ không thêm module đó vào ancestors chain của class. |
Việc nắm vững cách sử dụng các phương thức này sẽ giúp bạn tổ chức mã nguồn một cách hiệu quả và linh hoạt hơn trong các dự án Ruby của mình. Hãy thử nghiệm và áp dụng chúng để tận dụng tối đa khả năng của Ruby!