Tôi có thể gọi một phương thức cá thể trên mô-đun Ruby mà không bao gồm nó không?


180

Lý lịch:

Tôi có một mô-đun khai báo một số phương thức cá thể

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

Và tôi muốn gọi một số phương thức này từ trong một lớp. Làm thế nào bạn thường làm điều này trong ruby ​​là như thế này:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Vấn đề

include UsefulThingsmang lại tất cả các phương pháp từ UsefulThings. Trong trường hợp này tôi chỉ muốn format_textvà rõ ràng không muốn get_filedelete_file.

Tôi có thể thấy một số giải pháp có thể cho việc này:

  1. Bằng cách nào đó gọi phương thức trực tiếp trên mô-đun mà không bao gồm nó ở bất cứ đâu
    • Tôi không biết làm thế nào / nếu điều này có thể được thực hiện. (Do đó câu hỏi này)
  2. Bằng cách nào đó bao gồm Usefulthingsvà chỉ mang lại một số phương pháp của nó
    • Tôi cũng không biết làm thế nào / nếu điều này có thể được thực hiện
  3. Tạo một lớp proxy, bao gồm UsefulThingstrong đó, sau đó ủy quyền format_textcho thể hiện proxy đó
    • Điều này sẽ làm việc, nhưng các lớp proxy ẩn danh là một hack. Kinh quá.
  4. Chia mô đun thành 2 hoặc nhiều mô đun nhỏ hơn
    • Điều này cũng sẽ hoạt động, và có lẽ là giải pháp tốt nhất tôi có thể nghĩ ra, nhưng tôi muốn tránh nó vì tôi kết thúc với sự phát triển của hàng chục và hàng chục mô-đun - việc quản lý điều này sẽ rất nặng nề

Tại sao có nhiều hàm không liên quan trong một mô-đun? Đó là ApplicationHelpertừ một ứng dụng đường ray, mà nhóm của chúng tôi đã quyết định thực tế là bãi rác cho bất cứ thứ gì không đủ cụ thể để thuộc về bất cứ nơi nào khác. Chủ yếu là các phương pháp tiện ích độc lập được sử dụng ở mọi nơi. Tôi có thể chia nó thành những người trợ giúp riêng biệt, nhưng có 30 người trong số họ, tất cả chỉ có 1 phương pháp ... điều này dường như không hiệu quả


Nếu bạn thực hiện cách tiếp cận thứ 4 (tách mô-đun), bạn có thể làm cho nó để một mô-đun luôn tự động bao gồm mô-đun kia bằng cách sử dụng hàm Module#includedgọi lại để kích hoạt mô-đun includekia. Các format_textphương pháp có thể được chuyển sang mô-đun riêng của nó, vì nó có vẻ là hữu ích về riêng của nó. Điều này sẽ làm cho việc quản lý bớt một chút gánh nặng.
Batkins

Tôi bối rối bởi tất cả các tham chiếu trong câu trả lời cho các chức năng mô-đun. Giả sử bạn có module UT; def add1; self+1; end; def add2; self+2; end; endvà bạn muốn sử dụng add1nhưng không phải add2trong lớp Fixnum. Làm thế nào nó sẽ giúp có chức năng mô-đun cho điều đó? Tui bỏ lỡ điều gì vậy?
Cary Swoveland

Câu trả lời:


135

Nếu một phương thức trên một mô-đun được chuyển thành một chức năng mô-đun, bạn có thể chỉ cần gọi nó ra khỏi Mod như thể nó đã được khai báo là

module Mods
  def self.foo
     puts "Mods.foo(self)"
  end
end

Cách tiếp cận module_feft dưới đây sẽ tránh phá vỡ mọi lớp bao gồm tất cả các Mod.

module Mods
  def foo
    puts "Mods.foo"
  end
end

class Includer
  include Mods
end

Includer.new.foo

Mods.module_eval do
  module_function(:foo)
  public :foo
end

Includer.new.foo # this would break without public :foo above

