বানর যখন একটি উদাহরণ পদ্ধতিতে প্যাচিং করে, আপনি কি নতুন বাস্তবায়ন থেকে ওভাররাইড পদ্ধতিতে কল করতে পারেন?


443

বলুন আমি একটি বর্গ একটি ক্লাসে একটি পদ্ধতি প্যাচিং করছি, আমি কীভাবে ওভাররাইড পদ্ধতি থেকে ওভাররাইড পদ্ধতিতে কল করতে পারি? মানে কিছুটা ভালো লাগছেsuper

যেমন

class Foo
  def bar()
    "Hello"
  end
end 

class Foo
  def bar()
    super() + " World"
  end
end

>> Foo.new.bar == "Hello World"

প্রথম ফু শ্রেণিটি কি অন্য কোনও এবং দ্বিতীয় ফু এর থেকে উত্তরাধিকারী হওয়া উচিত নয়?
ড্রাকো আটার

1
নাহ, আমি বানর প্যাচিং করছি। আমি আশা করছিলাম সুপারের মতো কিছু থাকবে () আমি মূল পদ্ধতিটি কল করতে পারি
জেমস হলিংওয়ার্থ

1
আপনি যখন তৈরি Foo এবং এর ব্যবহার নিয়ন্ত্রণ করেন না তখন এটি প্রয়োজন Foo::bar। সুতরাং আপনাকে পদ্ধতিটি বানরের প্যাচ করতে হবে।
হালিল Özgür

উত্তর:


1165

সম্পাদনা : আমি এই উত্তরটি মূলত লিখেছিলাম 9 বছর হয়ে গেছে এবং এটি বর্তমান রাখতে কিছু প্রসাধনী শল্যচিকিত্সার দাবি রাখে।

আপনি এখানে সম্পাদনার আগে শেষ সংস্করণটি দেখতে পাবেন ।


আপনি নাম বা কীওয়ার্ড দ্বারা ওভাররাইট করা পদ্ধতিটি কল করতে পারবেন না । বাঁদর প্যাচিং এড়ানো উচিত এবং এর পরিবর্তে উত্তরাধিকারকে অগ্রাধিকার দেওয়া উচিত এর অনেকগুলি কারণ হ'ল স্পষ্টতই আপনি ওভাররাইড পদ্ধতিতে কল করতে পারেন

বানরের প্যাচিং এড়ানো

উত্তরাধিকার

সুতরাং, যদি সম্ভব হয় তবে আপনার এই জাতীয় কিছু পছন্দ করা উচিত:

class Foo
  def bar
    'Hello'
  end
end 

class ExtendedFoo < Foo
  def bar
    super + ' World'
  end
end

ExtendedFoo.new.bar # => 'Hello World'

আপনি যদি Fooবস্তুগুলির সৃষ্টি নিয়ন্ত্রণ করেন তবে এটি কাজ করে। Fooপরিবর্তে একটি তৈরি করতে পারে এমন প্রতিটি স্থান পরিবর্তন করুন ExtendedFoo। আপনি যদি নির্ভরতা ইনজেকশন ডিজাইন প্যাটার্ন , কারখানার পদ্ধতি ডিজাইনের প্যাটার্ন , অ্যাবস্ট্রাক্ট ফ্যাক্টরি ডিজাইন প্যাটার্ন বা সেই লাইনগুলি বরাবর কিছু ব্যবহার করেন তবে এটি আরও ভাল কাজ করে, কারণ সেই ক্ষেত্রে আপনার পরিবর্তনের প্রয়োজন কেবলমাত্র একটি জায়গা।

প্রতিনিধি

আপনি যদি অবজেক্টগুলির সৃষ্টি নিয়ন্ত্রণ না করেনFoo , উদাহরণস্বরূপ কারণ সেগুলি এমন একটি ফ্রেমওয়ার্ক দ্বারা তৈরি করা হয়েছে যা আপনার নিয়ন্ত্রণের বাইরে (উদাহরণস্বরূপ), তারপরে আপনি মোড়ক ডিজাইনের প্যাটার্নটি ব্যবহার করতে পারেন :

require 'delegate'

class Foo
  def bar
    'Hello'
  end
end 

class WrappedFoo < DelegateClass(Foo)
  def initialize(wrapped_foo)
    super
  end

  def bar
    super + ' World'
  end
end

