Phân tích thiết kế hướng đối tượng trong Ruby - Phần 2
Bài đăng này đã không được cập nhật trong 8 năm
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.
All rights reserved