23/12/2018, 23:14

SOLID và Ruby

I. SOLID là gì? SOLID là một cụm từ viết tắt của một bộ các nguyên tắc giúp lập trình viên tạo ra source code dễ bảo trì trong thời gian lâu dài, sử dụng ngôn ngữ lập trình hướng đối tượng. LƯU Ý: vì đây chỉ là một bộ các nguyên tắc, nên hiển nhiên nó sẽ không thay thế được suy nghĩ, tư duy ...

I. SOLID là gì?

  • SOLID là một cụm từ viết tắt của một bộ các nguyên tắc giúp lập trình viên tạo ra source code dễ bảo trì trong thời gian lâu dài, sử dụng ngôn ngữ lập trình hướng đối tượng.
  • LƯU Ý: vì đây chỉ là một bộ các nguyên tắc, nên hiển nhiên nó sẽ không thay thế được suy nghĩ, tư duy thông thường của người lập trình viên. Dưới đây là chi tiết từng nguyên tắc với ví dụ ngắn và dễ hiểu.
  • P/S: Theo duck typing design, Interface Segregation và Dependency Inversion sẽ không liên quan tới Ruby.
  • Trong bài viết này chúng ta sẽ cùng khám phá:
    • Single responsibility principle
    • Open/closed principle
    • Liskov substitution principle
    • Interface segregation principle
    • Dependency inversion principle

II. Các khái niệm trong SOLID

1. Single responsibility principle:

  • Nguyên tắc này dựa trên thực tế rằng một class chỉ nên chịu trách nhiệm về một khía cạnh duy nhất trong chương trình. Chịu trách nhiệm ở đây nghĩa là class chỉ có thể chịu tác động bởi một thay đổi cho một khía cạnh duy nhất trong chương trình.
class Logger
  def log(message)
    puts message
  end
end

class Authenticator
  def initialize
    @logger = Logger.new
  end

  def sign_in(profile)
    @logger.log("user #{profile.email}: signed in at <#{profile.signed_in_at}>"
  end
end
  • Ví dụ một thay đổi xảy ra trên đường tới log, tiến hành log lại vào 1 file thay vì 1 puts đơn giản – sau đó chỉ class Logger chịu tác động bởi nó là class duy nhất chịu trách nhiệm trong việc logging.

2. Open/closed principle:

  • Nguyên tắc này dựa trên thực tế một class chỉ nên mở ra cho việc mở rộng và đóng lại với việc chỉnh sửa.
class Collection < Array
  def print_collection
    # ...
  end
  
  def [](position)
    # ...
  end
end
  • Ở đây, class Array giữ nguyên không đổi khi class Collection – như một chuyên môn của Array – vẫn có các thuộc tính của một mảng. Vì thế pattern này cho phép bạn thêm vào một bộ các chức năng và sửa đổi các phương thức sẵn có của Array mà không phải tác động tới tất cả các instance của Array.

3. Liskov substitution principle:

  • Nguyên tắc này dựa trên sự thật rằng một class con có thể thay thế class cha.
class Role
  def to_s; 'default' end
end

class Admin < Role
  def to_s; 'admin' end
end

class User < Role
  def to_s; 'user' end
end

class RoleLogger
  def print_role(role)
    p "role: #{role}"
  end
end

logger = RoleLogger.new
logger.print_role(Role.new)  # => "role: default"
logger.print_role(Admin.new) # => "role: admin"
logger.print_role(User.new)  # => "role: user"
  • Ở ví dụ trên, phương thức RoleLogger#print_role nhận đối số role. Trong trường hợp này, các class Admin và User là con của class Role. Chúng ta có thể dễ dàng thế chỗ các class này cho nhau mà không làm hỏng kết quả kì vọng ban đầu của phương thức RoleLogger#print_role.

4. Interface segregation principle:

  • 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ể.
  • Nguyên lý này khá dễ hiểu. Hãy tưởng tượng chúng ta có 1 interface lớn, khoảng 100 methods. Việc implements sẽ khá cực khổ, ngoài ra còn có thể dư thừa vì 1 class không cần dùng hết 100 method. Khi tách interface ra thành nhiều interface nhỏ, gồm các method liên quan tới nhau, việc implement và quản lý sẽ dễ hơn.

5. Dependency inversion principle:

  • 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.
  • Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại (Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation).
  • Nguyên lý này khá lắt léo, mình sẽ lấy ví dụ thực tế. Chúng ta đều biết 2 loại đèn: đèn tròn và đèn huỳnh quang. Chúng cùng có đuôi tròn, do đó ta có thể thay thế đèn tròn bằng đèn huỳnh quang cho nhau 1 cách dễ dàng.
  • Ở đây, interface chính là đuôi tròn, implementation là bóng đèn tròn và bóng đèn huỳnh quang. Ta có thể swap dễ dàng giữa 2 loại bóng vì ổ điện chỉ quan tâm tới interface (đuôi tròn), không quan tâm tới implementation.
  • Trong code cũng vậy, khi áp dụng Dependency Inverse, ta chỉ cần quan tâm tới interface. Để kết nối tới database, ta chỉ cần gọi hàm Get, Save … của Interface IDataAccess. Khi thay database, ta chỉ cần thay implementation của interface này.

III. Kết luận

  • Trên đây là các định nghĩa cơ bản của SOLID và ứng dụng trong Ruby. Hi vọng bài viết có thể giúp các bạn có được cách nhìn tổng quan về SOLID và cách dùng trong Ruby.
  • Người dịch: Bùi Mạnh Cường
  • Nguồn: https://medium.com/@farsi_mehdi/solid-ruby-in-5-short-examples-353ea22f9b05
0