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

Tìm hiểu thiết kế hướng đối tượng trong Ruby on Rails (Phần IV) Tạo nên các interface mềm dẻo

I. Giới thiệu

Thật là đơn giản khi nghĩ các chương trình hướng đối tượng như là tập hợp của các lớp. Các lớp rất dễ dàng nhìn thấy, và các ý kiến về thiết kế thường xoay quanh các chức năng của lớp và những thành phần phụ thuộc. Các lớp chính là những thứ bạn thấy trên trình soạn thảo của bạn và những thứ bạn kiểm tra trong bộ mã nguồn của bạn.

Đó là một quan điểm về thiết kế phải được đáp ứng ở mức độ đó, nhưng với một ứng dụng hướng đối tượng thì không chỉ như vậy. Nó hình thành nên các lớp nhưng được định nghĩa bởi các thông điệp. Các lớp điều khiển những thứ nằm trong bộ mã nguồn của bạn, các thông điệp phản ánh sự hoạt động qua lại của ứng dụng.

Do đó, thiết kế phải quan tâm đến các thông điệp được gửi qua lại giữa các đối tượng. Nó chứa không chỉ các đối tượng biết những gì và biết được ai, mà cả cách các thông điệp trao đổi qua lại. Cuộc trò chuyện giữa các đối tượng diễn ra thông qua các interface của chúng, hay còn gọi lầ bề mặt của chúng, trong chương này, ta sẽ tìm hiểu cách tạo ra các interface mềm dẻo mà cho phép các ứng dụng có thể mở rộng và thay đổi.

II _ Xây dựng interface

Hiểu biết về interface:

Tưởng tượng rằng có 2 ứng dụng đang hoạt động, Mỗi ứng dụng chứa các đối tượng và các thông điệp trao đổi giữa chúng

Ta có 2 hình thức liên lạc:

  • Hình thức đầu tiên: tất cả các đối tượng đều đều có thông điệp trực tiếp đến đối tượng khác trong hệ thống.

  • Hình thức thứ 2 là các thông điệp được định nghĩa theo một khuôn mẫu rõ ràng. Các đối tượng liên lạc theo các cách cụ thể rõ ràng.

Các đối tượng trong hình thức đầu tiên sẽ khó khăn trong việc tận dụng lại. Mỗi một đối tượng thể hiện ra quá nhiều thông tin về nó và biết quá nhiều về hàng xóm của nó. Không có đối tượng nào đứng đơn lẻ, để sử dụng lại một vài thì ta cần phải sử dụng tất cả đối tượng, để thay đổi một đối tượng, bạn phải thay đổi tất cả.

Hình thức thứ hai là tập hợp các đối tượng có khả năng kết nối với nhau. Mỗi đối tượng tiết lộ rất ít về nó và biết rất ít về những đối tượng khác.

Thiết kế theo hình thức đầu tiên vi phạm nguyên tắc phụ thuộc hay chức năng duy nhất. Vấn đề không chỉ nằm ở mỗi lớp dùng để làm gì mà cả ở các lớp đấy tiết lộ những thông tin gì. Trong hình thức đầu tiên mỗi lớp tiết lộ tất cả. Mọi phương thức trong lớp có vị trí ngang bằng nhau và được tham chiếu bởi một vài đối tượng khác.

Kinh nghiệm chỉ ra rằng tất cả các phương thức trong một lớp không giống nhau, một vài thì tổng quát hơn, còn số khác thì dễ thay đổi hơn. Trong ứng đụng dầu tiên đã bỏ qua điều đó. Nó cho phép tất cả các phương thức của đối tượng được gọi từ các đối tượng khác.

Trong ứng dụng thứ 2, các khuôn mẫu thông diệp được cố định. Mỗi đối tượng có một tập rõ ràng các phương thức được trông chờ để sử dụng.

Các phương thức này tạo nên public interfact của lớp.

Định nghĩa interface

