2009-08-25 50 views
5

Điều này là dành cho một API công cộng đã tồn tại mà tôi không thể phá vỡ, nhưng tôi muốn mở rộng.Cách thanh lịch của chuỗi đánh máy, biểu tượng và mảng thanh lịch?

Hiện nay phương pháp này có một chuỗi hoặc một biểu tượng hay bất cứ điều gì khác có ý nghĩa khi truyền như tham số đầu tiên send

tôi muốn thêm khả năng gửi một danh sách các chuỗi, biểu tượng, vân vân . Tôi chỉ có thể sử dụng is_a? Array, nhưng có nhiều cách khác để gửi danh sách, và đó không phải là rất ruby-ish.

Tôi sẽ gọi số map trong danh sách, do đó, độ nghiêng đầu tiên là sử dụng respond_to? :map. Nhưng một chuỗi cũng phản hồi với :map, do đó sẽ không hoạt động.

+3

Không có gì để làm với câu hỏi của bạn, nhưng tôi hy vọng bạn đang thực hiện một số kiểm tra trước khi bạn chuyển đầu vào của người dùng cho phương thức gửi! Yikes. –

+0

điểm tốt. Trong trường hợp của chúng tôi, các tài liệu nêu rõ rằng tham số là một tên phương thức. Đó là một API đủ sâu rằng điều này sẽ là đủ .... –

+0

Nó không phải là vấn đề của tài liệu - đó là một lỗ hổng bảo mật. Nếu họ vượt qua trong một backtick, tham số thứ hai của gửi được chạy trong một vỏ. Đó có thực sự là điều bạn muốn không? Bạn nên kiểm tra đầu vào trong báo cáo trường hợp và sau đó gửi các biểu tượng của cấu trúc của riêng bạn để gửi. –

Trả lời

6

Cách xử lý tất cả là Arrays? Các hành vi mà bạn muốn cho String s cũng giống như cho một Array chứa duy nhất mà String:

def foo(obj, arg) 
    [*arg].each { |method| obj.send(method) } 
end 

Bí quyết [*arg] làm việc vì các nhà điều hành splat (*) biến một yếu tố duy nhất vào bản thân hoặc một Array vào một danh sách inline các yếu tố của nó.

Sau

này về cơ bản là chỉ là một phiên bản cú pháp ngọt hoặc Arnaud's answer, mặc dù có sự khác biệt tinh tế nếu bạn vượt qua một Array chứa Array s khác.

Sau đó vẫn

Có một sự khác biệt thêm việc phải làm với giá trị trả về foo 's. Nếu bạn gọi foo(bar, :baz), bạn có thể ngạc nhiên khi nhận được [baz] quay lại. Để giải quyết điều này, bạn có thể thêm một Kestrel:

def foo(obj, arg) 
    returning(arg) do |args| 
    [*args].each { |method| obj.send(method) } 
    end 
end 

mà sẽ luôn luôn trở arg như trôi qua. Hoặc bạn có thể làm returning(obj) để bạn có thể thực hiện các cuộc gọi đến foo. Tùy thuộc vào bạn loại hành vi trả về giá trị nào bạn muốn.

+0

Đẹp hơn nhiều so với bài viết gốc của tôi. Tôi không bao giờ có thể quen với toán tử *. – Arnaud

+0

[* arg] rực rỡ! – Dmitry

0

Bạn có thể chuyển đổi hành vi dựa trên tham số.class.name không? Đó là xấu xí, nhưng nếu tôi hiểu chính xác, bạn có một phương pháp duy nhất mà bạn sẽ đi qua nhiều loại để - bạn sẽ phải phân biệt bằng cách nào đó.

Hoặc, chỉ cần thêm một phương thức xử lý tham số kiểu mảng. Đó là hành vi hơi khác nhau nên một phương pháp bổ sung có thể có ý nghĩa.

+0

Nhận xét thứ hai của bạn là giải pháp tốt hơn, nhưng tôi hy vọng sẽ có thêm nhiều kinh nghiệm từ các chuyên gia ngăn xếp. Thứ nhất có cùng vấn đề với 'is_a? Array': một người dùng có thể gửi một chuỗi không phải là một 'String' hoặc một danh sách không phải là một' mảng '. –

+0

Mặc dù vậy, tôi không chắc chắn các chuỗi 'String' thông thường.danh sách non-'Array' khá phổ biến - nhiều đối tượng là 'Enumerable'. Thật không may, do đó, là dây .... –

0

Sử dụng Marshal để tuần tự hóa các đối tượng của bạn trước khi gửi chúng.

