क्या मैं इसे शामिल किए बिना रूबी मॉड्यूल पर एक इंस्टेंस विधि लागू कर सकता हूं?


181

पृष्ठभूमि:

मेरे पास एक मॉड्यूल है जो कई उदाहरण विधियों की घोषणा करता है

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

  def format_text(x); ...
end

और मैं इनमें से कुछ तरीकों को एक कक्षा के भीतर से कॉल करना चाहता हूं। आप आम तौर पर माणिक में ऐसा कैसे करते हैं:

class UsefulWorker
  include UsefulThings

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

संकट

include UsefulThingsसभी तरीकों से लाता है UsefulThings। इस मामले में मैं केवल चाहते हैं format_textऔर स्पष्ट रूप से नहीं करना चाहते हैं get_fileऔर delete_file

मैं इसके कई संभावित समाधान देख सकता हूं:

  1. किसी भी तरह से बिना किसी तरह के मॉड्यूल पर सीधे विधि लागू करना
    • मुझे नहीं पता कि यह कैसे किया जा सकता है। (इसलिए यह सवाल)
  2. किसी तरह शामिल है Usefulthingsऔर केवल यह कुछ तरीकों में ले आओ
    • मुझे यह भी नहीं पता कि यह कैसे किया जा सकता है
  3. एक प्रॉक्सी क्लास बनाएं, UsefulThingsउसमें शामिल करें , फिर format_textउस प्रॉक्सी इंस्टेंस को सौंपें
    • यह काम करेगा, लेकिन अनाम प्रॉक्सी कक्षाएं एक हैक हैं। छी।
  4. मॉड्यूल को 2 या अधिक छोटे मॉड्यूल में विभाजित करें
    • यह भी काम करेगा, और शायद सबसे अच्छा समाधान है जिसके बारे में मैं सोच सकता हूं, लेकिन मैं इसे टालना पसंद करूंगा क्योंकि मैं दर्जनों और दर्जनों मॉड्यूलों के प्रसार के साथ समाप्त हो जाऊंगा - इसे प्रबंधित करना बोझ होगा

एकल मॉड्यूल में बहुत सारे असंबंधित कार्य क्यों हैं? यह ApplicationHelperएक रैलिंग ऐप से है, जिसे हमारी टीम ने डंपिंग ग्राउंड के रूप में तय किया है, जो किसी भी अन्य जगह से संबंधित होने के लिए पर्याप्त नहीं है। ज्यादातर स्टैंडअलोन उपयोगिता विधियां जो हर जगह इस्तेमाल की जाती हैं। मैं इसे अलग-अलग सहायकों में तोड़ सकता था, लेकिन उनमें से 30 होंगे, सभी 1 विधि के साथ ... यह अनुत्पादक लगता है


यदि आप 4 वें दृष्टिकोण (मॉड्यूल को विभाजित करना) लेते हैं, तो आप इसे बना सकते हैं ताकि एक मॉड्यूल हमेशा दूसरे के ट्रिगर करने के लिए Module#includedकॉलबैक का उपयोग करके दूसरे को स्वचालित रूप से शामिल करे include। इस format_textविधि को अपने स्वयं के मॉड्यूल में स्थानांतरित किया जा सकता है, क्योंकि यह अपने आप में उपयोगी लगता है। इससे प्रबंधन थोड़ा कम बोझ होगा।
बाटकिंस

मैं मॉड्यूल कार्यों के जवाब में सभी संदर्भों से हैरान हूं। माना कि आपके पास है module UT; def add1; self+1; end; def add2; self+2; end; endऔर आप उपयोग करना चाहते हैं add1लेकिन add2कक्षा में नहीं Fixnum। इसके लिए मॉड्यूल फ़ंक्शन करने में कैसे मदद मिलेगी? क्या मैं कुछ भूल रहा हूँ?
कैरी स्वेवेलैंड

जवाबों:


135

यदि किसी मॉड्यूल पर एक विधि को एक मॉड्यूल फ़ंक्शन में बदल दिया जाता है, तो आप इसे मॉड की तरह कॉल कर सकते हैं जैसे कि इसे घोषित किया गया था

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