Hãy tưởng tượng về bếp của một nhà hàng. Các khách hàng gọi thức ăn qua một thực đơn. Những yêu cầu này được gửi đến bếp thông qua một cửa sổ nhỏ và thức ăn được gửi lên. Ban đầu, bạn có thể tưởng tưởng là căn bếp đã chứa sẵn những đĩa thức ăn rồi, và đang đợi để được yêu cầu, nhưng trên thực tế, căn bếp chỉ có người, nguyên liệu và mỗi khi có một yêu cầu món ăn thì sẽ bắt đầu một quá trính nấu mới.

Căn bếp thực hiện nhiều việc, nhưng không thể hiện ra tất cả cho khách hàng. Nó có một bộ mặt công khai _ public interface, mà các khách hàng mong muốn sử dụng, đó là thực đơn. Trong khi đó bên trong căn bếp có rất nhiều việc xảy ra, nhiều thông điệp khác được thực hiện, nhưng những thông điệp đó lại là riêng tư và do đó vô hình với khách hàng. Mặc dù khách hàng có thể yêu cầu, nhưng không được chào đón để đi vào và làm đảo món súp.

Có sự phân biệt rõ ràng giữa công khai và riêng tư bởi vì nó là cách hiệu quả nhất để kinh doanh. Nếu khác hàng muốn nấu ăn, họ phải được đào tạo lại bất cứ khi nào bếp hết một công thức nấu ăn thì nó cần phải được thay thế lại. Sử dụng một thực đơn sẽ tránh khỏi vấn đề này bằng cách chỉ cho phép khách hàng hỏi về họ muốn gì mà không cần biết mọi thứ về cách mà chúng được tạo ra như thế nào.

Mỗi lớp của bạn là một căn bếp. Lớp tồn tại để thực hiện một chức năng duy nhất nhưng thực hiện nhiều phương thức. Những phương thức này gồm nhiều kiểu, mục đích, phạm vi, những hàm tổng quát thể hiện chức năng chính của lớp và sử dụng những hàm công cụ được định nghĩa để dùng ở bên trong lớp. Một vài hàm sẽ đại hiện cho thực đơn của lớp và nên là công khai _ public, những loại khách với mục đích thực hiện chức năng bên trong lớp sẽ là private.

Public interface:

Những phương thức nằm trong public interface _ bộ mặt công khai của lớp chính là thể hiện một bộ mặt cho thế giới. Chúng sẽ:

  • Tiết lộ chức năng cơ bản.

  • Được gọi bởi các phương thức khác.

  • Sẽ không được thay đổi tùy ý.

  • An toàn với những phương thức phụ thuộc vào nó.

  • Được kiểm tra kỹ càng.

Private interface:

Tất cả những phương thức khác trong lớp là private interface _ bộ mặt riêng, của lớp. Chúng sẽ:

  • Nắm giữ các quy trình thực thi.

  • Được được gọi bởi các phương thức khác.

  • Có thể thay đổi bởi một vài lý do nào đó.

  • Không an toàn với các phương thức phụ thuộc.

  • Có thể không được kiểm tra.

Responsibilites, Dependencies và Interfaces

Trong chương 2 thiết kế lớp với một mục đích duy nhất đã xây dựng một lớp chỉ với một trách nhiệm duy nhất. Nếu bạn nghĩ về một lớp chỉ có một chức năng duy nhất, sau đó suy nghĩ đến cái gì để nó thực hiện mục đích đấy. Các phương thức công khai nên giống như một bản mô tả mục đích đó. Giao diện công khai _ public interface, là một bản hợp đồng phân rõ trách nhiệm của lớp.

Trong chương 3, quản lý các sự phụ thuộc, thì các thông điệp của một lớp nên chỉ phụ thuộc vào các lớp mà ít thay đổi. Bây giờ khi chia mọi lớp thành phần công khai và phần riêng tư thì ý kiến đó phụ thuộc vào nhưng thứ thay đổi ít nhất có thể.

Phần công khai của một lớp là phần cố định, phần riêng là phần có thể thay đổi. Khi bạn đánh dấu một hàm là public hay private nghĩa là bạn nói với người dùng lớp này của bạn là nó có thể phụ thuộc một cách an toàn. Khi các lớp của bạn sử dụng các phương thức public của lớp khác, bạn tin tương vào các phương thức này là ổn định. Khi bạn quyết định phụ thuộc vào các hàm private của lớp khác, bạn hiểu là bạn đang dựa vào một vài thứ không được ổn định và do đó tăng khả năng bị ảnh hưởng bởi các sự thay đổi không liên quan.

