Proxy Pattern trong Ruby

Mục đích của Proxy pattern

Đầu tiên, việc hình thành ra các design patterns là một "phát minh" lớn đối với các developer, bởi nó cung cấp chuẩn hóa cho việc giải quyết các vấn đề. Và như mọi người cũng có đọc qua thì quyển sách Gang of four là quyển sách đầu tiên đưa ra các khái niệm về design patterns. Mình cũng có tìm hiểu và hôm nay xin phép được tổng hợp lại các kiến thức mình đã tìm hiểu được trên mạng về 1 patterns đó chính là Proxy patterns trong ruby.
Theo Gof (Gang of four) mục đích của proxy patterns là: "Provide a surrogate or placeholder for another object to control access to it."
Tạm dịch thì mọi người có thể hiểu đại khái là muốn tạo ra một đối tượng sẽ ủy quyền, thay thế cho một đối tượng khác. Hay Proxy Pattern là mẫu thiết kế mà ở đó tất cả các truy cập trực tiếp một đối tượng nào đó sẽ được chuyển hướng vào một đối tượng trung gian (Proxy Class). Nếu như Factory Pattern giúp quản lý đối tượng tốt hơn thì Proxy Pattern có nhiệm vụ bảo vệ việc truy cập một đối tượng thông qua Proxy, hay còn gọi là truy cập gián tiếp. Proxy Pattern được sử dụng khi bạn muốn đơn giản một đối tượng phức tạp. Proxy được ủy quyền về phía ứng dụng khách cho phép tương tác với đối tượng đích theo những cách khác nhau, như gửi yêu cầu một dịch vụ nào đó, theo dõi trạng thái và vòng đời đối tượng, xây dựng lớp vỏ bảo vệ đối tượng, ….

Các ví dụ và lợi ích của Proxy patterns

  • Proxies: Proxies, như được xác định bởi Proxy patterns, rất đơn giản, rất hữu ích và rất linh hoạt. Tiền đề trung tâm của Proxy patterns là bất cứ khi nào bạn cần kiểm soát quyền truy cập vào một đối tượng, một Proxy object sẽ làm một việc gần như "lừa đảo". Đó là ngồi giữa một người gọi (Client) và người nhận (Subject), Proxy class của Proxy Pattern có thể cung cấp sự bảo vệ, che giấu sự phức tạp, hoặc trì hoãn những hành động tốn kém.

  • Protection proxies: Đôi khi, vì lý do của việc bảo mật, nên đặt một proxy object trước một đối tượng để tăng thêm tính bảo mật cho đối tượng đó. Những proxy này được gọi là các protection proxies.

  • Virtual proxies: Trong một hoàn cảnh khác, sẽ khá là "khôn ngoan" để trì hoãn việc tạo ra các đối tượng đắt tiền cho đến khi chúng hoàn toàn cần thiết. Đặc biệt là việc trì hoãn sẽ cho ta lợi ích tốt nhất trong những trường hợp mà sự thể hiện đó (các đối tượng đó) có thể không cần thiết chút nào. Ví dụ tuyệt vời của loại proxy này là ActiveRecord::Associations::CollectionProxy trong Rails. Những proxy này được gọi là các virtual proxies.

  • Remote proxies: Đôi khi, việc đại diện cho một đối tượng tồn tại trên một hệ thống từ xa, che giấu đi sự phức tạp của việc đi qua mạng (hay sự lằng nhằng của việc request đến các routes phức tạp) từ máy khách cục bộ là việc rất cần thiết. Lúc đó ta có thể tạo ra những proxy này và chúng được gọi là các remote proxies.


Sau đây, mình sẽ đưa ra ví dụ mà mình cop trên mạng cho các loại proxy trên. Như các ví dụ khác trên mạng, ví dụ này có rất nhiều comment để các bạn đọc và nó đưa ra các ví dụ khá là dễ hiểu để mọi người đọc và hiểu hơn về proxy patterns

#################################################################################
#################################################################################
# The 'Subject' for all of the proxy examples
#################################################################################
#################################################################################

# The 'BankAccount' class will be the 'Subject'
# of this particular demonstration of the Proxy
# pattern. It will be proxied in each of the
# following examples.
class BankAccount
  def balance
    # pretend like this method has been fully implemented.
    puts "check balance"
  end

  def deposit(amount)
    # pretend like this method has been fully implemented.
    puts "deposit #{amount}"
  end

  def withdraw(amount)
    # pretend like this method has been fully implemented.
    puts "withdraw #{amount}"
  end
end

#################################################################################
#################################################################################
# 'Remote Proxy' Example
#################################################################################
#################################################################################

# The 'RemoteBankAccountProxy' class will act as
# a local representative of a remote
# 'BankAccount' object. It will hide and
# manage the querying and securing of
# information across the network to a
# remote server.
#
# From the perspective of the local client,
# the 'RemoteBankAccountProxy' is identical to the
# 'BankAccount' object on the remote server.
class RemoteBankAccountProxy
  # Here, we are setting a base uri for the
  # 'BankAccount' object on the remote server.
  # Of course, in real life we would incorporate
  # security and grapple with potential network
  # problems. We would also pass in some kind
  # of UID, in order to specify a specific
  # bank account.
  def initialize
    @base_uri = "localhost:3000/bank_account"
  end

  # The 'balance' method returns the
  # account balance from the remote account.
  def balance
    rest_service.get("/balance")
  end

  # The 'deposit' method posts the
  # deposited money to the remote account.
  def deposit(amount)
    rest_service.post("/deposit", {amount: amount})
  end

  # The 'withdraw' method withdraws
  # money from the remote account.
  def withdraw(amount)
    rest_service.delete("/withdraw", {amount: amount})
  end

  #######
  private
  #######

  # The 'rest_service' method is responsible for
  # lazily creating and returning a REST client
  # which can be used to communicate with the
  # remote server. I've used the class name
  # 'RestClient' as an example, but I haven't
  # included a real library here.
  def rest_service
    # We've opted to send and receive data in a JSON
    # format.
    @rest_client ||= RestClient.new(base_uri, :json)
  end

  # The rest_service attribute is private, as we
  # don't want the rest_service to be accessible
  # outside of this remote proxy.
  attr_reader :rest_service