+0

Tôi đang bối rối. 'send' là phương thức gọi hàm theo tên trong Ruby. Marshal sẽ giúp như thế nào? –

1

ArrayString là cả hai Enumerables, không có cách nào thanh lịch để nói "một điều có thể là không đọc được, nhưng không phải là chuỗi", ít nhất là không được thảo luận.

gì tôi sẽ làm là vịt kiểu cho Enumerable (responds_to? :[]) và sau đó sử dụng một tuyên bố case, như vậy:

def foo(obj, arg) 
    if arg.respond_to?(:[]) 
    case arg 
    when String then obj.send(arg) 
    else arg.each { |method_name| obj.send(method_name) } 
    end 
    end 
end 

hoặc thậm chí sạch hơn:

def foo(obj, arg) 
    case arg 
    when String then obj.send(arg) 
    when Enumerable then arg.each { |method| obj.send(method) } 
    else nil 
    end 
end 
+0

Cảm ơn lời giải thích mở rộng, nhưng trong câu hỏi ban đầu tôi đã yêu cầu một cái gì đó sạch hơn 'is_a? Mảng'. Sử dụng 'is_a? Chuỗi' (hoặc một biến thể giống như bạn đã hiển thị) phá vỡ API ban đầu cho bất kỳ ai đang sử dụng thứ gì đó bỏ qua như một String cho 'send'. Thực tế, hầu hết người dùng có thể sử dụng 'Symbol' để thay thế. Tôi có thể sử dụng 'x.is_a? (String) || x.is_a? (Biểu tượng) ', nhưng đó không phải là rất ruby-ish, và có thể phá vỡ API cho một số người dùng của tôi. –

0

Nếu bạn không muốn monkeypatch, chỉ cần xoa bóp danh sách vào một chuỗi thích hợp trước khi gửi. Nếu bạn không nhớ monkeypatching hoặc kế thừa, nhưng muốn giữ lại cùng một phương pháp chữ ký:

class ToBePatched 
    alias_method :__old_takes_a_string, :takes_a_string 

    #since the old method wanted only a string, check for a string and call the old method 
    # otherwise do your business with the map on things that respond to a map. 
    def takes_a_string(string_or_mappable) 
     return __old_takes_a_string(string_or_mappable) if String === string_or_mappable 
     raise ArgumentError unless string_or_mappable.responds_to?(:map) 
     # do whatever you wish to do 
    end 
end 
0

Có lẽ câu hỏi là không đủ rõ ràng, nhưng một đêm ngủ cho tôi xem hai cách sạch để trả lời câu hỏi này.

1: to_sym có sẵn trên StringSymbol và sẽ có sẵn trên mọi thứ có chức năng như chuỗi.

if arg.respond_to? :to_sym 
    obj.send(arg, ...) 
else 
    # do array stuff 
end 

2: gửi ném TypeError khi chuyển một mảng.

begin 
    obj.send(arg, ...) 
rescue TypeError 
    # do array stuff 
end 

Tôi đặc biệt thích # 2. Tôi nghi ngờ nghiêm trọng bất kỳ người dùng nào của API cũ đang mong đợi TypeError được nâng lên bằng phương thức này ...

+1

Giải pháp đầu tiên có ý nghĩa, vì gửi thực sự mong đợi một biểu tượng. Nhưng tôi hy vọng bạn không nghiêm túc về giải pháp thứ hai. Ngoại lệ không phải là luồng điều khiển. –

+0

@Sarah: ngẫu nhiên, nhưng tôi nghĩ khoảng 40% tất cả các nhận xét và phản hồi của bạn về SO bao gồm câu "Ngoại lệ không phải là luồng điều khiển". – Telemachus

+0

@Telemachus - ít nhất là gần đây. :) –

1

Hãy nói rằng chức năng của bạn được đặt tên func

tôi sẽ làm một mảng từ các thông số với

def func(param) 
    a = Array.new 
    a << param 
    a.flatten! 
    func_array(a) 
end 

Bạn kết thúc với việc thực hiện chức năng func_array của bạn đối với mảng chỉ

với func (" hello world ") bạn sẽ nhận được a.flatten! => ["hello world"] với func (["hello", "world"]) bạn sẽ nhận được a.flatten! => ["hello", "world"]

2

Một chi tiết quan trọng mà đã bị bỏ qua trong tất cả các câu trả lời: dây làm không đáp ứng với :map, vì vậy câu trả lời đơn giản nhất là trong câu hỏi ban đầu: chỉ cần sử dụng respond_to? :map.

Các vấn đề liên quan