Method và xử lí dupicate trong Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Methods và xử lí duplicate trong ruby
Ở phần trước chúng ta đã tìm hiểu về object trong Ruby, được làm quen với instance variables, method, class, module, methods lockup, ancestors chain … Ở phần này chúng ta sẽ tìm hiểu thêm về phương thức và cách xử lí duplicate code trong ruby.
Vấn đề về trùng lặp code
Đây là vấn đề mà hầu hết tất cả các lập trình viên đều gặp phải, và với những lập trình viên có nhiều kinh nghiệm thì họ luôn cố gắng để không xảy ra vấn đề này.
Chúng ta sẽ cùng nhau xem một ví dụ về trùng lặp code và cách xử lí.
Hê thống lưu trữ thông tin các máy trạm làm việc, có class DS (data source)
class DS
def initialize # connect to data source...
def get_mouse_info(workstation_id) # ...
def get_mouse_price(workstation_id) # ...
def get_keyboard_info(workstation_id) # ...
def get_keyboard_price(workstation_id) # ...
def get_cpu_info(workstation_id) # ...
def get_cpu_price(workstation_id) # ...
def get_display_info(workstation_id) # ...
def get_display_price(workstation_id) # ...
# ...and so on
DS#initialize()
kết nối tới dữ liệu hệ thống khi bạn tạo một đối tượng DS(), bạn có thể xem các thông tin của một máy trạm nào đó.
ds = DS.new
ds.get_cpu_info(42) # => 2.16 Ghz
ds.get_cpu_price(42) # => 150
ds.get_mouse_info(42) # => Dual Optical
ds.get_mouse_price(42) # => 40
Và sau đó để đưa ra các thông tin chi tiết của máy trạm, mỗi máy tính như vậy cần phải là một đối tượng, và bạn có class Computer như sau :
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
info = @data_source.get_mouse_info(@id)
price = @data_source.get_mouse_price(@id)
result = "Mouse: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
def cpu
info = @data_source.get_cpu_info(@id)
price = @data_source.get_cpu_price(@id)
result = "Cpu: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
def keyboard
info = @data_source.get_keyboard_info(@id)
price = @data_source.get_keyboard_price(@id)
result = "Keyboard: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
#
end
Class Computer hiện tại đã thực hiện được chức năng mà bạn yêu cầu nhưng nó quá dài, nó bị trùng lặp code. Vậy bạn sẽ xử lí nó như thế nào ?
Phương thức động
Chúng ta có thể xử lý, xóa bỏ các đoạn code bị trùng lặp với phương thức động hoặc method_missing().
Gọi phương thức động :
Khi bạn gọi một phương thức, bạn thường sẽ sử dụng kí hiệu là dấu chấm.
class MyClass
def my_method(my_arg)
my_arg * 2
end
end
2.1.5 :016 > obj = MyClass.new
=> #<MyClass:0x00000000e509c0>
2.1.5 :017 > obj.my_method(4)
=> 8
Tuy nhiên bạn cũng có thể gọi một phương thức bằng cách sử dụng Object#send() thay cho việc sử dụng kí hiệu dấu chấm.
2.1.5 :018 > obj.send(:my_method, 4)
=> 8
Định nghĩa phương thức động :
Để định nghĩa một phương thức động bạn có thể sử dụng define_method( ) , bạn chỉ cần cung cấp tên phương thức và một đoạn code bên trong.
class MyClass
define_method :my_method do |my_arg|
my_arg * 3
end
end
2.1.5 :006 > obj = MyClass.new
=> #<MyClass:0x00000001b90680>
2.1.5 :007 > obj.my_method(4)
=> 8
define_method()
được thực thi bên trong MyClass
vì vậy my_method()
được định nghĩa như một instance method
của MyClass
.
Quay lại với vấn đề trùng lặp code mà chúng ta đã đề cập, bây giờ chúng ta đã biết thêm về cách định nghĩa và cách gọi một phương thức động, chúng ta sẽ áp dụng vào để xóa đi những dòng code bị trùng lặp.
Bước 1 : chúng ta sử dụng phương thức send()
thay vì sử dụng dấu chấm.
Bạn sẽ loại bỏ được trùng lặp code do cách gọi các hàm tên khác nhau nhưng có các tham số truyền vào và chức năng tương đương nhau.
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
component :mouse
end
def cpu
component :cpu
end
def keyboard
component :keyboard
end
def component(name)
info = @data_source.send "get_#{name}_info" , @id
price = @data_source.send "get_#{name}_price" , @id
result = "#{name.to_s.capitalize}: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
end
Bước 2 : sử dụng define_method()
để tạo ra phương thức
Như vậy bạn sẽ loại bỏ được trùng lặp code vì định nghĩa nhiều hàm với các chức năng bên trong có sự giống nhau.
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def self.define_component(name)
define_method(name) {
info = @data_source.send "get_#{name}_info" , @id
price = @data_source.send "get_#{name}_price" , @id
result = "#{name.to_s.capitalize}: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
}
end
define_component :mouse
define_component :cpu
define_component :keyboard
end
Bước 3 : xem lại code và xóa bỏ những mẩu code trùng lặp còn sót lại.
Chúng ta có thể loại bỏ việc gọi tới define_component
nhiều lần trên nhiều dòng khác nhau bằng cách chú ý vào data_source.methods
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
end
def self.define_component(name)
define_method(name) {
info = @data_source.send "get_#{name}_info" , @id
price = @data_source.send "get_#{name}_price" , @id
result = "#{name.capitalize}: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
}
end
end
Vậy là sau 3 bước, sử dụng dynamic method thì chúng ta đã xóa bỏ được các đoạn code bị trùng lặp. Tiếp theo thì chúng ta sẽ nghiên cứu thêm về một phương thức có trong trong ruby mà chúng ta đã được tìm hiểu sơ qua ở phần trước, đó chính là method_missing()
. Làm thế nào sử dụng method_missing()
để loại bỏ các đoạn code bị trùng lặp ?
method_missing()
Nhắc lại : Trong ruby khi bạn gọi một phương thức, nó sẽ tìm dựa trên ancestors chain
, nếu nó không tồn tại, nó sẽ gọi tới method_mising()
class Lawyer; end
2.1.5 :009 > nick = Lawyer.new
=> #<Lawyer:0x00000001bc75e0>
2.1.5 :010 > nick.talk_simple
NoMethodError: undefined method `talk_simple' for #<Lawyer:0x00000001bc75e0>
và bạn có thể ghi đè lại phương thức method_missing()
để tạo ra kết quả như bạn mong muốn.
class Lawyer
def method_missing(method, *args)
puts "You called: #{method}(#{args.join(', ')})"
puts "(You also passed it a block)" if block_given?
end
end
2.1.5 :007 > jason = Lawyer.new
=> #<Lawyer:0x000000012696b0>
2.1.5 :008 > jason.talk_simple('a' , 'b' ) do
2.1.5 :009 > # a block
2.1.5 :010 > end
You called: talk_simple(a, b)
(You also passed it a block)
=> nil
Vậy ghi đè lại method_missing()
cho phép bạn gọi tới những phực thức mà nó không tồn tại.
Bạn cũng có thể sử dụng method_missing()
để xử lý đoạn code trùng lặp, ví dụ như áp dụng ở class Computer như sau :
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def method_missing(name, *args)
super if !@data_source.respond_to?("get_#{name}_info" )
info = @data_source.send("get_#{name}_info" , args[0])
price = @data_source.send("get_#{name}_price" , args[0])
result = "#{name.to_s.capitalize}: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
end
Kết : dù sao đi chăng nữa việc duplicate code sẽ gây ra lãng phí tài nguyên và nhiều lúc sẽ gây ra sự rắc rối, khó hiểu trong chương trình của bạn. Chính vì vậy bạn hãy luôn cố gắng, đừng để nó xảy ra.
All rights reserved