Tìm kiếm các giao diện công khai:

Tìm kiếm và định nghĩa các giao diện công khai là một nghệ thuật. Nó là một thử thách rõ ràng vì không có một công thức cụ thể. Có nhiều cách để tạo ra một interface tốt và hậu quả của một interface tồi có thể không thể hiện ngay.

Mục tiêu thiết kế là đảm bảo sự mềm dẻo tối đa cho tương lai trong khi chỉ cần viết đủ dòng code để đáp ứng với yêu cầu hiện tại. Các public interface tốt sẽ giảm bớt chi phí thay đổi không cần thiết, còn public interface tồi sẽ chỉ làm tăng nó lên.

Một ví dụ: công ty tổ chức du lịch xe đạp

Một công ty tổ chức du lịch bằng xe đạp cho cả các chuyến đi đường hay leo núi. Họ thực hiện ý tưởng này thông qua một hệ thống giấy tờ và thông thường không tự động.

Mỗi chuyến đi có một lộ trình cụ thể và có thể diễn ra vài lần trong năm, và giới hạn số lượng khách tham gia và yêu cầu một số người hướng dẫn.

Mỗi chuyến sẽ được tính thông qua độ khó. Các chuyến đạp xe leo núi sẽ có thêm tỉ lệ độ khó về kỹ thuật. Các khách hàng có các mức yêu cầu về ngoại hình và mức độ kinh nghiệm leo núi mà quyết định chuyến đi có hay không.

Khách hàng có thể thuê xe hay mang xe họ đi. Công ty có hệ thống xe để cho mượn hay các xe thuê ở cửa hàng xe đạp. Các xe thuê có nhiều kích thước và phù hợp với đường giao thông hay đường đồi núi.

Với các yêu cầu đó, chúng ta có một use case: Một khác hàng, để chọn ra một chuyến đi, cần phải xem một danh sách các chuyến đi phù hợp với các độ khó tương ứng, vào một ngày cụ thể và có thể thuê xe.

Xây dưng ý tưởng:

Dựa theo mô tả trên, bạn có thể hình dung các lớp có thể tồn tại trong chương trình này. Đó là Customer, Trip, Route, Bike, và Mechanic.

Những lớp này chứa các danh từ trong chương trình bao gồm cả dữ liệu và các hành vi. Đó là các domain objects.

Các đối tượng chính này dễ dàng để nhận ra nhưng chúng không là trọng tâm thiết kế. đó là một cái bẫy nếu không cẩn thận. Những chuyên gia thiết kế chú ý đến chúng nhưng không tập trung vào chúng, họ tập trung không chỉ những đối tượng này mà là đến các thông điệp qua lại giữa chúng. Những thông điệp này sẽ dẫn bạn tìm ra các đối tượng cần thiết.

Do đó trước khi viết code, bạn nên hình dung về các đối tượng và các thông điệp cần thiết cho use case này.

Sử dụng mô hình chuỗi _ sequence diagram:

Có một cách đơn giản, hoàn hảo để thử nghiệm các đối tượng và các thông điệp đó là mô hình các chuỗi _ sequence diagram.

Sequen diagram được định nghĩa trong UML _ Unified Modeling Language. UML là một công cụ tuyệt vời cho thiết kế hướng đối tượng. Các mô hình UML cung cấp các cách nhìn nhận hiệu quả để xây dựng các thiết kế một cách thích hợp. Chúng ta có thể dễ dàng thử nghiệm các cách sắp xếp khác nhau và các thông điệp qua lại giữa chúng, cung cấp một phương tiện để kết nối và liên lạc giữa các ý tưởng.

Viết code theo các interface:

Sự rõ ràng của các interface tiết lộ khả năng thiết kế của bạn và phản ánh sự kỷ luật của bạn. Bởi vì kỹ năng thiết kế luôn phải được hoàn thiện và không bao giờ là hoàn hảo, bởi vì một thiết kế hoàn hảo tại thời điểm này có thể trở nên tồi tệ với các yêu cầu trong tương lai, thật khó để tạo ra các thiết kế hoàn chỉnh.

