2010-07-29 23 views
9

Tôi đang viết một ứng dụng dòng lệnh Ruby nhỏ sử dụng fileutils từ thư viện chuẩn cho các thao tác tệp. Tùy thuộc vào cách người dùng gọi ứng dụng, tôi sẽ muốn bao gồm FileUtils, FileUtils::DryRun hoặc FileUtils::Verbose.Làm thế nào tôi có thể chọn phiên bản của một mô-đun để bao gồm động trong Ruby?

include là riêng tư, tuy nhiên, tôi không thể đặt logic để chọn phương thức initialize của đối tượng. (Đó là suy nghĩ đầu tiên của tôi, kể từ đó tôi có thể chuyển thông tin về lựa chọn của người dùng như một tham số cho new.) Tôi đã đưa ra hai tùy chọn có vẻ hiệu quả, nhưng tôi không hài lòng với một trong hai:

  1. Đặt một biến toàn cầu trong không gian tên của ứng dụng dựa trên sự lựa chọn của người dùng, và sau đó làm một điều kiện bao gồm trong lớp:

    class Worker 
        case App::OPTION 
        when "dry-run" 
        include FileUtils::DryRun 
        etc. 
    
  2. Tạo sub-lớp học, nơi sự khác biệt duy nhất là phiên bản FileUtils chúng bao gồm. Chọn tùy chọn phù hợp, tùy thuộc vào lựa chọn của người dùng.

    class Worker 
        include FileUtils 
        # shared Worker methods go here 
    end 
    class Worker::DryRun < Worker 
        include FileUtils::DryRun 
    end 
    class Worker::Verbose < Worker 
        include FileUtils::Verbose 
    end 
    

Phương pháp đầu tiên dường như DRY-er, nhưng tôi hy vọng rằng có một cái gì đó đơn giản hơn mà tôi chưa nghĩ đến.

Trả lời

7

Vậy điều gì sẽ xảy ra nếu riêng tư?

class Worker 
    def initialize(verbose=false) 
    if verbose 
     (class <<self; include FileUtils::Verbose; end) 
    else 
     (class <<self; include FileUtils; end) 
    end 
    touch "test" 
    end 
end 

này bao gồm FileUtils::something trong metaclass đặc biệt của 's Worker - không có trong lớp học chính Worker. Các công nhân khác nhau có thể sử dụng khác nhau FileUtils theo cách này.

+0

"Vậy điều gì" nghe có vẻ đúng với tôi. (Đây là * chính xác * điều đơn giản hơn tôi không thấy.) Cảm ơn. – Telemachus

+0

Rất tiếc, lỗi của tôi. Mã mà tôi đã đưa ra trước đây sẽ sửa đổi lớp 'Worker' để tất cả' Worker 'sẽ sử dụng cùng một thiết lập. Bây giờ nó thực sự sử dụng metaclass và cho phép cài đặt trước khi làm việc. – taw

+1

thay vì '(lớp << tự; bao gồm FileUtils; kết thúc)' bạn cũng có thể sử dụng 'extend FileUtils'. –

0

Nếu bạn muốn tránh các "công tắc" và tiêm các mô-đun, cú pháp

def initialize(injected_module) 
    class << self 
     include injected_module 
    end 
end 

sẽ không hoạt động (biến injected_module là ra khỏi phạm vi). Bạn có thể sử dụng các trick self.class.send, nhưng mỗi đối tượng dụ mở rộng có vẻ hợp lý hơn đối với tôi, không chỉ vì nó là ngắn hơn để viết:

def initialize(injected_module = MyDefaultModule) 
    extend injected_module 
end 

mà còn nó giảm thiểu các tác dụng phụ - những chia sẻ và dễ dàng trạng thái có thể thay đổi của lớp, điều này có thể dẫn đến hành vi không mong muốn trong một dự án lớn hơn. Trong Ruby thì không có "sự riêng tư" thực sự để nói, nhưng một số phương thức được đánh dấu riêng tư không phải là một lý do.

0

có điều kiện bao gồm cả các mô-đun thông qua các phương pháp gửi làm việc cho tôi như trong ví dụ dưới đây được thử nghiệm:

class Artefact 
    include HPALMGenericApi 
    # the initializer just sets the server name we will be using ans also the 'transport' method : Rest or OTA (set in the opt parameter) 
    def initialize server, opt = {} 
    # conditionally include the Rest or OTA module 
    self.class.send(:include, HPALMApiRest) if (opt.empty? || (opt && opt[:using] opt[:using] == :Rest)) 
    self.class.send(:include, HPALMApiOTA) if (opt && opt[:using] opt[:using] == :OTA)  
    # ... rest of initialization code 
    end 
end 
Các vấn đề liên quan