12/08/2018, 10:27

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.

duplicate-code.png

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.

do_not_duplicate.jpg

0