12/08/2018, 13:25

Phân tích thiết kế hướng đối tượng trong Ruby - Phần 2

Chào các bạn, trong bài viết này mình sẽ tiếp tục bàn luận về thiết kế hướng đối tượng trong Ruby. Các bạn có thể tham khảo lại phần 1 của chuyên mục này tại Phần 1 Sự phụ thuộc (Dependencies) là gì? Một đối tượng phụ thuộc vào một đối tượng khác khi nó bắt buộc phải thay đổi theo đối tượng ...

Chào các bạn, trong bài viết này mình sẽ tiếp tục bàn luận về thiết kế hướng đối tượng trong Ruby. Các bạn có thể tham khảo lại phần 1 của chuyên mục này tại Phần 1

Sự phụ thuộc (Dependencies) là gì?

Một đối tượng phụ thuộc vào một đối tượng khác khi nó bắt buộc phải thay đổi theo đối tượng kia. Chúng ta thử viết lại lớp Gear so sới phần trước (Phần 1). Lớp Gear được khởi tạo bởi 4 tham số :chainring, :cog, :rim, :tire. Phương thức gear_inches sử dụng hai tham số rim và tire để tạo một thể hiện của lớp Wheel. Trong đó lớp Wheel vẫn được giữ nguyên.

class ear
 attr_reader :chainring,:cog,:rim,:tire
 def initialize(chainring, cog, rim, tire)
     @chainring= chainring
     @cog = cog
     @rim = rim
     @tire = tire
 end

 def gear_inches
  ratio * Wheel.new(rim, tire).diameter
 end

 def ratio
  chainring / cog.to_f
 end
# ...
end

class Wheel
 attr_reader :rim,:tire

 def initialize(rim, tire)
  @rim = rim
  @tire = tire
 end

 def diameter
  rim + (tire * 2)
 end
# ...
end

Gear.new(52,11, 26, 2).gear_inches

Ví dụ trên thể hiện rất rõ việc lớp Gear sẽ phải thay đổi khi lớp Wheel thay đổi.

Nhận biết sự phụ thuộc

Một đối tượng bị phụ thuộc vào đối tượng khác khi:

  • Nó chứa tên của một lớp khác. Lớp Gear đã gọi tới lớp Wheel
  • Nó sử dụng một thể hiện của lớp khác.

Thử thách đặt ra để quản lý sự phụ thuộc lẫn nhau khi thiết kế là mỗi một lớp cần có ít nhất có thể sự phụ thuộc vào những lớp khác. Một lớp chỉ cần biết vừa đủ để có thể hoàn thành các nhiệm vụ của nó, không cần nhiều hơn.

Coupling Between Objects (CBO)

These dependencies couple Gear to Wheel. Alternatively, you could say that each coupling creates a dependency. The more Gear knows about Wheel, the more tightly coupled they are. The more tightly coupled two objects are, the more they behave like a single entity.

Tiếp theo bài viết này sẽ thảo luận về các loại phụ thuộc trên và tìm hiểu các kỹ thuật để tránh được những vấn đề chúng gây ra.

Mỗi sự phụ thuộc như một chất keo dính giúp cho class của bạn có thể liên kết tới các lớp khác. Chúng thực sự cần thiết. Tuy nhiên khi có quá nhiều chúng sẽ khiến cho chương trình của bạn cứng nhắc. Làm giảm thiểu sự phụ thuộc có nghĩa là sẽ nhận ra những sự không cần thiết và loại bỏ chúng khỏi chương trình.

Ví dụ sau đây sẽ trình bày một kỹ thuật giúp giảm thiểu sự phụ thuộc bởi cách chia nhỏ code.

Inject Dependencies

Việc tham chiếu đến một lớp khác bởi tên của nó sẽ tạo ra một sự phụ thuộc. Như trong code phía trên, phương thức gear_inches đã tham chiếu tới lớp Wheel:

def gear_inches
 ratio * Wheel.new(rim, tire).diameter
end

Như vậy khi chúng ta thay đổi tên của lớp Wheel thì phương thức gear_inches cũng cần phải thay đổi theo.

Lớp Gear hiện tại đang biết quá nhiều thứ không cần thiết. Lớp Gear không cần và cũng không nên biết về lớp Wheel khi cần thực thi phương thức gear_inches. Việc lớp Wheel được khởi tạo với hai tham số rim và tire nên diễn ra độc lập, cuối cùng chỉ cần cung cấp phương thức diameter cho phương thức gear_inches là được.

Dựa trên phân tích đó, chúng ta có thể viết lại lớp Gear như sau. Lớp Gear được khởi tạo với một đối tượng có thể trả về giá trị diameter:

class Gear
 attr_reader :chainring,:cog,:wheel
 def initialize(chainring, cog, wheel)
  @chainring= chainring
  @cog = cog
  @wheel = wheel
 end

 def gear_inches
  ratio * wheel.diameter
 end
# ...
end

# Gear expects a ‘Duck’ that knows ‘diameter’
Gear.new(52,11,Wheel.new(26, 1.5)).gear_inches

Lớp Gear giờ đây sử dụng biến @wheel và không cần biết chi tiết về lớp Wheel ngoại trừ một đối tượng có thể cung cấp giá trị diameter.

Kỹ thuật trên được biết với tên "Dependency injection". Lớp Gear đã không còn biết quá nhiều thông tin không cần thiết trong khi vẫn đảm bảo được nghiệp vụ đề ra.

Isolate Dependencies (Cô lập sự phụ thuộc)

Tốt nhất bạn nên phá vỡ tất cả các sự phụ thuộc. Tuy nhiên trong nhiều trường hợp nó là bất khả thi về mặt kỹ thuật. Vì vậy, trong những trường hợp đó bạn nên cô lập chúng với class của bạn.

Isolate Instance Creation (Cô lập việc tạo ra các thể hiện)

Nếu bạn không thể thay đổi code để chèn thêm lớp Wheel vào bên trong lớp Gear, bạn nên cô lập sự tạo ra một thể hiện mới của Wheel bên trong lớp Gear. Mục đích là để thể hiện rõ ràng sự phụ thuộc trong khi giảm sự kết nối với lớp Gear. Chúng ta cùng xem xét ví dụ sau:

class Gear
 attr_reader :chainring,:cog,:rim,:tire
 def initialize(chainring, cog, rim, tire)
  @chainring= chainring
  @cog = cog
  @rim = rim
  @tire = tire
 end

 def gear_inches
  ratio * wheel.diameter
 end
 def wheel
  @wheel|| = Wheel.new(rim, tire)
 end

Ví dụ trên đã giúp code sáng tỏ hơn khi làm rõ ràng sự phụ thuộc của phương thức gear_inches vào lớp Wheel. Khi chúng ta cần thay đổi wheel, nó sẽ được diễn ra trong phương thức wheel thay vì phương thức gear_inches như trước đó.

Managing Dependency Direction (Quản lý hướng phụ thuộc)

Tính phụ thuộc trong thiết kế luôn luôn có một hướng nhất định (class này phụ thuộc vào một class khác). Trong phần này chúng ta sẽ tìm hiểu sâu hơn nữa để quyết định hướng phụ thuộc nào trong thiết kế các đối tượng sẽ tốt nhất.

Reversing Dependencies (Đảo ngược sự phụ thuộc)

Các ví dụ đã được đề cập đều biểu diễn sự phụ thuộc của Gear vào Wheel hoặc diameter. Tuy nhiên chúng ta có thể viết lại code để Wheel sẽ phụ thuộc vào Gear hoặc ratio. Chúng ta cùng xem ví dụ sau:

class Gear
 attr_reader :chainring,:cog
 def initialize(chainring, cog)
  @chainring= chainring
  @cog = cog
 end

 def gear_inches diameter
  ratio * diameter
 end

 def ratio
  chainring / cog.to_f
 end
# ...
end

class Wheel
 attr_reader :rim,:tire,:gear
 def initialize(rim, tire, chainring, cog)
  @rim = rim
  @tire = tire
  @gear =Gear.new(chainring, cog)
 end

 def diameter
  rim + (tire * 2)
 end

 def gear_inches
  gear.gear_inches(diameter)
 end
# ...
end

Wheel.new(26, 1.5, 52, 11).gear_inches

Việc thay đổi code như trên không gây ảnh hưởng tới yêu cầu bài toán. Để thực hiện phương thức gear_inches vẫn cần sự kết hợp giữa Gear và Wheel và kết quả trả về không hề bị ảnh hưởng gì từ việc tái cấu trúc lại code.

Tóm lại chúng ta đã tìm hiểu thêm một vài khía cạnh của việc quản lý sự phụ thuộc trong thiết kế hướng đối tượng. Nó là việc rất cần thiết để có thể thiết kế được một nền tảng ứng dụng tốt. Việc bổ sung sự phụ thuộc sẽ tạo nên những cặp đối tượng rời rạc có thể được tái sử dụng theo một phương pháp khác. Bên cạnh đó, việc cô lập các sự phụ thuộc sẽ giúp cho các đối tượng có thể nhanh chóng thích nghi với những sự thay đổi không mong muốn. Chìa khóa để quản lý sự phụ thuộc là kiểm soát hướng của sự phụ thuộc đó.

Cảm ơn các bạn đã theo dõi bài viết này. Chúng ta sẽ cùng tìm hiểu sâu hơn về thiết kế hướng đối tượng trong phần 3.

0