foo = Foo.new # this is not actually in your code, it comes from somewhere else

wrapped_foo = WrappedFoo.new(foo) # this is under your control

wrapped_foo.bar # => 'Hello World'

মূলত, সিস্টেমের সীমানায়, যেখানে Fooবস্তুটি আপনার কোডের মধ্যে আসে, আপনি এটিকে অন্য কোনও বস্তুতে মুড়িয়ে রাখুন এবং তারপরে আপনার কোডের অন্য যে কোনও জায়গায় আসলটির পরিবর্তে সেই বস্তুটি ব্যবহার করুন ।

এটি stdlib এর লাইব্রেরি Object#DelegateClassথেকে সহায়ক পদ্ধতি ব্যবহার করে delegate

"পরিষ্কার" বানর প্যাচিং

Module#prepend: মিক্সিন প্রিপেন্ডিং

বানরের প্যাচিং এড়ানোর জন্য উপরের দুটি পদ্ধতিতে সিস্টেম পরিবর্তন করা দরকার। এই বিভাগটি বানরের প্যাচিংয়ের পছন্দের এবং সর্বনিম্ন আক্রমণাত্মক পদ্ধতিটি দেখায়, সিস্টেমটি পরিবর্তন করা উচিত বিকল্প নয়।

Module#prependএই ব্যবহারের ক্ষেত্রে কমবেশি সমর্থন করার জন্য যুক্ত করা হয়েছিল। ক্লাসের নীচে সরাসরি মেশিনে মিশ্রিত করা বাদে Module#prependএকই কাজ করে:Module#include

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  prepend FooExtensions
end

Foo.new.bar # => 'Hello World'

দ্রষ্টব্য: আমি Module#prependএই প্রশ্নেও কিছুটা লিখেছিলাম : রুবি মডিউল প্রিপেন্ড বনাম ডেরাইভেশন

মেশিন উত্তরাধিকার (ভাঙা)

আমি কিছু লোককে দেখেছি (এবং এটি স্ট্যাকওভারফ্লোতে কেন কাজ করে না সে সম্পর্কে জিজ্ঞাসা করে) এরকম কিছু, অর্থাত্ এটির includeপরিবর্তে মিক্সিন যুক্ত করা prepend:

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  include FooExtensions
end

দুর্ভাগ্যক্রমে, এটি কাজ করবে না। এটি একটি ভাল ধারণা, কারণ এটি উত্তরাধিকার ব্যবহার করে যার অর্থ আপনি ব্যবহার করতে পারেন super। যাইহোক, Module#includemixin সন্নিবেশ উপরে উত্তরাধিকার শ্রেণীবিন্যাসে বর্গ, যার মানে FooExtensions#barকখনোই বলা হবে (এবং যদি এটা হয় বলা হয়, superআসলে না পড়ুন হবে Foo#barবরং করার Object#bar, যা বিদ্যমান নেই) যেহেতু Foo#barসবসময় প্রথম পাওয়া যাবে।

পদ্ধতি মোড়ানো

বড় প্রশ্ন হ'ল: আমরা প্রকৃতbar কোনও পদ্ধতি অবলম্বন না করে কীভাবে পদ্ধতিটি ধরে রাখতে পারি ? ফাংশনাল প্রোগ্রামিংয়ে যেমন উত্তরটি প্রায়শই ঘটে থাকে তেমনি lies আমরা প্রকৃত অবজেক্ট হিসাবে পদ্ধতিটি ধরে রাখি এবং আমরা এবং কেবলমাত্র আমরা সেই অবজেক্টটিকে ধরে রেখেছি তা নিশ্চিত করতে আমরা একটি ক্লোজার (অর্থাত্ একটি ব্লক) ব্যবহার করি :

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  old_bar = instance_method(:bar)

  define_method(:bar) do
    old_bar.bind(self).() + ' World'
  end
end

Foo.new.bar # => 'Hello World'

এটি খুব পরিষ্কার: যেহেতু old_barকেবল একটি স্থানীয় পরিবর্তনশীল, তাই এটি শ্রেণিবদ্ধের শেষে শেষের বাইরে চলে যাবে এবং প্রতিচ্ছবি ব্যবহার করেও এটি যে কোনও জায়গা থেকে অ্যাক্সেস করা অসম্ভব ! এবং যেহেতু Module#define_methodএকটি ব্লক নেয় এবং তাদের আশেপাশের লেক্সিকাল পরিবেশের উপরে ব্লকগুলি বন্ধ হয়ে যায় (যার কারণে আমরা এখানে define_methodপরিবর্তে ব্যবহার defকরছি), এটি (এবং এটি কেবলমাত্র ) এর old_barবাইরেও অ্যাক্সেস পাবে , এমনকি এটি সুযোগ ছাড়িয়ে যাওয়ার পরেও।

