Command Design Pattern trong Ruby
Bài đăng này đã không được cập nhật trong 6 năm
Tiếp nối cho chuổi Design Pattern trước thì hôm nay mình sẽ giới thiệu đến mọi người một Design Pattern thuộc loại Behavioural pattern đó là Command Pattern.
Command Pattern là gì?
Command Pattern là một behavioural pattern, Nó được sử dụng để đóng gói tất cả thông tin cần để thực hiện một action hay trigger một event tại một thời điểm nào sau đó. Các thông tin này bao gồm Tên method, Object sở hữu các method vào các parameters của methods.
Mục đich:
- Đóng gói 1 request vào trong một object.
- Tham số hóa các request từ các clients khác nhau.
- Cho phép saving các requests vào một hàng đợi.
Khi nào thì nên dùng:
- Tham chiếu đến một Object.
- Xác định và thực hiện những Request tại những thời điểm khác nhau.
- Cần thực hiện thao tác Undo
- Cần thực hiện thao tác Logging changes (Trong trường hợp hệ thống bị treo)
- Cấu trúc hệ thống có dạng: điều khiển cấp cao được xây dựng trên những điều khiển nền tảng.
Cụ thể nó là thế nào:
Các thành phần được dùng với command pattern:
- Command: Định nghĩa 1 interface để thực hiện một hoạt động. Là đối tượng lưu giữ Request và State của một đối tượng tại một thời điểm.
- ConcreteCommand:
- Định nghĩa một kết dính giữa Receiver và một action.
- Implements Execute bằng cách khai báo các hoạt động tương ứng trên Receiver.
- Receiver: Là đối tượng thực hiện lệnh trên mỗi Request.
- Invoker: Là nơi lưu trữ và phát sinh mỗi Request dưới dạng Command Object. Quyết định khi nào thực hiện nó.
- Client: Tạo một ConcreteCommand object và thiết lập reveiver của nó.
Sơ đồ UML
Ưu điểm và hạn chế của nó:
- Command Pattern tách riêng đối tượng và các request của đối tượng mà vẫn biết cách đáp ứng các request lên đối tượng đó.
- Các Command là những Object cơ bản, chúng ta có thể vận dụng và mở rộng như bất kỳ đối tượng khác.
- Các Command có thể được tập hợp thành một Composite Command.
- Dễ dàng thêm một Command mới mà không cần chỉnh sửa lại các class đã có.
Example Code
#invoker
class ButtonInvoker
def initialize(name, command)
@name, @command = name, command
end
def press
History.add @command
@command.execute
end
end
class History
class << self
def stack
@stack ||= []
end
def add command
stack << command
end
def undo
command = stack.pop
command.undo
p "Undoing #{command.class} command"
end
def undo_all
stack.each do |command|
command.undo
p "Undoing #{command.class} command"
end
end
end
end
#Command interface
class Command
attr_accessor :receiver
def initialize receiver
@receiver = receiver
end
def execute
raise NotImplementedError
end
def undo
raise NotImplementedError
end
end
#ConcreteCommand
class MoveDownCommand < Command
def execute
receiver.move_down
end
def undo
receiver.move_up
end
end
class MoveLeftCommand < Command
def execute
receiver.move_left
end
def undo
receiver.move_right
end
end
class MoveRightCommand < Command
def execute
receiver.move_right
end
def undo
receiver.move_left
end
end
class MoveUpCommand < Command
def execute
receiver.move_up
end
def undo
receiver.move_down
end
end
#Receiver
class RobotReceiver
def initialize(x = 0, y = 0)
@x, @y = x, y
end
def location
"x[#{@x}], y[#{@y}]"
end
def move_right
@x += 1
end
def move_left
@x -= 1
end
def move_up
@y += 1
end
def move_down
@y -= 1
end
end
#Client
class Client
attr_accessor :receiver
def initialize
@receiver = RobotReceiver.new
@move_up_cmd = MoveUpCommand.new @receiver
@move_down_cmd = MoveDownCommand.new @receiver
@move_right_cmd = MoveRightCommand.new @receiver
@move_left_cmd = MoveLeftCommand.new @receiver
end
def move_with key
case key
when "up"
ButtonInvoker.new(key, @move_up_cmd).press
when "down"
ButtonInvoker.new(key, @move_down_cmd).press
when "right"
ButtonInvoker.new(key, @move_up_right).press
else
ButtonInvoker.new(key, @move_up_left).press
end
end
end
Giải thích một chút về đoạn code trên: Chúng ta có thể thấy rằng các thành phần trong Sơ đồ đều được định nghĩa đầy đủ trong đoạn code trên. RobotReceiver nó là Receiver là đó tượng thực hiện các action của mỗi lần Request. Command là được đinh nghĩa như một Interface với 2 method execute là undo. Nó sẽ có các ConcreteCommand kế thừa và định nghĩa. Tạo liên kết giữa Receiver và một action. Chúng ta có 1 Invoker là ButtonInvoker được dùng để lưu trữ và thực hiện các Request. lựa chọn các cách request khi client yêu cầu. Và cuối cùng là Client tạo ra các ConcreteCommand và các Receiver của nó.
Các vấn đề cụ thể và Implement
Chúng ta đã hiểu pattern này hoạt động như thế, và đây là lúc chúng ta xem nó có lợi thế gì và những mặt sai sót gì cần tránh.
Sự thông minh của Command:
Có 2 điểm chú ý mà các lập trình viên phải tránh khi sử dụng pattern này:
1. Command chỉ là một liên kết giữa Receiver mà Action cái thực hiện request.
2. Command thực hiện mọi thứ của nó mà không gửi bất cứ cái gì về cho receiver.
Chúng ta phải luôn luôn giữ suy nghĩ thực tế là receiver là một người mà biết cách thực hiện một hoạt động được cần. Command giúp cho client uỷ quyền request của nó nhanh hơn và đảm bảo rằng command kết thúc nơi nó muốn.
Undo và Redo action:
Với việc sửa dụng Command Pattern bạn có thể thực hiện được việc Undo và Redo nhờ vào việc định nghĩa và lưu trữ của Invoker. Một ý tưởng tuyệt vời cho việc undo và redo tương tự cũng được áp dụng khác hay trong Memento Pattern. Bài viết sau mình sẽ chia sẽ về nó (Hy vọng có đủ nhiều thời gian)
Kết luận:
Trên đây là những điều thú vị mình tìm hiểu về Command Pattern và áp dụng nó trong Ruby. Hy vọng sẽ có ích với các bạn. Đừng ngại ngần đóng góp ý kiến hoặc chia sẽ thêm những điều thú vị về pattern này nhé. Thank all .
All rights reserved