0

Tìm hiểu thiết kế hướng đối tượng trong Rails Phần 2

Tìm hiểu thiết kế hướng đối tượng trong Ruby on Rails (Phần II)

I. Giới thiệu

Trong phần trước đã giới thiệu qua về thiết kế hướng đối tượng. Trong bài viết này, chúng ta sẽ tìm hiều một vài quy tắc trong thiết kế, đó là: Thiết kế class với chỉ mục đích duy nhất

II. Nguyên tắc 1: Thiết kế lớp_class chỉ với mục chức năng duy nhất

Các biện pháp để giải quyết các vấn đề trong thiết kế Một hệ thống hướng đối tượng là tập các thông điệp _ message, nhưng cấu trúc dễ nhận ra nhất là các lớp _ class. Các thông điệp là phần cốt lõi của thiết kế, nhưng trong chương này, ta sẽ tập trung vào những thành phần nên có trong một lớp. Từ đó ta đặt ra các câu hỏi là:

  • Lớp mục đích để làm gì?
  • Nên có bao nhiêu lớp?
  • Chúng sẽ thực hiện các chức năng gì?
  • Các lớp đấy sẽ được giao tiếp với nhau như thế nào?
  • Chúng thể hiện những gì ra bên ngoài?

Mọi quyết định đều luôn đi kèm với nguy hiểm. Đừng sợ nó. Trong hoàn cảnh đấy, hành động đầu tiên của bạn là hít thật sau và đảm bảo là suy nghĩ đơn giản. Mục đích của bạn là tạo model cho ứng dụng, sử dụng các lớp, để nó có thể đảm bảo thực hiện ngay bây giờ và cũng dễ dàng để thay đổi về sau.

Có 2 tình huống khác nhau. Ai đó có thể sắp xếp code để tạo ra chương trình hoạt động ngay lập tức. Các ứng dụng ngày nay hiện đang đi theo hướng đấy. Điều đó tùy thuộc vào bản thân bạn.

Tuy nhiên tạo ra một ứng dụng dễ dàng để thay đổi là một vấn đề khó khăn hơn. Ứng dụng của bạn cần hoạt động ngay lập tức, nó phải dễ dàng thay đổi về sau. Tiêu chuẩn dễ thay đổi đó thuộc phạm trù lập trình khéo léo. Đạt được điều đó có thể nâng cao kiến thức, kỹ năng và một ít sức sáng tạo nghệ thuật

Thật may mắn, bạn không cần phải bắt đầu từ những điều vụn vặt. Đã có rất nhiều nghiên cứu và ý kiến về vấn đề định hình chất lượng để tạo ra một ứng dụng dễ dàng thay đổi. Các kỹ thuật này là đơn giản, bạn chỉ cần biết chúng là gì và sử dụng như thế nào.

Quyết định những thứ nên nằm trong một lớp

Bạn có một ứng dụng trong đầu. Bạn biết làm như thế nào. Bạn thậm chí có thể nghĩ cách thức hiện chức năng quan trong nhất của ý tưởng đó. Vấn đề không phải nằm ở kiến thức kỹ thuật mà là ở cách tổ chức, bạn biết cách viết code nhưng không biết đặt ở đâu.

Nhóm các hàm vào trong các lớp

Trong ngôn ngữ hương đối tượng dựa vào lớp giống như Ruby, các hàm được định nghĩa trong một lớp. Các lớp bạn tạo ra sẽ ảnh hưởng cách bạn suy nghĩ về ứng dụng sau này. Chúng định nghĩa một thế giới ảo mà làm hạn chế khả năng tưởng tượng của mọi người. Bạn đang xây một hộp mà có thể rất khó để suy nghĩ vượt ra ngoài nó.

Mặc dù hiều rõ tầm quan trọng của nhóm các hàm vào lớp một cách chính xác, tại thời điểm bắt đầu của dự án, bạn có thể không chú ý đến nó. Bạn sẽ không bao giờ biết ít hơn những gì bạn biết hiện nay. Nếu ứng dụng của bạn thành công, nhiều quyết định ngày hôm nay có thể sẽ được thay đổi về sau. Khi ngày đấy đến, khả năng đế đáp ứng các thay đổi đấy phụ thuộc hoàn toàn vào thiết kế của ứng dụng.

Thiết kế thiên về nghệ thuật quản lý thay đổi hơn là đạt được độ hoàn hảo.

Tổ chức code dựa theo các thay đổi