সংক্ষিপ্ত ব্যাখ্যা:

old_bar = instance_method(:bar)

এখানে আমরা barপদ্ধতিটি কোনও UnboundMethodমেথড অবজেক্টে মোড়ানো এবং এটি স্থানীয় ভেরিয়েবলের জন্য বরাদ্দ করছি old_bar। এর অর্থ, barওভাররাইট হয়ে যাওয়ার পরেও আমাদের এখন ধরে রাখার উপায় রয়েছে।

old_bar.bind(self)

এটি কিছুটা জটিল। মূলত, রুবিতে (এবং প্রায় সমস্ত একক-প্রেরণা ভিত্তিক ওও ভাষাগুলিতে), একটি পদ্ধতি একটি নির্দিষ্ট রিসিভার অবজেক্টের সাথে আবদ্ধ, যার নাম selfরুবি। অন্য কথায়: একটি পদ্ধতি সর্বদা জানে যে কী ऑब्जेक्टটির জন্য ডাকা হয়েছিল, এটি এটি কী তা জানতে পারে self। তবে, আমরা একটি ক্লাস থেকে পদ্ধতিটি সরাসরি ধরলাম, কীভাবে এটি কীভাবে তা জানবে self?

ঠিক আছে, এটি হয় না, এ কারণেই bindআমাদের UnboundMethodপ্রথমে আমাদের কোনও বস্তুর কাছে যেতে হবে, যা কোনও Methodবস্তুকে আমরা কল করতে পারি তা ফিরিয়ে দেবে । ( UnboundMethodগুলি বলা যায় না, কারণ তাদের না জেনে কী করা উচিত তা তারা জানে না self))

এবং আমরা bindএটি কি ? আমরা কেবল bindএটা নিজেদের, যে যেভাবে এটা আচরণ করবে ঠিক মত মূল barআছে হবে!

শেষ অবধি, আমাদের Methodযেটি ফিরে এসেছে তা কল করতে হবে bind। রুবি ১.৯-তে, ( .()) এর জন্য কিছু নিফটি নতুন সিনট্যাক্স রয়েছে , তবে আপনি যদি 1.8 তে থাকেন তবে আপনি সহজ callপদ্ধতিটি ব্যবহার করতে পারেন ; যে কি .()যাই হোক না কেন অনুবাদ করা হয়।

এখানে আরও কয়েকটি প্রশ্ন দেওয়া হল, যেখানে এই ধারণাগুলির কয়েকটি ব্যাখ্যা করা হয়েছে:

"নোংরা" বানর প্যাচিং

alias_method শৃঙ্খল

আমাদের বানরের প্যাচিংয়ের সাথে আমাদের যে সমস্যা হচ্ছে তা হ'ল আমরা যখন পদ্ধতিটি ওভাররাইট করি তখন পদ্ধতিটি চলে যায়, তাই আমরা এটিকে আর কল করতে পারি না। সুতরাং, আসুন কেবল একটি ব্যাকআপ কপি করা যাক!

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  alias_method :old_bar, :bar

  def bar
    old_bar + ' World'
  end
end

Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'

এটির সাথে সমস্যাটি হ'ল আমরা এখন একটি অতিরিক্ত প্রয়োজনযুক্ত old_barপদ্ধতিতে নামস্থান দূষিত করেছি । এই পদ্ধতিটি আমাদের ডকুমেন্টেশনে প্রদর্শিত হবে, এটি আমাদের আইডিইগুলিতে কোড সমাপ্তিতে প্রদর্শিত হবে, এটি প্রতিবিম্বের সময় প্রদর্শিত হবে। এছাড়াও, এটি এখনও বলা যেতে পারে, তবে সম্ভবত আমরা এটি বানরকে ছুঁড়ে মারলাম, কারণ এটির আচরণটি আমরা প্রথমে পছন্দ করি না, তাই আমরা অন্য লোকেরা এটি কল করতে চাই না।

