Method và xử lí dupicate trong Ruby
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 ...
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.