नीचे मॉड्यूल_फंक्शन दृष्टिकोण किसी भी वर्ग को तोड़ने से बचना होगा जिसमें सभी मॉड शामिल हैं।

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  

हालाँकि, मैं उत्सुक हूं कि असंबंधित कार्यों का एक सेट पहली जगह में एक ही मॉड्यूल के भीतर क्यों निहित है?

यह दिखाने के लिए संपादित किया जाता है कि इसमें अभी भी काम शामिल है अगर public :fooबाद में कॉल किया जाएmodule_function :foo


1
एक तरफ के रूप में, एक module_functionविधि को एक निजी में बदल देता है, जो अन्य कोड को तोड़ देगा - अन्यथा यह स्वीकृत उत्तर होगा
ओरियन एडवर्ड्स

मैंने सभ्य काम किया और अपने कोड को अलग-अलग मॉड्यूल में रिफलेक्ट किया। यह उतना बुरा नहीं था जितना मैंने सोचा था कि यह हो सकता है। आपका उत्तर अभी भी इसे सबसे सही ढंग से हल करेगा मेरे मूल बाधाओं को गलत, इसलिए स्वीकार किया!
ओरियन एडवर्ड्स

@dgtized संबंधित फ़ंक्शन एक मॉड्यूल में हर समय समाप्त हो सकते हैं, इसका मतलब यह नहीं है कि मैं उन सभी के साथ अपने नाम स्थान को प्रदूषित करना चाहता हूं। एक सरल उदाहरण यदि कोई Files.truncateहै Strings.truncateऔर मैं स्पष्ट रूप से एक ही कक्षा में दोनों का उपयोग करना चाहता हूं। हर बार जब मुझे एक विशिष्ट विधि या उदाहरण की आवश्यकता होती है तो एक नया वर्ग / उदाहरण बनाना एक अच्छा तरीका नहीं है, हालांकि मैं रूबी देव नहीं हूं।
TW19Strrob

146

मुझे लगता है कि सिर्फ थ्रो-दूर सिंगल कॉल (मौजूदा मॉड्यूल्स में बदलाव किए बिना या नए बनाने के लिए) करने का सबसे छोटा तरीका इस प्रकार होगा:

Class.new.extend(UsefulThings).get_file

2
Erb फ़ाइलों के लिए बहुत उपयोगी है। html.erb, या js.erb। धन्यवाद ! मुझे आश्चर्य है कि अगर यह प्रणाली स्मृति को बर्बाद कर देती है
अल्बर्ट कैटला

5
@ AlbertCatalà मेरे परीक्षणों और stackoverflow.com/a/23645285/54247 के अनुसार गुमनाम कक्षाएं सब कुछ के रूप में एकत्र की जाती हैं, इसलिए यह स्मृति को बर्बाद नहीं करना चाहिए।
डॉल्ज़ेंको

1
यदि आप प्रॉक्सी के रूप में एक अनाम वर्ग बनाना पसंद नहीं करते हैं, तो आप विधि के लिए प्रॉक्सी के रूप में एक ऑब्जेक्ट का उपयोग भी कर सकते हैं। Object.new.extend(UsefulThings).get_file
3limin4t0r

83

ऐसा करने का एक और तरीका है यदि आप मॉड्यूल का उपयोग "स्वयं" करते हैं 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

27
और उस मामले के लिए जहाँ आप इसके मालिक नहीं हैं: UsefulThings.send: मॉड्यूल_फंक्शन: b
डस्टिन