end

#################################################################################
#################################################################################
# 'Virtual Proxy' Example
#################################################################################
#################################################################################

class VirtualBankAccountProxy

  def balance
    # When 'balance' is called on this proxy,
    # the 'subject is lazily instantiated
    # on demand. In other words, if the
    # bank account object has not yet been
    # instantiated, it will now be instantiated
    # and set.
    subject.balance
  end

  def deposit(amount)
    # When 'deposit' is called, it either invokes
    # the bank account, or instantiates it, then
    # calls 'deposit' on the bank account. This
    # ensures that the BankAccount object is not
    # instantiated until it actually needs to
    # be used.
    subject.deposit(amount)
  end

  def withdraw(amount)
    # ditto the above two methods.
    subject.withdraw(amount)
  end

  #######
  private
  #######

  def subject
    # Key to the virtual proxy pattern:
    # the lazy initialization of the proxied
    # 'Subject'.
    @subject ||= BankAccount.new
  end
end

#################################################################################
#################################################################################
# 'Protection Proxy' Example
#################################################################################
#################################################################################

# The 'ProtectionBankAccountProxy' class, being
# a 'protection proxy', is responsible for
# protecting the 'BankAccount' subject object
# from unwanted access. It acts as a security
# buffer to the 'BankAccount' object, allowing
# that object to concern itself with the behavior
# and responsibilities of its own domain, and not
# with security concerns.
class ProtectionBankAccountProxy
  attr_reader :user_credentials

  # 'user_credentials' containing the security level
  # of the given user are passed in.
  def initialize(user_credentials)
    @subject = BankAccount.new
    @user_credentials = user_credentials
  end

  # When 'balance' is called, security permissions
  # are first checked. If the user in question has
  # an appropriate level of security, then the
  # subject of this proxy will be called. Otherwise,
  # an error will be raised.
  #
  # In this case, we are specifically checking for
  # 'read' permissions.
  def balance
    check_permissions(:read)
    subject.balance
  end

  # Ditto the above method.
  #
  # It's possible that a user who is authorized to
  # check the balance of an account might not be
  # authorized to deposit money to the account.
  #
  # In this case, we are specifically checking for
  # 'write' permissions.
  def deposit(amount)
    check_permissions(:write)
    subject.deposit(amount)
  end

  # Ditto the above methods.
  def withdraw(amount)
    check_permissions(:write)
    subject.withdraw(amount)
  end

  #######
  private
  #######

  def check_permissions(permission_type)
    # We are calling a 'CredentialValidator' (undefined in this example code)
    # in order to verify whether or not a user has the requisite security
    # credentials to perform a certain action.
    #
    # In this example, a user might have 'read' permissions but not
    # 'write' permissions on the account.
    unless CredentialValidator.validate(@user_credentials, permission_type)
      # If the user does not have the proper credentials, an error is raised.
      raise "Unauthorized #{permission_type} action from: #{@user_credentials}. Account action denied."
    end
  end
end

Một số các tìm hiểu khác

  • Sử dụng method_missing Trong Ruby, đôi khi khá hữu ích khi thúc đẩy method_missing để chuyển tiếp tất cả các method không được định nghĩa trên proxy đến subject được ủy quyền (proxied). Trong trường hợp các method được xử lý thống nhất trên proxy interface, làm như vậy có thể tiết kiệm khá nhiều thời gian và công sức. Nó cũng có thể bảo vệ proxy từ các bổ sung trong tương lai tới subject được ủy quyền.
  • Proxies vs decorators Bạn có thể nhận thấy rằng một proxy đôi khi sử dụng cùng một khuôn mẫu thực hiện như một decorators. Trong những trường hợp như vậy, sự khác biệt giữa decorators và một proxy nằm ở mục đích. decorators có ý định thêm trách nhiệm bổ sung cho một đối tượng, trong khi một proxy kiểm soát truy cập vào một đối tượng.
  • Proxies vs adapters Adapters cung cấp một interface khác cho đối tượng mà nó adapts, trong khi một proxy cung cấp cùng một interface với đối tượng của nó, hoặc một tập con của interface của subject đó.

Kết luận

Proxy patterns là một trong nhiều design patterns khác (theo gof thì có đến 23 design patterns và có thể mình sẽ có các bài viết về các design patterns khác nữa).
Proxy patterns giúp cung cấp giải pháp cao trong nhiều vấn đề như tăng tính bảo mật, che giấu sự phức tạp hay trì hoãn một hành động không tốt của hệ thống đến khi cần thiết.
Tuy nhiên đây cũng chưa phải tất cả các loại proxy mà Proxy patterns cung cấp, chỉ là một vài proxy mình hiểu, còn nhiều loại proxy khác mà mọi người có thể đọc thêm.
Trên đây chỉ là tìm hiểu của cá nhân mình từ các trang mà mình đã đọc, có thể nó khá sơ sài và nhiều chỗ không đúng do mình đọc hiểu sai, mong các bạn có thể góp ý vào phần comment bên dưới để mình có thể hiểu một cách đúng hơn.
Cảm ơn mọi nguời đã để ý theo dõi đến hết bài này.