এটিতে কিছু অযাচিত বৈশিষ্ট্য রয়েছে তবুও দুর্ভাগ্যক্রমে এটি অ্যাসিভসপোর্টের মাধ্যমে জনপ্রিয় হয়ে উঠেছে Module#alias_method_chain

একদিকে: পরিমার্জন

আপনার কেবলমাত্র কয়েকটি নির্দিষ্ট জায়গায় ভিন্ন আচরণের প্রয়োজন এবং পুরো সিস্টেম জুড়ে নয়, আপনি বানর প্যাচকে একটি নির্দিষ্ট সুযোগে সীমাবদ্ধ করতে আপনি পরিশোধনগুলি ব্যবহার করতে পারেন। আমি Module#prependউপরের উদাহরণটি ব্যবহার করে এটি এখানে প্রদর্শন করতে যাচ্ছি :

class Foo
  def bar
    'Hello'
  end
end 

module ExtendedFoo
  module FooExtensions
    def bar
      super + ' World'
    end
  end

  refine Foo do
    prepend FooExtensions
  end
end

Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!

using ExtendedFoo
# Activate our Refinement

Foo.new.bar # => 'Hello World'
# There it is!

আপনি এই প্রশ্নে সংশোধনগুলি ব্যবহারের আরও পরিশীলিত উদাহরণ দেখতে পারেন: নির্দিষ্ট পদ্ধতির জন্য বানর প্যাচকে কীভাবে সক্ষম করবেন?


পরিত্যক্ত ধারণা

রুবি সম্প্রদায়টি বসতি স্থাপনের আগে Module#prepend, চারপাশে একাধিক বিভিন্ন ধারণা ভেসে উঠছিল যে আপনি মাঝে মাঝে পুরানো আলোচনায় রেফারেন্স দেখতে পাবেন। এই সমস্ত দ্বারা গ্রহন করা হয় Module#prepend

পদ্ধতি সংযুক্তকারী

একটি ধারণা ছিল সিএলওএসের পদ্ধতিতে সংযুক্তকারীগুলির ধারণা। এটি মূলত অ্যাসপেক্ট-ওরিয়েন্টেড প্রোগ্রামিংয়ের একটি উপসেটের খুব হালকা ভার্সন।

মত সিনট্যাক্স ব্যবহার

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end

  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

আপনি barপদ্ধতিটি কার্যকর করতে "প্রবেশ করতে" সক্ষম হবেন ।

তবে কীভাবে এবং কীভাবে আপনি barএর মধ্যে ফেরতের মান অ্যাক্সেস পান তা পুরোপুরি পরিষ্কার নয় bar:after। সম্ভবত আমরা (আব) superকীওয়ার্ডটি ব্যবহার করতে পারি?

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar:after
    super + ' World'
  end
end

প্রতিস্থাপন

prependপূর্বের সংযোগকারীটি একটি ওভাররাইডিং পদ্ধতির সাথে মিক্সিন আইংয়ের সমতুল্য যা পদ্ধতির superএকেবারে শেষে কল করে । অনুরূপভাবে, সংযুক্তিটি prependএকটি ওভাররাইডিং পদ্ধতির সাথে মিক্সিন আইংয়ের সমতুল্য যা পদ্ধতির superএকেবারে প্রারম্ভে কল করে ।

কল করার আগে এবং পরে আপনি স্টাফও superকরতে পারেন, আপনি superএকাধিকবার কল করতে পারেন এবং পদ্ধতি সংযোজকগুলির চেয়ে আরও শক্তিশালী করে উভয়ই পুনরুদ্ধার superমানটি পুনরুদ্ধার ও হেরফের করতে পারেন prepend

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end
end

# is the same as

module BarBefore
  def bar
    # will always run before bar, when bar is called
    super
  end
end

class Foo
  prepend BarBefore
end

এবং

class Foo
  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

# is the same as

class BarAfter
  def bar
    original_return_value = super
    # will always run after bar, when bar is called
    # has access to and can change bar’s return value
  end
end

class Foo
  prepend BarAfter
end

old শব্দ

এই ধারণাটির অনুরূপ একটি নতুন কীওয়ার্ড যুক্ত হয়েছে super, যা আপনাকে ওভাররাইট পদ্ধতিতে একইভাবে superকল করতে দেয় যাতে আপনি ওভাররাইড পদ্ধতিতে কল করতে পারেন :

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