Tuy nhiên bạn không cần phải quan tâm quá mức tới điều đó, chỉ cần có một thiết kế được định nghĩa rõ ràng mà thôi.

Tạo ra các interface rõ ràng:

Mục đích của bạn viết code ngày hôm nay là có thể dễ dàng mở rộng hay dùng lại trong tương lai. Những người khác có thể sẽ dùng lại hay gọi các hàm của bạn.

Mỗi khi bạn tạo một lớp, hãy xác định interface của nó. Các phương thức trong public interface nên thỏa mãn:

  • Được xác định rõ ràng

  • Chú trọng đến để làm gì hơn là làm như thế nào.

  • Có tên gọi cố định không đổi

  • Nhận một hash như một tham số option

Ruby cung cấp 3 từ khóa liên quan là public, protected, và private.

Sử dụng những từ khóa này với 2 mục đích phân biệt:

  • Thứ nhất chúng chỉ ra những phương thức nào là ổn định và không ổn định.

  • Thứ hai chúng điều khiển một phương thức ẩn hiện đối với các phần khác của chương trình.

Trân trọng các public interface của các lớp:

Bạn đã từng tương tác với các lớp khác chỉ sử dụng các public interface. Thực tế là tác giả của các lớp không ở gần nhau, tách biệt nhau, họ cố gắng liên hệ với các phương thức phụ thuộc. Sự phân biệt rõ ràng public và private họ đưa ra chỉ với mong muốn giúp bạn

Nếu thiết kế của bạn bắt buộc phải sử dụng một hàm private trong lớp khác, hãy suy nghĩ lại ngay lập tức. Nó sẽ dẫn tới một sự thay đổi, bạn nên cố gắng để tìm hướng khác.

Khi bạn phụ thuộc vào một pirvate interface, bạn tăng nguy cơ bắt buộc phải thay đổi. Khi private interface là một phần của framework bên ngoài hệ thống mà đã được phát hành, sự phụ thuộc đó như một quả bom hẹn giờ sẽ gây ra một hệ quả tồi tệ. Khi framework đấy được nâng cấp, các hàm private sẽ thay đổi, và chương trình sẽ bị phá vỡ.

Phụ thuộc vào một hàm private của một framework bên ngoài là một dạng nợ nần. Hãy tránh xa những điều đó.

Thực hiện các lưu ý khi phụ thuộc vào private interface:

Mặc dù bạn rất cố gắng, nhưng vẫn bắt buộc phải phụ thuộc vào một private interface. Đó là một sự phụ thuộc rất nguy hiểm và nó nên được cô lập lại bằng các kỹ thuật được mô tả trong chương 3. Thậm chí nếu bạn không thể tránh khỏi việc sử dụng một phương thức private, bạn có thể ngăn chặn hàm đó được tham chiếu từ nhiều nơi trong chương trình của bạn. Khi phụ thuộc vào một private interface, nó sẽ làm tăng độ rủi ro, hãy giữ độ rủi ro đấy ở mức nhỏ nhất.

III Kết luận

Các chương trình hướng đối tượng được định nghĩa bởi các thông điệp thực hiện qua lại giữa các đối tượng. Các thông điệp này diễn ra trong các public interface, những public interface được định nghĩa rõ ràng sẽ bao gồm các phương thức ổn định thể hiện các chức năng của lớp và cung cấp lợi ích tối đa cho việc giảm chi phí.

Tập trung vào các thông điệp cho phép các đối tượng có thể được giám sát. Khi các thông điệp được tin tưởng và yêu cầu người gửi mong muốn gì thay vì kể với người nhận cách để giải quyết, các đối tượng sẽ cung cấp các public interface mêm dẻo và có thể tận dụng một cách dễ dàng theo những cách không mong muốn.

Trên đây là cách thức để xây dựng các interface một cách mềm dẻo

Hẹn gặp lại trong phần tới ta sẽ tìm hiểu về cách giảm bớt chi phí với duck typing

Tài liệu tham khảo:

Pratical Object-oriented design in ruby