Nếu bạn định nghĩa khả năng thay đổi dễ dàng là:

  • Sự thay đổi xuất phát từ các ảnh hưởng không mong muốn.

  • Một vài sự thay đổi nhỏ trong yêu cầu dự án yêu cầu phải có một vài thay đổi nhỏ trong code tương ứng.

  • Các code có sẵn dễ dàng để tái sử dụng.

  • Cách đơn giản nhất để tạo một thay đổi là thêm code mà có khả năng tự thay đổi.

Sau đó, code bạn viết nên dựa theo các tiêu chuẩn. Code nên là:

  • Rõ ràng: dấu hiệu của thay đổi nên thể hiện rõ ràng trong code và các đoạn code liên quan đến thay đổi đó.

  • Hợp lý: ảnh hưởng của thay đổi nên tỉ lệ với lợi ích mà thay đổi đạt được.

  • Tái sử dụng: các đoạn code có sẵn nên tái sử dụng trong các bối cảnh mới và không mong muốn.

  • Mẫu: các đoạn code chính nó nên khuyến khích những người dự định thay đổi nó đảm bảo các tiêu chuẩn đấy.

Code dựa theo các đặc tính Rõ ràng, hợp lý, tái sử dụng, mẫu (TRUE) không chỉ đảm bảo cho sự cần thiết hiện tại mà còn đảm bảo cho sự thay đổi có thể có trong tương lai. Bước đầu tiên để xây dựng code theo chuẩn TRUE là đảm bảo cho mỗi lớp có một nhiệm vụ riêng duy nhất và được định nghĩa rõ ràng.

Tạo các lớp có một nhiệm vụ duy nhất

Một lớp nên thực hiện một vấn đề hữu ích nhỏ nhất có thể, nó nên chỉ có duy nhất thực hiện một chức năng.

Hãy hình dung cách tạo một lớp mà chỉ có một chức năng thông qua ví dụ sau.

Xe đạp và bánh răng:

Xe đạp là một cỗ mãy hiệu quả tuyệt vời, bởi vì chúng sử dụng các bánh răng cung cấp cho con người khả năng thuận tiện. Khi đạp xe, bạn có thể chọn giữa bánh răng nhỏ (cái mà có thể dễ dàng đạp nhưng không được nhanh) hay một bánh răng to (cái mà khó đạp hơn nhưng có thể đưa bạn đi xa hơn). Các bánh răng thật tuyệt bởi vì bạn có thể sử dụng các loại nhỏ để leo núi và loại to để đi nhanh.

Các bánh răng hoạt động bằng cách thay đổi quãng đường của xe mỗi lần bạn hoàn thành một vòng của bản đạp. Cụ thể hơn, bánh răng điều khiển bao nhiêu lần bánh xe quay theo mỗi lần bàn đạp quay. Với một bánh răng nhỏ vòng quay của chân bạn chỉ cần ít là có thể làm cho bánh xe quay ngay lập tức, với một bánh răng lớn, mỗi lần quay bàn đạp có thể làm cho các bánh xe quay nhiều lần.

Thuật ngữ nhỏ và lớn chưa được chính xác lắm. So sanh với các bánh răng khác nhau, người lái xe sử dụng tỉ lệ của các răng. Tỉ lệ này có thể được tính với mã Ruby đơn giản:

chainring = 52
cog = 11
ratio = chainring / cog.to_f
puts ratio    # -> 4.72727272727273

chainring = 30
cog = 27
ratio = chainring / cog.to_f
puts ratio   # -> 1.11111111111111

Bánh răng được hình thành từ 52 răng chainring với 11 răng cog có tỉ lệ là 4.73. Mỗi lần chân bạn đạp bàn đạp 1 lần, bánh xe sẽ chuyển động nhiều hơn 5 lần. 30 x 27 là một bánh răng dễ dùng hơn, mỗi vòng bàn đạp làm cho bánh răng quay lâu hơn.

Bạn có thể thực hiện một ứng dụng Ruby để tính tỉ lệ bánh răng:

Ứng dụng này sẽ tạo ra các lớp Ruby, mỗi lần thực hiện một vài yêu cầu nào đó. Nếu bạn đọc qua mô tả trên, tìm các dạnh từ miêu tả các đối tượng, bạn sẽ thấy các từ như xe đạp và bánh răng. Những danh từ này đại diện cho các thành phần đơn giản nhất của lớp. Có thể nói là xe đạp nên là một lớp, nhưng không có bất kỳ mô tả nào về cách cư xử của xe đạp, nó không hiệu quả cho lắm. Tuy nhiên bánh răng bao gồm chainrings, cogs, ratios, những nhân tố đấy có chứa dữ liệu và cách cư xử. Ta có thể xây dựng một lớp về Bánh răng (Gear):

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

  def ratio
    chainring / cog.to_f
  end