এর সাথে মুখ্য সমস্যাটি এটি পিছনের দিকে অসম্পূর্ণ: আপনার যদি পদ্ধতিটি কল করা হয় old, আপনি আর এটি কল করতে পারবেন না!

প্রতিস্থাপন

superprependএড মিক্সিনে একটি ওভাররাইড পদ্ধতিতে মূলত oldএই প্রস্তাবের মতোই ।

redef শব্দ

উপরের মতই, তবে ওভাররাইটন পদ্ধতিতে কল করার জন্য এবং defএকা রেখে নতুন কীওয়ার্ড যুক্ত করার পরিবর্তে আমরা পুনরায় সংজ্ঞায়িত পদ্ধতিগুলির জন্য একটি নতুন কীওয়ার্ড যুক্ত করি । এটি পিছনের দিকে সামঞ্জস্যপূর্ণ, কারণ সিনট্যাক্স বর্তমানে যাইহোক অবৈধ:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

দুটি নতুন কীওয়ার্ড যুক্ত করার পরিবর্তে আমরা superঅভ্যন্তরের অর্থটি আবারও সংজ্ঞায়িত করতে পারি redef:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    super + ' World'
  end
end

Foo.new.bar # => 'Hello World'

প্রতিস্থাপন

redefএকটি পদ্ধতি ইনিং একটি এড মিক্সিনে পদ্ধতিটিকে ওভাররাইড করার সমতুল্য prependsuperওভাররাইড পদ্ধতিতে superবা oldএই প্রস্তাব মতো আচরণ করে ।


@ জার্গ ডব্লু মিটাগ, পদ্ধতিটি মোড়ানো পদ্ধতির থ্রেড কি নিরাপদ? দুটি সমবর্তী থ্রেড bindএকই old_methodভেরিয়েবলের কল করলে কী হয় ?
হরিশ শেঠি

1
@KandadaBoggu: আমি জিনিসটা কি চেষ্টা করছি ঠিক আপনি যে বলতে :-) যাইহোক, আমি নিশ্চিত এটা কোন কম থ্রেড-নিরাপদ রুবি মধ্যে metaprogramming অন্য কোন ধরনের চেয়ে আছি। বিশেষত, প্রতিটি কলই UnboundMethod#bindনতুন, ভিন্ন Method, আবার ফিরে আসবে , তাই আপনি বিভিন্ন থ্রেড থেকে একই সাথে আপনি দুটি বার বা একই সাথে দু'বার কল করেছেন কিনা তা বিবেচনা না করেই কোনও বিরোধ দেখা দিচ্ছে না।
Jörg ডব্লু মিট্টাগ

1
আমি রুবি এবং রেল শুরু করার পর থেকেই এই জাতীয় প্যাচিংয়ের বিষয়ে ব্যাখ্যা খুঁজছিলাম। দুর্দান্ত উত্তর! আমার জন্য কেবল অনুপস্থিত ছিল ক্লাস_ওয়ালাল বনাম একটি ক্লাস পুনরায় চালু করার বিষয়ে একটি নোট। এটি এখানে: stackoverflow.com/a/10304721/188462
ইউজিন

1
রুবি ২.০ এর সংশোধন রয়েছে ব্লগ.ইউইওয়ারকস.কম
২০১২ /

5
আপনি কোথায় পাবেন oldএবং redef? আমার 2.0.0 এগুলি নেই। আহা, অন্য প্রতিযোগিতামূলক ধারণাগুলি যে এটি রুবিকে তৈরি করে নি সেগুলি
নাকিলন

12

এলিয়াসিং পদ্ধতিগুলি দেখুন, এটি পদ্ধতিটির নতুন নামকরণের মতো।

আরও তথ্য এবং একটি সূচনা পয়েন্টের জন্য এই প্রতিস্থাপন পদ্ধতিগুলির নিবন্ধটি দেখুন (বিশেষত প্রথম অংশ)। রুবি এপিআই ডক্স , এছাড়াও উপলব্ধ (ক কম সম্প্রসারিত) উদাহরণ।


-1

যে ক্লাসটি ওভাররাইড তৈরি করবে সেগুলি অবশ্যই মূল পদ্ধতি ধারণ করে এমন ক্লাসের পরে পুনরায় লোড করতে হবে, সুতরাং requireফাইলটিতে এটি ওভাররাইড তৈরি করবে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.