কম্পিউটার

ডিকপলিং রুবি:ডেলিগেশন বনাম ডিপেনডেন্সি ইনজেকশন

অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং-এ , একটি বস্তু প্রায়ই কাজ করার জন্য অন্য বস্তুর উপর নির্ভর করে।

উদাহরণস্বরূপ, যদি আমি আর্থিক প্রতিবেদন চালানোর জন্য একটি সাধারণ ক্লাস তৈরি করি:

class FinanceReport
  def net_income
    FinanceApi.gross_income - FinanceApi.total_costs
  end
end

আমরা বলতে পারি যে FinanceReport নির্ভর করে FinanceApi , যা এটি একটি বাহ্যিক পেমেন্ট প্রসেসর থেকে তথ্য সংগ্রহ করতে ব্যবহার করে।

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

ডিপেন্ডেন্সি ইনজেকশনের সাথে, আমরা স্পষ্টভাবে FinanceApi উল্লেখ করি না FinanceReport এর ভিতরে . পরিবর্তে, আমরা একটি যুক্তি হিসাবে এটি পাস. আমরা ইনজেক্ট করি এটা।

ডিপেন্ডেন্সি ইনজেকশন ব্যবহার করে, আমাদের ক্লাস হয়ে যায়:

class FinanceReport
  def net_income(financials)
    financials.gross_income - financials.total_costs
  end
end

এখন আমাদের ক্লাসের কোন জ্ঞান নেই যে FinanceApi বস্তু এমনকি বিদ্যমান! আমরা যে কোনো বস্তু পাস করতে পারি এটি যতক্ষণ পর্যন্ত এটি gross_income প্রয়োগ করে এবং total_costs .

এর বেশ কিছু সুবিধা রয়েছে:

  • আমাদের কোড এখন FinanceApi এর সাথে কম "কাপল" হয়েছে .
  • আমরা FinanceApi ব্যবহার করতে বাধ্য হচ্ছি একটি পাবলিক ইন্টারফেসের মাধ্যমে।
  • আমরা এখন আমাদের পরীক্ষায় একটি মক বা স্টাব অবজেক্টে পাস করতে পারি যাতে আমাদের আসল API তে আঘাত করতে না হয়।

বেশিরভাগ বিকাশকারীরা নির্ভরতা ইনজেকশন বিবেচনা করে সাধারণভাবে একটি ভাল জিনিস হতে (আমিও!) যাইহোক, সমস্ত কৌশলের মত, এখানেও ট্রেড-অফ আছে।

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

যদি কলগুলি অন্যথায় self-এ চলে যেত , তারপর আমরা কোডটিকে আরও ভার্বোস করেছি। অবজেক্ট-ওরিয়েন্টেড "একটি বস্তুতে একটি বার্তা পাঠান এবং এটিকে কাজ করতে দিন" দৃষ্টান্ত ব্যবহার করার পরিবর্তে, আমরা নিজেদেরকে আরও কার্যকরী "ইনপুট -> আউটপুট" দৃষ্টান্তে চলে যাচ্ছি।

এটি শেষ কেস (কলগুলিকে পুনঃনির্দেশ করা যা self-এ চলে যেত ) যেটা আমি আজ দেখতে চাই। আমি এই পরিস্থিতিগুলির জন্য নির্ভরতা ইনজেকশনের একটি সম্ভাব্য বিকল্প উপস্থাপন করতে চাই:বেস ক্লাসটি গতিশীলভাবে পরিবর্তন করুন (একরকম)।

সমস্যা সমাধান

আসুন একটি মুহূর্ত ব্যাক আপ করি এবং সেই সমস্যাটি দিয়ে শুরু করি যা আমাকে এই পথ দিয়ে শুরু করে দিয়েছিল:PDF রিপোর্ট।

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

আমরা সম্মানিত prawn ব্যবহার করছি Prawn::Document থেকে প্রতিটি রিপোর্টের নিজস্ব রুবি অবজেক্ট সহ এই পিডিএফগুলি তৈরি করার জন্য রত্ন। .

এরকম কিছু:

class CostReport < Prawn::Document
  def initialize(...)
    ...
  end

  def render
    text "Cost Report"
    move_down 20
    ...
  end

এ পর্যন্ত সব ঠিকই. কিন্তু এখানে ঘষা হল:ক্লায়েন্ট একটি "ওভারভিউ" রিপোর্ট চায় যা এই সমস্ত অন্যান্য রিপোর্টের অংশগুলি অন্তর্ভুক্ত করে .

সমাধান 1:নির্ভরতা ইনজেকশন

পূর্বে উল্লিখিত হিসাবে, এই ধরনের সমস্যার একটি সাধারণ সমাধান হল ডিপেন্ডেন্সি ইনজেকশন ব্যবহার করার জন্য কোড রিফ্যাক্টর করা। অর্থাৎ, এই সমস্ত রিপোর্ট থাকার পরিবর্তে self-এ কল করুন , আমরা পরিবর্তে একটি যুক্তি হিসাবে আমাদের PDF নথিতে পাস করব।

এটি আমাদের আরও কিছু দেবে:

class CostReport < Prawn::Document
...
  def title(pdf = self)
    pdf.text "Cost Report"
    pdf.move_down 20
    ...
  end
end

এটি কাজ করে, কিন্তু এখানে কিছু ওভারহেড আছে। একটি জিনিসের জন্য, প্রতিটি একক অঙ্কন পদ্ধতিতে এখন pdf নিতে হবে যুক্তি, এবং prawn-এর প্রতি একক কল এখন এই pdf দিয়ে যেতে হবে যুক্তি।

ডিপেনডেন্সি ইনজেকশনের কিছু সুবিধা রয়েছে:এটি আমাদের সিস্টেমে ডিকপলড কম্পোনেন্টের দিকে ঠেলে দেয় এবং ইউনিট টেস্টিংকে সহজ করার জন্য আমাদের মক বা স্টাবগুলিতে পাস করতে দেয়।

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

পরীক্ষা করাও এখানে একটি বড় উদ্বেগের বিষয় নয়, কারণ আমাদের ক্ষেত্রে স্বয়ংক্রিয় পরীক্ষার সাথে জেনারেট করা পিডিএফ রিপোর্টগুলি পরীক্ষা করা খুবই কষ্টকর।

তাই ডিপেনডেন্সি ইনজেকশন আমাদের পছন্দের আচরণ দেয় কিন্তু আমাদের জন্য ন্যূনতম সুবিধার সাথে অতিরিক্ত ওভারহেডও চালু করে। আসুন আরেকটি বিকল্পের দিকে নজর দেওয়া যাক।

সমাধান 2:অর্পণ

রুবির স্ট্যান্ডার্ড লাইব্রেরি আমাদের SimpleDelegator প্রদান করে ডেকোরেটর প্যাটার্ন বাস্তবায়নের একটি সহজ উপায় হিসাবে। আপনি আপনার অবজেক্ট কনস্ট্রাক্টরের কাছে পাঠান এবং তারপর ডেলিগেটরের কাছে যেকোন পদ্ধতির কল আপনার অবজেক্টে ফরওয়ার্ড করা হয়।

SimpleDelegator ব্যবহার করে , আমরা একটি বেস রিপোর্ট ক্লাস তৈরি করতে পারি যা prawn এর চারপাশে মোড়ানো .

class PrawnWrapper < SimpleDelegator
  def initialize(document: nil)
    document ||= Prawn::Document.new(...)
    super(document)
  end
end

তারপরে আমরা এই ক্লাস থেকে উত্তরাধিকারী হওয়ার জন্য আমাদের প্রতিবেদনগুলি আপডেট করতে পারি, এবং সেগুলি এখনও আমাদের ইনিশিয়ালাইজারে তৈরি ডিফল্ট নথি ব্যবহার করে আগের মতোই কাজ করবে। যাদুটি ঘটে যখন আমরা এটিকে আমাদের ওভারভিউ-এ ব্যবহার করি রিপোর্ট:

class OverviewReport < PrawnWrapper
  ...
  def render
    sales = SaleReport.new(..., document: self)
    sales.sales_table
    costs = CostReport.new(..., document: self)
    costs.costs_pie_chart
    ...
  end
end

এখানে SaleReport#sales_table এবং CostReport#costs_pie_chart অপরিবর্তিত থাকবে, কিন্তু তাদের কল prawn (যেমন, text(...) , move_down 20 , ইত্যাদি) এখন OverviewReport-এ ফরোয়ার্ড করা হচ্ছে SimpleDelegator এর মাধ্যমে আমরা তৈরি করেছি।