class Thing
  def bar
    Mods.foo
  end
end

Thing.new.bar  

Tuy nhiên, tôi tò mò tại sao một tập hợp các hàm không liên quan đều được chứa trong cùng một mô-đun ở vị trí đầu tiên?

Đã chỉnh sửa để hiển thị bao gồm vẫn hoạt động nếu public :foođược gọi saumodule_function :foo


1
Ở một khía cạnh khác, module_functionbiến phương thức thành một phương thức riêng tư, điều này sẽ phá vỡ mã khác - nếu không đây sẽ là câu trả lời được chấp nhận
Orion Edwards

Cuối cùng tôi đã làm điều tốt và tái cấu trúc mã của tôi thành các mô-đun riêng biệt. Nó không tệ như tôi nghĩ. Câu trả lời của bạn vẫn sẽ giải quyết nó một cách chính xác nhất WRT các ràng buộc ban đầu của tôi, vì vậy được chấp nhận!
Orion Edwards

Các hàm liên quan @dgtized có thể kết thúc trong một mô-đun mọi lúc, điều đó không có nghĩa là tôi muốn làm ô nhiễm không gian tên của mình với tất cả chúng. Một ví dụ đơn giản nếu có một Files.truncatevà a Strings.truncatevà tôi muốn sử dụng cả hai trong cùng một lớp, một cách rõ ràng. Tạo một lớp / thể hiện mới mỗi lần tôi cần một phương thức cụ thể hoặc sửa đổi bản gốc không phải là một cách tiếp cận hay, mặc dù tôi không phải là một nhà phát triển Ruby.
TWiStErRob

146

Tôi nghĩ rằng cách ngắn nhất để thực hiện cuộc gọi đơn lẻ (không thay đổi các mô-đun hiện có hoặc tạo cuộc gọi mới) sẽ như sau:

Class.new.extend(UsefulThings).get_file

2
Rất hữu ích cho các tập tin erb. html.erb hoặc js.erb. cảm ơn ! Tôi tự hỏi nếu hệ thống này lãng phí bộ nhớ
Albert Català

5
@ AlbertCirthà theo các thử nghiệm của tôi và stackoverflow.com/a/23645285/54247 các lớp ẩn danh là rác được thu thập giống như mọi thứ khác, vì vậy nó không nên lãng phí bộ nhớ.
dolzenko

1
Nếu bạn không muốn tạo một lớp ẩn danh làm proxy, bạn cũng có thể sử dụng một đối tượng làm proxy cho phương thức. Object.new.extend(UsefulThings).get_file
3limin4t0r

82

Một cách khác để làm điều đó nếu bạn "sở hữu" mô-đun là sử dụng module_function.

module UsefulThings
  def a
    puts "aaay"
  end
  module_function :a

  def b
    puts "beee"
  end
end

def test
  UsefulThings.a
  UsefulThings.b # Fails!  Not a module method
end

test

26
Và đối với trường hợp bạn không sở hữu nó: UtilityThings.send: module_feft ,: b
Dustin

3
module_function chuyển đổi phương thức thành một phương thức riêng tư (dù sao nó cũng có trong IRB của tôi), điều này sẽ phá vỡ các người gọi khác :-(
Orion Edwards

Tôi thực sự thích cách tiếp cận này, cho mục đích của tôi ít nhất. Bây giờ tôi có thể gọi ModuleName.method :method_nameđể lấy một đối tượng phương thức và gọi nó qua method_obj.call. Nếu không, tôi sẽ phải liên kết phương thức với một thể hiện của đối tượng ban đầu, điều này là không thể nếu đối tượng ban đầu là một Mô-đun. Để đáp ứng với Orion Edwards, module_functionkhông đặt phương thức cá thể ban đầu thành riêng tư. ruby-doc.org/core/groupes/Module.html#M001642
John

2
Orion - Tôi không tin đó là sự thật. Theo ruby-doc.org/docs/ProgrammingRuby/html/ Khăn , module_feft "tạo các hàm mô-đun cho các phương thức được đặt tên. các mô-đun. Các chức năng mô-đun là bản sao của bản gốc và do đó có thể được thay đổi độc lập. Các phiên bản phương thức cá thể được đặt ở chế độ riêng tư. Nếu được sử dụng không có đối số, các phương thức được xác định sau đó sẽ trở thành chức năng mô-đun. "
Ryan Crispin Heneise

2
bạn cũng có thể định nghĩa nó làdef self.a; puts 'aaay'; end
Tilo

17

Nếu bạn muốn gọi các phương thức này mà không bao gồm mô-đun trong một lớp khác thì bạn cần định nghĩa chúng là các phương thức mô-đun:

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...
end

và sau đó bạn có thể gọi cho họ với

UsefulThings.format_text("xxx")

hoặc là

UsefulThings::format_text("xxx")

Nhưng dù sao tôi cũng khuyên bạn nên đặt các phương thức liên quan trong một mô-đun hoặc trong một lớp. Nếu bạn gặp vấn đề mà bạn muốn bao gồm chỉ một phương thức từ mô-đun thì có vẻ như mùi mã xấu và việc kết hợp các phương thức không liên quan với nhau không phải là một cách tốt.


17

Để gọi một phương thức cá thể mô-đun mà không bao gồm mô-đun (và không tạo các đối tượng trung gian):

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end

Hãy cẩn thận với phương pháp này: ràng buộc với bản thân có thể không cung cấp phương thức với mọi thứ nó mong đợi. Ví dụ, có lẽ format_textgiả sử sự tồn tại của một phương thức khác được cung cấp bởi mô-đun, mà (nói chung) sẽ không có mặt.
Nathan

Đây là cách, có thể tải bất kỳ mô-đun nào, bất kể phương pháp hỗ trợ mô-đun có thể được tải trực tiếp. Thậm chí tốt hơn là thay đổi ở cấp độ Mô-đun. Nhưng trong một số trường hợp, dòng này là những gì người hỏi muốn nhận được.
twindai

6

Đầu tiên, tôi khuyên bạn nên chia mô-đun thành những thứ hữu ích bạn cần. Nhưng bạn luôn có thể tạo một lớp mở rộng cho yêu cầu của bạn:

module UsefulThings
  def a
    puts "aaay"
  end
  def b
    puts "beee"
  end
end

def test
  ob = Class.new.send(:include, UsefulThings).new
  ob.a
end

test

4

A. Trong trường hợp bạn, luôn muốn gọi họ theo cách "đủ điều kiện", độc lập (UtilityThings.get_file), sau đó chỉ cần làm cho họ tĩnh như những người khác chỉ ra,

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...

  # Or.. make all of the "static"
  class << self
     def write_file; ...
     def commit_file; ...
  end

end

B. Nếu bạn vẫn muốn giữ cách tiếp cận mixin trong cùng một trường hợp, cũng như cách gọi độc lập một lần, bạn có thể có một mô-đun một lớp mở rộng chính nó với mixin:

module UsefulThingsMixin
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

Vì vậy, cả hai hoạt động sau đó:

  UsefulThings.get_file()       # one off

   class MyUser
      include UsefulThingsMixin  
      def f
         format_text             # all useful things available directly
      end
   end 

IMHO sạch hơn module_functionmọi phương thức - trong trường hợp muốn tất cả chúng.


extend selflà một thành ngữ phổ biến.
smathy

4

Theo tôi hiểu câu hỏi, bạn muốn trộn một số phương thức cá thể của mô-đun vào một lớp.

Hãy bắt đầu bằng cách xem xét cách Module # bao gồm hoạt động. Giả sử chúng ta có một mô-đun UsefulThingschứa hai phương thức thể hiện:

module UsefulThings
  def add1
    self + 1
  end
  def add3
    self + 3
  end
end

UsefulThings.instance_methods
  #=> [:add1, :add3]

Fixnum includemô-đun đó:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Chúng ta thấy rằng:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

Bạn có muốn UsefulThings#add3ghi đè Fixnum#add3, vì vậy 1.add3sẽ trở lại 4? Xem xét điều này:

Fixnum.ancestors
  #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable,
  #    Object, Kernel, BasicObject] 

Khi lớp includes là mô-đun, mô-đun trở thành siêu lớp của lớp. Vì vậy, do cách thức kế thừa hoạt động, gửi add3đến một thể hiện Fixnumsẽ gây ra Fixnum#add3được gọi, trả lại dog.

Bây giờ hãy thêm một phương thức :add2vào UsefulThings:

module UsefulThings
  def add1
    self + 1
  end
  def add2
    self + 2
  end
  def add3
    self + 3
  end
end

Bây giờ chúng ta muốn Fixnumđể includechỉ những phương pháp add1add3. Làm như vậy, chúng tôi hy vọng sẽ nhận được kết quả tương tự như trên.

Giả sử, như trên, chúng tôi thực hiện:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Kết quả là gì? Phương thức không mong muốn :add2được thêm vào Fixnum, :add1được thêm vào và, vì những lý do tôi đã giải thích ở trên, :add3không được thêm vào. Vì vậy, tất cả chúng ta phải làm là undef :add2. Chúng ta có thể làm điều đó với một phương thức trợ giúp đơn giản:

module Helpers
  def self.include_some(mod, klass, *args)
    klass.send(:include, mod)
    (mod.instance_methods - args - klass.instance_methods).each do |m|
      klass.send(:undef_method, m)
    end
  end
end

mà chúng tôi gọi như thế này:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  Helpers.include_some(UsefulThings, self, :add1, :add3)
end

Sau đó:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

đó là kết quả mà chúng ta muốn


2

Không chắc ai đó vẫn cần nó sau 10 năm nhưng tôi đã giải quyết nó bằng eigenclass.

module UsefulThings
  def useful_thing_1
    "thing_1"
  end

  class << self
    include UsefulThings
  end
end

class A
  include UsefulThings
end

class B
  extend UsefulThings
end

UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"

0

Sau gần 9 năm, đây là một giải pháp chung:

module CreateModuleFunctions
  def self.included(base)
    base.instance_methods.each do |method|
      base.module_eval do
        module_function(method)
        public(method)
      end
    end
  end
end

RSpec.describe CreateModuleFunctions do
  context "when included into a Module" do
    it "makes the Module's methods invokable via the Module" do
      module ModuleIncluded
        def instance_method_1;end
        def instance_method_2;end

        include CreateModuleFunctions
      end

      expect { ModuleIncluded.instance_method_1 }.to_not raise_error
    end
  end
end

Thủ thuật đáng tiếc bạn cần áp dụng là bao gồm mô-đun sau khi các phương thức đã được xác định. Ngoài ra, bạn cũng có thể bao gồm nó sau khi bối cảnh được xác định làModuleIncluded.send(:include, CreateModuleFunctions) .

Hoặc bạn có thể sử dụng nó thông qua reflection_utils đá quý.

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

Chà, cách tiếp cận của bạn giống như phần lớn các câu trả lời chúng ta có thể thấy ở đây không giải quyết vấn đề ban đầu và tải tất cả các phương thức. Câu trả lời tốt duy nhất là hủy liên kết phương thức khỏi mô-đun ban đầu và liên kết nó trong lớp được nhắm mục tiêu, vì @renier đã trả lời 3 năm trước.
Joel AZEMAR

@JoelAZEMAR Tôi nghĩ bạn đang hiểu nhầm giải pháp này. Nó được thêm vào mô-đun bạn muốn sử dụng. Kết quả là không có phương pháp nào của nó sẽ phải được đưa vào để sử dụng chúng. Theo đề xuất của OP là một trong những giải pháp khả thi: "1, bằng cách nào đó, gọi phương thức trực tiếp trên mô-đun mà không bao gồm nó ở bất cứ đâu". Đây là cách nó hoạt động.
thisismydesign
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.