3
मॉड्यूल_फंक्शन विधि को एक निजी में परिवर्तित करता है (वैसे भी यह मेरे आईआरबी में करता है), जो अन्य कॉलर्स को तोड़ देगा :-(
ओरियन एडवर्ड्स

मुझे वास्तव में यह दृष्टिकोण पसंद है, कम से कम मेरे उद्देश्यों के लिए। अब मैं ModuleName.method :method_nameएक विधि ऑब्जेक्ट प्राप्त करने के लिए और इसके माध्यम से कॉल कर सकता हूं method_obj.call। अन्यथा मुझे उस विधि को मूल वस्तु के उदाहरण से बांधना होगा, जो कि मूल वस्तु के मॉड्यूल होने पर संभव नहीं है। ओरियन एडवर्ड्स के जवाब में, module_functionमूल उदाहरण विधि को निजी बनाता है। ruby-doc.org/core/classes/Module.html#M001642
जॉन

2
ओरियन - मेरा मानना ​​है कि यह सच नहीं है। रूबी- doc.org/docs/ProgrammingRuby/html/… के अनुसार , मॉड्यूल_फंक्शन "नामित विधियों के लिए मॉड्यूल फ़ंक्शन बनाता है। इन कार्यों को मॉड्यूल के साथ एक रिसीवर के रूप में कहा जा सकता है, और कक्षाओं में मिक्स तरीकों के रूप में भी उपलब्ध हो सकता है जो मिश्रण करते हैं। मॉड्यूल। मॉड्यूल फ़ंक्शन मूल की प्रतियां हैं, और इसलिए इसे स्वतंत्र रूप से बदला जा सकता है। उदाहरण-विधि संस्करण निजी बनाये जाते हैं। यदि बिना किसी तर्क के साथ उपयोग किया जाता है, तो बाद में परिभाषित तरीके मॉड्यूल फ़ंक्शन बन जाते हैं। "
रयान क्रिस्पिन हेनीज़ 19

2
आप इसे इस रूप में भी परिभाषित कर सकते हैंdef self.a; puts 'aaay'; end
तिलो

17

यदि आप इन विधियों को किसी अन्य वर्ग में मॉड्यूल को शामिल किए बिना कॉल करना चाहते हैं, तो आपको उन्हें मॉड्यूल विधियों के रूप में परिभाषित करने की आवश्यकता है:

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

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

और फिर आप उन्हें कॉल कर सकते हैं

UsefulThings.format_text("xxx")

या

UsefulThings::format_text("xxx")

लेकिन वैसे भी मैं आपको सलाह दूंगा कि आप केवल संबंधित विधियों को एक मॉड्यूल या एक कक्षा में रखें। यदि आपको समस्या है कि आप मॉड्यूल से सिर्फ एक विधि को शामिल करना चाहते हैं तो यह एक खराब कोड गंध की तरह लगता है और असंबंधित तरीकों को एक साथ रखना अच्छा नहीं है रूबी शैली।


17

मॉड्यूल सहित (और मध्यस्थ वस्तुओं को बनाए बिना) एक मॉड्यूल उदाहरण विधि लागू करने के लिए:

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

इस दृष्टिकोण के साथ सावधान रहें: स्वयं के लिए बाध्य करना हर चीज के साथ विधि प्रदान नहीं कर सकता है जो इसकी अपेक्षा करता है। उदाहरण के लिए, शायद format_textमॉड्यूल द्वारा प्रदान की गई किसी अन्य विधि के अस्तित्व को मानता है, जो (आम तौर पर) मौजूद नहीं होगी।
नाथन

यह तरीका है, किसी भी मॉड्यूल को लोड कर सकता है, भले ही मॉड्यूल समर्थन विधि को सीधे लोड किया जा सकता है। यहां तक ​​कि मॉड्यूल स्तर में बदलाव करना बेहतर है। लेकिन कुछ मामलों में, यह लाइन वही है जो पूछने वाले चाहते हैं।
ट्विंडाई

6

सबसे पहले, मैं आपको आवश्यक चीजों में मॉड्यूल को तोड़ने की सलाह दूंगा। लेकिन आप हमेशा एक वर्ग बना सकते हैं जो आपके आह्वान के लिए विस्तृत हो:

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. यदि आप हमेशा उन्हें "योग्य", स्टैंडअलोन तरीके से कॉल करना चाहते हैं (UsefulThings.get_file), तो बस उन्हें स्थिर बनाएं जैसा कि दूसरों ने बताया है,

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

ख। यदि आप अभी भी मिक्सिन के दृष्टिकोण को एक ही स्थिति में रखना चाहते हैं, साथ ही साथ एकतरफा गतिरोध को भी रोक सकते हैं, तो आपके पास एक-लाइनर मॉड्यूल हो सकता है जो मिक्सिन के साथ ही विस्तारित होता है:

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

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

तो दोनों काम करता है:

  UsefulThings.get_file()       # one off

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

IMHO यह module_functionहर एक विधि की तुलना में क्लीनर है - अगर सभी चाहते हैं।


extend selfएक सामान्य मुहावरा है।
स्माइली

4

जैसा कि मैंने प्रश्न को समझा है, आप एक मॉड्यूल के कुछ तरीकों को कक्षा में मिलाना चाहते हैं।

आइए यह विचार करके शुरू करें कि मॉड्यूल # में कैसे कार्य शामिल हैं । मान लें कि हमारे पास एक मॉड्यूल UsefulThingsहै जिसमें दो उदाहरण विधियाँ हैं:

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

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

और Fixnum includeउस मॉड्यूल:

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

हम देखते है कि:

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

क्या आप UsefulThings#add3ओवरराइड करने की उम्मीद कर रहे थे Fixnum#add3, ताकि वह 1.add3वापस आ जाए 4? इस पर विचार करो:

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

जब वर्ग includeमॉड्यूल होता है, तो मॉड्यूल वर्ग का सुपरक्लास बन जाता है। इसलिए, इनहेरिटेंस कैसे काम करता है, add3का एक उदाहरण के लिए भेजने का Fixnumकारण बनता Fixnum#add3है, लौटने का कारण होगा dog

अब हम एक विधि जोड़ने :add2के लिए UsefulThings:

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

अब हम केवल तरीकों की इच्छा Fixnumरखते हैं और । ऐसा कर रहे हैं, हम ऊपर के रूप में एक ही परिणाम प्राप्त करने की उम्मीद है।includeadd1add3

मान लीजिए, जैसा कि ऊपर, हम निष्पादित करते हैं:

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

इसका परिणाम क्या है? अवांछित पद्धति :add2को जोड़ा जाता है Fixnum, :add1जोड़ा जाता है और, जिन कारणों से मैंने ऊपर बताया है, :add3उन्हें जोड़ा नहीं गया है। तो हमें बस इतना करना है undef :add2। हम एक सरल सहायक विधि के साथ ऐसा कर सकते हैं:

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

जो हम इस तरह से आह्वान करते हैं:

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

फिर:

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

जो हम चाहते हैं परिणाम है।


2

यकीन नहीं होता कि किसी को 10 साल बाद भी इसकी जरूरत है लेकिन मैंने ईजेंक्लस का इस्तेमाल कर इसे हल किया।

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

लगभग 9 वर्षों के बाद यहाँ एक सामान्य समाधान है:

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

तरीकों को परिभाषित करने के बाद मॉड्यूल को शामिल करने के लिए आपको जिस दुर्भाग्यपूर्ण चाल को लागू करना होगा। वैकल्पिक रूप से आप इसे संदर्भ के रूप में परिभाषित करने के बाद भी शामिल कर सकते हैं ModuleIncluded.send(:include, CreateModuleFunctions)

या आप इसे रिफ्लेक्ट_यूटिल्स रत्न के माध्यम से उपयोग कर सकते हैं ।

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

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

खैर, आपका दृष्टिकोण अधिकांश प्रतिक्रियाओं की तरह है जिसे हम यहां देख सकते हैं मूल समस्या का समाधान नहीं करते हैं और सभी तरीकों को लोड करते हैं। एकमात्र अच्छा उत्तर मूल मॉड्यूल से विधि को अनबाइंड करना है और इसे लक्षित वर्ग में बाँधना है, क्योंकि @renier पहले से ही 3 साल पहले जवाब देता है।
जोएल अजमेरा

@JoelAZEMAR मुझे लगता है कि आप इस समाधान को गलत समझ रहे हैं। इसे उस मॉड्यूल में जोड़ा जाना है जिसका आप उपयोग करना चाहते हैं। परिणामस्वरूप इसका उपयोग करने के लिए इसके किसी भी तरीके को शामिल नहीं करना होगा। ओपी द्वारा संभावित समाधानों में से एक के रूप में सुझाव दिया गया है: "1, किसी भी तरह से बिना किसी भी तरीके के सीधे मॉड्यूल पर विधि लागू करें"। यह इस तरह काम करता है।
thisismydesign
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.