end
puts Gear.new(52, 11).ratio   # -> 4.72727272727273
puts Gear.new(30, 27).ratio   # -> 1.11111111111111

Lớp Gear thực sự rất đơn giản. Bạn tạo một đối tượng của Gear bằng cách cung cấp sô răng cho chainring và cog. Mỗi đối tượng thực hiện 3 hàm là chainring, cog và ratio

Gear là lớp con của Object và do đó thừa kế nhiều hàm khác nữa. Một Gear bao gồm mọi thứ mà nó thừa kế trục tiếp cộng với những thứ mà nó định nghĩa, vì vậy tạo ra một tập các hành vi, hay tập các thể hiện, do đó tạo nên tập hợp các thông điệp mà nó có thể phản hồi.

Bạn đưa cho khả năng tính toán của Gear cho một người bạn lái xe và cô đấy thấy nó rất hữu dụng và ngay lập tức hỏi mượn. Cô ấy có 2 xe đạp, chúng thực sự có các bánh răng giống nhau nhưng khác nhau về kích thước bánh xe. Cô ấy mong muốn bạn cũng có thể tính toán sự khác nhau của các bánh xe.

Một chiếc xe với bánh lớn có thể di chuyển xe hơn trong mỗi vòng quay của bánh hơn là một bánh xe nhỏ.

Người lái xe sử dụng một vài thứ là gear inches để so sánh các xe mà khác nhau về cả bánh răng (gearing) và kích thước bánh xe. Ta có công thức:

gear inches = wheel diameter * gear ratio

với wheel diameter = rim diameter + twice tire diameter.

Bạn có thể thêm vào lớp Gear một hành vi mới:

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

  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    ratio * (rim + (tire * 2))
  end
end

puts Gear.new(52, 11, 26, 1.5).gear_inches

# -> 137.090909090909

puts Gear.new(52, 11, 24, 1.25).gear_inches

# -> 125.272727272727

Tại sao phải thực hiện nhiệm vụ duy nhất cho mỗi class

Các ứng dụng mà dễ thay đổi bao gồm các lớp dễ dàng tái sử dụng. Một ứng dụng mà dễ thay đổi giống như một tòa nhà hình hộp, bạn có thể lựa chọn các mảnh bạn cần và tập hợp chúng theo bất kỳ cách nào.

Một lớp có nhiều hơn một nhiệm vụ sẽ khó để tái sử dụng. Đa dạng các chức năng giống như các lớp vướng vào nhau. Nếu bạn muốn tận dụng một vài hành vì của lớp (không phải tất cả), điều đó là không thể để lấy những thứ bạn cần.

Nếu nhiệm vụ bị gộp lại, bạn không thể sử dụng hành vi mà bạn cần, điều đó làm cho bạn có thể lặp lại đoạn code đấy. Thật là ý tưởng tồi tệ. Code bị lặp làm tăng thời gian bảo trì và số lượng bug. Nếu lớp được thiết kế để bạn có thể tiếp cận chỉ duy nhất chức năng bạn cần, bạn có thể tái sử dụng toàn bộ lớp.

Bởi vì lớp bạn sử dụng lại bị lẫn lộn giữa mục đích nó là gì và chứa những trách nhiệm đan xem với nhau, nó có nhiều lsy do để thay đổi. Nó có thể thay đổi lsy do mà không liên quan đến bạn dùng chúng như thế nào, và mỗi lần thay đổi, nó sẽ phá vỡ mọi lớp phụ thuộc vào nó. Bạn tăng khả năng thay đổi của ứng dụng thì càng tăng sự kết nối không cần thiết nếu các lớp phụ thuộc nhau quá nhiều.

III. Summary

Trên đây là bước đầu tiên của quá trình thiết kế dựa theo hướng đối tượng: xây dựng các lớp chỉ với một chức năng duy nhất

Hẹn gặp lại trong phần tới sẽ đi sâu hơn về các nguyên tắc và vận dụng chúng trong xây dựng phần mềm với Rails.

Tài liệu tham khảo:

Pratical Object-oriented design in ruby


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí