- vừa được xem lúc

SOLID là gì ? Áp dụng các nguyên lý SOLID trong thiết kế

0 1 34

Người đăng: Duy Hoai Tang

Theo Viblo Asia

SOLID là viết tắt của 5 chữ cái đầu trong 5 nguyên tắc thiết kế hướng đối tượng, giúp cho developer viết ra những đoạn code dễ đọc, dễ hiểu, dễ maintain, được đưa ra bởi Bob Martin và Michael Feathers. Việc theo sát 5 nguyên tắc này nói thì để đáp ứng cả 5 nguyên tắc e là điều không đơn giản. 5 nguyên tắc đó bao gồm:

  • Single responsibility priciple (SRP)
  • Open/Closed principle (OCP)
  • Liskov substitution principe (LSP)
  • Interface segregation principle (ISP)
  • Dependency inversion principle (DIP) Trong bài viết này mình sẽ giới thiệu từng nguyên tắc trong 5 nguyên tắc trên cũng như cách áp dụng nó làm tăng chất lượng code trong Ruby

Single responsibility priciple

Nguyên lý đầu tiên ứng với chữ S trong SOLID, có ý nghĩa là một class chỉ nên giữ một trách nhiệm duy nhất. Một class có quá nhiều chức năng sẽ trở nên cồng kềnh và trở nên khó đọc, khó maintain. Mà đối với ngành IT việc requirement thay đổi, cần thêm sửa chức năng là rất bình thường, nên việc code trong sáng, dễ đọc dễ hiểu là rất cần thiết. Để hiểu rõ hơn, ta cũng soi vào đoạn code vi phạm nguyên tắc này:

class DealProcessor def initialize(deals) @deals = deals end def process @deals.each do |deal| Commission.create(deal: deal, amount: calculate_commission) mark_deal_processed end end private def mark_deal_processed @deal.processed = true @deal.save! end def calculate_commission @deal.dollar_amount * 0.05 end
end

Class này ngoài việc đánh dấu giao dịch đã được xử lý, còn thực hiện tính hoa hồng cho mỗi giao dịch nữa. Ngoài ra sau này còn có thể thêm chức năng ví dụ như là gửi mail nội dung cụ thể về hoa hồng cho người có liên quan, gửi thông báo v.v.. dẫn đến việc class này làm nhiều hơn 1 nhiệm vụ và đã vi phạm nguyên tắc đơn trách nhiệm trong SOLID. Có thể refactor lại như sau:

class DealProcessor def initialize(deals) @deals = deals end def process @deals.each do |deal| mark_deal_processed CommissionCalculator.new.create_commission(deal) end end private def mark_deal_processed @deal.processed = true @deal.save! end
end class CommissionCalculator def create_commission(deal) Commission.create(deal: deal, amount: deal.dollar_amount * 0.05) end
end

Giờ thì chúng ta đã có 2 class nhỏ hơn thực hiện 2 nhiệm vụ riêng biệt: một processor chiệu trách nhiệm lưu lại việc xử lý và 1 calculator chịu trách nhiệm tính toán và tạo ra các dữ liệu liên quan tới tiền hoa hồng.

Open/Closed principle

Nguyên lý thứ 2 ứng với chữ O trong SOLID. Nội dung Có thể thoải mái mở rộng 1 class nhưng không được sửa đổi bên trong class đó (open for extension but closed for modification) Ta có đoạn code sau:

class Report def body generate_reporty_stuff end def print body.to_json end
end

Đoạn code trên vi phạm OCP bởi nếu ta muốn thay đổi định dạng của report được print ra, ta sẽ cần phải sửa đổi code của class. Refactor lại như sau:

class Report def body generate_reporty_stuff end def print(formatter: JSONFormatter.new) formatter.format body end
end

Làm theo cách này thì ta vừa mở rộng tính năng mà khi thay đổi format sẽ không cần phải thay đổi code

report = Report.new
report.print(formatter: XMLFormatter.new)

Liskov substitution principle

Nguyên tắc thứ 3, ứng với chữ L trong SOLID. Nội dung nguyên tắc này được phát biểu như sau: Bất cứ instance nào của class cha cũng có thể được thay thế bởi instance của class con của nó mà không làm thay đổi tính đúng đắn của chương trình Hãy cùng xem ví dụ về việc vi phạm nguyên tắc Liskov dưới đây

class Rectangle def set_height(height) @height = height end def set_width(width) @width = width end
end class Square < Rectangle def set_height(height) super(height) @width = height end def set_width(width) super(width) @height = width end
end

Hình vuông (Square) là con của hình chữ nhật (Rectangle), vì vậy để hợp lý hóa, khi ta gọi method set_height hay set_width của Square thì đều phải modify cả @width@height. Ở đây việc ứng dụng tính đa hình chưa đúng có thể gây hiểu lầm, ví dụ trong trường hợp iterate qua 1 collection của Rectangle, mà trong đó lại có 1 instance của class Square, việc gọi method set_height nhưng lại gây ra hậu quả không mong muốn là thay đổi cả width. Một ví dụ điển hình của việc vi phạm nguyên tắc Liskov đó là throw exception trong overridden method ở class con.

Interface segregation principle

Nguyên tắc thứ 4 ứng với chữ I trong SOLID, nội dung nguyên tắc này như sau: Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể Client không nên phụ thuộc vào interface mà nó không sử dụng. Nguyên tắc này tương đối dể hiểu, thay vì gộp hết lại trong 1 interface lớn, ta có thể chia nhỏ thành nhiều interface nhỏ hơn gồm các method liên quan tới nhau, như vậy sẽ dễ quản lý hơn.

class Car def open end def start_engine end def change_engine end
end class Driver def drive @car.open @car.start_engine end
end class Mechanic def do_stuff @car.change_engine end
end

Trong đoạn code trên, class Car có một interface được sử riêng rẽ bởi cả DriverMechanic. Ta có thể refactor lại code như sau:

class Car def open end def start_engine end
end class CarInternals def change_engine end
end class Driver def drive @car.open @car.start_engine end
end class Mechanic def do_stuff @car_internals.change_engine end
end

Bằng việc tách ra là 2 interface nhỏ hơn, code ta đã đảm bảo tuân thủ nguyên tắc ISP

Dependency inversion principle

Nguyên tắc thứ 5 ứng với chữ D trong SOLID, nội dung nguyên tắc này như sau: 1. Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào abstraction. 2. Abstraction không nên phụ thuộc vào chi tiết, mà ngược lại. Chúng ta hãy cùng quay trở lại với ví dụ đưa ra ở mục 2, nguyên tắc đóng/mở, tuy nhiên sẽ có một chút thay đổi:

class Report def body generate_reporty_stuff end def print JSONFormatter.new.format body end
end class JSONFormatter def format body ... end
end

Bây giờ chúng ta đã có một lớp để thực hiện việc định dạng in ra của báo cáo. Tuy nhiên, dễ thấy rằng code trong method print của class Report vẫn đang được fix cứng, từ đó sẽ tạo nên sự phụ thuộc của class này vào class JSONFormatter. Vì Report là class trừu tượng ở mức cao hơn so với JSONFormatter, cách viết này đang vi phạm nguyên tắc đảo ngược dependency.

Có thể giải quyết vấn đề này tương tự như cách chúng ta đã thực hiện với nguyên tắc đóng/mở, bằng cách sử dụng kỹ thuật dependency injection:

class Report def body generate_reporty_stuff end def print formatter: JSONFormatter.new formatter.format body end
end

Sau khi được refactor, class Report đã không phụ thuộc vào class JSONFormatter và có thể sử dụng bất kỳ định dạng nào mà có định nghĩa method format.

KẾT LUẬN

Trên đây là bài giới thiêu sơ qua về 5 nguyên tắc SOLID trong việc design. Hi vọng bài viết phần nào có ích trong công việc của các bạn.

Tài liệu tham khảo

  1. (https://subvisual.co/blog/posts/19-solid-principles-in-ruby/)[https://subvisual.co/blog/posts/19-solid-principles-in-ruby/]

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 500

- vừa được xem lúc

Cài đặt WSL / WSL2 trên Windows 10 để code như trên Ubuntu

Sau vài ba năm mình chuyển qua code trên Ubuntu thì thật không thể phủ nhận rằng mình đã yêu em nó. Cá nhân mình sử dụng Ubuntu để code web thì thật là tuyệt vời.

0 0 374

- vừa được xem lúc

Đặt tên commit message sao cho "tình nghĩa anh em chắc chắn bền lâu"????

. Lời mở đầu. .

1 1 701

- vừa được xem lúc

Tìm hiểu về Resource Controller trong Laravel

Giới thiệu. Trong laravel, việc sử dụng các route post, get, group để gọi đến 1 action của Controller đã là quá quen đối với các bạn sử dụng framework này.

0 0 335

- vừa được xem lúc

Phân quyền đơn giản với package Laravel permission

Như các bạn đã biết, phân quyền trong một ứng dụng là một phần không thể thiếu trong việc phát triển phần mềm, dù đó là ứng dụng web hay là mobile. Vậy nên, hôm nay mình sẽ giới thiệu một package có thể giúp các bạn phân quyền nhanh và đơn giản trong một website được viết bằng PHP với framework là L

0 0 421

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 414