আচরণের পরিপ্রেক্ষিতে, আমরা মূলত এটিকে এমনভাবে তৈরি করেছি যেন SalesReport এখন OverviewReport এর একটি সাবক্লাস . আমাদের ক্ষেত্রে, এর মানে হল prawn-এ সমস্ত কল এর API এখন SalesReport -> OverviewReport -> Prawn::Document যান .

কিভাবে সাধারণ প্রতিনিধি কাজ করে

উপায় SimpleDelegator হুডের নিচে কাজ করে মূলত রুবির method_missing ব্যবহার করা অন্য বস্তুতে কল ফরওয়ার্ড করার কার্যকারিতা।

তাই SimpleDelegator (বা এটির একটি সাবক্লাস) একটি পদ্ধতি কল গ্রহণ করে। যদি এটি সেই পদ্ধতি প্রয়োগ করে, দুর্দান্ত; এটি অন্য যেকোন বস্তুর মতোই এটি কার্যকর করবে। তবে , যদি সেই পদ্ধতিটি সংজ্ঞায়িত না থাকে, তাহলে এটি method_missing হিট করবে . method_missing তারপর call করার চেষ্টা করবে সেই পদ্ধতিটি অবজেক্টের কনস্ট্রাক্টরকে দেওয়া হয়েছে।

একটি সহজ উদাহরণ:

require 'simple_delegator'
class Thing
  def one
    'one'
  end
  def two
    'two'
  end
end

class ThingDecorator < SimpleDelegator
  def two
    'three!'
  end
end

ThingDecorator.new(Thing.new).one #=> "one"
ThingDecorator.new(Thing.new).two #=> "three!"

SimpleDelegator সাবক্লাস করে আমাদের নিজস্ব ThingDecorator দিয়ে এখানে ক্লাস, আমরা কিছু পদ্ধতি ওভাররাইট করতে পারি এবং অন্যদেরকে ডিফল্ট Thing-এ যেতে দিতে পারি। বস্তু।

উপরের তুচ্ছ উদাহরণটি আসলে SimpleDelegator করে না ন্যায়বিচার, যদিও। আপনি এই কোডটি দেখতে পারেন এবং আমাকে খুব ভালভাবে বলতে পারেন, “Thing সাবক্লাসিং করে না আমাকে একই ফলাফল দাও?"

হ্যাঁ, হ্যাঁ এটা করে। কিন্তু এখানে মূল পার্থক্য হল:SimpleDelegator এটির কনস্ট্রাক্টরে একটি আর্গুমেন্ট হিসাবে এটি অর্পণ করা বস্তুটিকে নেয়। এর মানে আমরা রানটাইমে বিভিন্ন অবজেক্টে পাস করতে পারি .

এটিই ব্যবহার করে কলগুলিকে একটি prawn-এ পুনঃনির্দেশিত করতে দেয়৷ উপরের সমাধান 2 এ অবজেক্ট। যদি আমরা একটি একক প্রতিবেদন বলি, prawn কলগুলি কনস্ট্রাক্টরে তৈরি একটি নতুন নথিতে যায়। ওভারভিউ রিপোর্ট, যাইহোক, এটি পরিবর্তন করতে পারে যাতে prawn কল করা যায় তার এ ফরোয়ার্ড করা হয়৷ নথি।

উপসংহার

ডিপেন্ডেন্সি ইনজেকশন সম্ভবত অধিকাংশের সর্বোত্তম সমাধান ডিকপলিং সমস্যা সবচেয়ে সময়ের।

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

রুবির সমস্ত জিনিসের মতো, সর্বদা অন্য উপায় আছে . আমি প্রায়শই এই সমাধানের জন্য পৌঁছাতে পারি না, তবে এই পরিস্থিতিগুলির জন্য আপনার রুবি টুলবেল্টে এটি অবশ্যই একটি চমৎকার সংযোজন৷


  1. রুবিতে বিটওয়াইজ হ্যাক

  2. রুবিতে ল্যাম্বডাস ব্যবহার করা

  3. রুবিতে ইউনিকোড স্বাভাবিককরণ

  4. রুবি 2.6-এ 9টি নতুন বৈশিষ্ট্য