DRY trong Ruby bằng inheritance và mixin

Tram Ho

Trong bài viết này mình xin đề cập đến cách cơ bản để giảm thiểu trùng lặp code trong ứng dụng, bạn sẽ hay nghe đến DRY – Don’t Repeat Yourself.

Inheritance

Ruby là một ngôn ngữ lập trình hướng đối tượng (OOP) nên nó có tính chất inheritance, và nó dùng cho các class.
Trong ruby thì chỉ cần khai báo đơn giản bởi ký tự <, và thứ tự là con < cha. Hãy xem ví dụ đơn giản như sau.

Ở trên khai báo 3 class gồm Animal, và Dog, Cat là 2 class con trực tiếp của Animal. Khi đó ta có như sau:

superclass của DogCat đều là Animal

Cat không cần định nghĩa gì cả nhưng vẫn thực hiện được câu lệnh trên là bởi vì Cat thừa hưởng lại tất cả gồm class method, instance method và biến từ cha của nó là class Animal.

Dog cũng được thừa hưởng các method và biến từ Animal, ngoài biến color từ cha thì nó còn định nghĩa thêm cho mình biến name. Cho nên khác Cat, Dog có thêm biến cho object của mình.

Trong DogAnimal đều có method speak, như này nghĩa là Dog đang ghi đè lại chính method của cha mà nó kế thừa, còn Cat thì không ghi đè method này. Khi gọi speak như trên thì dog sẽ cho nội dung bên trong method speak của class Dog, còn với cat thì nó lấy luôn method speak của class Animal.

Việc ghi đè lại method của cha trong cây kế thừa chính là tính chất polymorphism – đa hình – một trong 4 tính chất quan trọng của OOP.

super

Với tính kế thừa của mình, Ruby cung cấp cho bạn method rất hay đấy là super. Khi bạn gọi super tại một method trong class hiện tại, nó sẽ tìm ngược lên các class cha trong cây kế thừa để tìm ra method có cùng tên với method kia để thực hiện nó. Hãy xem ví dụ để dễ hình dung:

Ta thấy, khi gọi super trong method speak của class Dog thì nó đã tìm và thực hiện luôn method speak trong class Animal.

Một cách quen thuộc để dùng super là lúc khởi tạo với method initialize như sau:

Như bạn thấy ở trên, class Dog không có khởi tạo cho thuộc tính name, và thay vào đó nó gọi đến super để tìm vào method initialize trong class Animal. Và thế là sau khi khởi tạo xong thì dog có luôn cả namecolor.

Mixing trong module.

Một cách thường gặp khác để DRY code đấy là sử dụng module.
Nhắc lại định nghĩa về module một chút, trong Ruby, module được dùng để gom nhóm các method, biến, hằng số với nhau (khi gom nhóm các class thì từ khóa module lúc này đóng vai trò là namespace). Ta mixin module vào trong class bằng từ khóa include, prepend hoặc extend.

Ví dụ với một cây kế thừa đơn giản như sau:

Chạy thử ta được kết quả:

Ta thấy recknemo có thể gọi đến method swim còn paws thì không, bởi vì chỉ có class DogFish đã mixin module Swimmable ở bên trong nó.

Ancestors method

Trên đây là 2 cách để DRY code. Phần này mình xin giới thiệu cho các bạn một thủ thuật nhỏ để khi bạn gọi một method từ một class hoặc từ một instance cụ thể của class đó, method ancestors.

Giả sử bạn có mô hình kế thừa kết hợp mixin như sau:

Ta thấy khi gọi đến method swim từ instance của class Fish, nó sẽ gọi đến method swim trong chính class đó. Mặc dù trong AnimalSwimmable cũng đều có method này, đó là việc ghi đè đã được nhắc đến bên trên. Nhưng bản chất nó là gì, hãy gọi method ancestors để xem nó trả về cho bạn cái gì.

Method ancestors sẽ trả về danh sách tất cả các class tổ tiên trong cây kế thừa và kể cả các module được mixin bên trong các class của cây đó (Kernel là module được mixin trong class Object). Và mỗi khi gọi một method từ class hoặc instance của class đó, Ruby sẽ lần lượt đi theo thứ tự từng class và module theo kết quả trả về của method ancestors để tìm kiếm method được gọi đến. Như ví dụ trên, method sẽ được tìm kiếm theo thứ tự Fish –> Swimmable –> Animal –> Object –> Kernel –> BasicObject. Tại đâu phát hiện ra có method swim, ta sẽ nhảy vào thực hiện và trả ra kết quả, và kết quả trả ra là method swim của Fish.

Thêm một ví dụ cuối cùng, giả sử mình tạo thêm module Pree và mixin nó trong class Fish bằng từ khóa prepend

Với ví dụ này thì method sẽ được tìm kiếm theo thứ tự Pree –> Fish –> Swimmable –> Animal –> Object –> Kernel –> BasicObject. Bạn có thể để ý, prepend sẽ chèn module lên trước, còn include sẽ chèn module ngay sau class đó.

Inheritance vs Modules

Phần cuối cùng trong bài viết này, mình xin đưa ra một vài so sánh giữa inheritance và module

  • Ruby không cung cấp đa kế thừa kiểu một con có nhiều cha, nhưng có thể mixin thoải mái nhiều module trong class.
  • Không thể tạo một instance của module, module chỉ dùng nhóm các method, biến, hằng và làm namespace
  • Với quan hệ is-a, hãy dùng kế thừa, ex: Dog is-a Animal. Với quan hệ has-a, hãy dùng mixin, ex: Dog has-a khả năng swim

Tham khảo

https://launchschool.com/books/oo_ruby/read/inheritance#classinheritance
https://www.oreilly.com/learning/ruby-cookbook-modules-and-namespaces


Cám ơn bạn đã theo dõi bài viết.

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo