আমার সাম্প্রতিক আর্টিকেলে, আমি রেইলস ৫.১-এ একটি চমৎকার নতুন ফিচার উল্লেখ করেছিলাম, delegate_missing_to। delegate_missing_to দিয়ে, যেকোনো মেথড যা আপনি একটি অবজেক্টে খুঁজে পান না, সেটি অন্য একটি অবজেক্টে কল হয়:
class Player
delegate_missing_to :@user
def initialize(user)
@user = user
end
def points
Game.points_for_user(user.id)
end
end
Player.new(user).name # calls user.name
কিন্তু, গ্যাভিন কমেন্টে উল্লেখ করেছিলেন, এটি ইনহেরিটেন্স এড়ানোর একটি অদ্ভুত উপায় মনে হচ্ছে। কেন না সাবক্লাস ব্যবহার করা হোক? আপনি একই ইফেক্ট পাবেন, এবং আপনাকে একটি সম্পূর্ণ নতুন ফিচার যোগ করতে হবে না। এটি একটি অদ্ভুত জিনিস যোগ করা মনে হচ্ছে।
delegate_missing_to যোগ করার পেছনে কোনো কারণ অবশ্যই থাকবে। এবং রেইলস ফিচারের জন্য, পুল রিকোয়েস্টগুলো সেই কারণ খুঁজে বের করার একটি চমৎকার উপায়। এই পুল রিকোয়েস্টে, DHH এরা কেন সে ফিচার সাজেস্ট করলেন সেটি বলেছেন:
এখানে একটি সাধারণ প্যাটার্ন যদি আপনি একটি ডেকোরেটর বানাতে চান:
এটি খোঁজা শুরু করার একটি খুব ভালো জায়গা মনে হচ্ছে।
কেন ডেকোরেটর?
যখন আপনি একটি ডেকোরেটর তৈরি করেন, আপনি একটি অবজেক্টের আচরণ পরিবর্তন করেন, একটি নতুন সাবক্লাস তৈরি না করে। উদাহরণস্বরূপ, আগের কোড থেকে:
class Player
delegate_missing_to :@user
def initialize(user)
@user = user
end
def points
Game.points_for_user(user.id)
end
end
আপনি বলবেন "Player ডেকোরেট করে user," কারণ একটি Player প্রায় User-এর মতো আচরণ করে, কিন্তু একটি এক্সট্রা মেথড আছে, points। এবং এটি ইনহেরিটেন্স ছাড়াই করে।
আপনার কেন এমন কিছু দরকার হবে? সেটি একটি কঠিন প্রশ্ন, কারণ অনেক ডিজাইন প্যাটার্নের মতো, এটি সবসময় স্পষ্ট নয় যে আপনি কোথায় এটি ব্যবহার করবেন অন্য কিছুর পরিবর্তে।
আপনি কখন ডেকোরেটর ব্যবহার করবেন?
ডেকোরেটর কেবল ইনহেরিটেন্স করার একটি অধিক জটিল উপায় হতে পারে। আমি বলি, এই দুটি লাইন কোডে কোনটি ভালো?
player = Player.new(User.new(name: "Justin")) # Player decorates User
player = Player.new(name: "Justin") # Player subclasses User
স্পষ্টভাবে দ্বিতীয়টি, ঠিক না? এখানে, Player কে ডেকোরেটর হিসেবে তৈরি করার পরিবর্তে সাবক্লাস হিসেবে তৈরি করা শুধু কোডের অপচয়।
কিন্তু কখনও কখনও, আপনি একটি অবজেক্টে পরে ফাংশনালিটি যোগ করতে চান, সেটি তৈরি করার জায়গা থেকে দূরে। উদাহরণস্বরূপ, যদি আপনার কোড এরকম হয়?
user = User.find(1)
... কিছুক্ষণ পর ...
player = Player.new(user)
এখন, আপনি যেভাবে চান সেভাবে ইউজার তৈরি করতে পারেন, যেকোনো মেথডে। যে কোড User অবজেক্টটি তৈরি করে, সেটি জানে বা খেয়াল করে না যে Player ক্লাস নামের কিছুর অস্তিত্ব আছে। এবং আপনি এখনও আসল User অবজেক্টটি ব্যবহার করতে পারেন যদি আপনি সে এক্সট্রা মেথডগুলো চান না।
এটি আপনাকে বিহেভিয়ার আলাদা ক্লাসে বিভক্ত করতে সাহায্য করে। প্রতিটি ক্লাস ফোকাস করতে পারে কীভাবে User অবজেক্টটি একটি নির্দিষ্ট পরিস্থিতিতে ব্যবহার হবে – একটি Player, একটি Employee, একটি Developer। ইনহেরিটেন্সে, এই সব জিনিস সহজে গুলিয়ে যেতে পারে।
MrChris কমেন্টে ডেকোরেটরের আরেকটি সুবিধা উল্লেখ করেছেন:
যখন আপনি একটি অবজেক্ট ডেকোরেট করেন, আপনি শুধু সেই অবজেক্টের পাবলিক মেথডগুলো কল করতে পারেন। যখন আপনি সাবক্লাস করেন, আপনি যেকোনো মেথড কল করতে পারেন, প্রাইভেটवालेও। এটি সাবক্লাসগুলাকে আরও বেশি ব্রেক করতে পারে, কারণ সেগুলো ভুলবশত তাদের প্যারেন্টের ইমপ্লিমেন্টেশন ডিটেইলের ওপর নির্ভর করতে পারে। সেস ডিটেইলস সাধারণত পাবলিক মেথডের চেয়ে বেশি বদলায়।
বড় ক্লাস ভাঙতে ডেকোরেটর খুব উপকারী হতে পারে। ডেকোরেটর সহ, সিঙ্গেল-রেসপন্সিবিলিটি প্রিন্সিপাল ফলো করা সহজ – প্রতিটি ডেকোরেটর একটা জিনিস করতে পারে এবং সেটি ভালো করতে পারে, এবং আপনি ডেকোরেটরগুলো কম্বাইন করতে পারেন আরও জটিল বিহেভিয়ার পেতে।
রুবিতে বিহেভিয়ার শেয়ার করার অনেক উপায় আছে। আপনি সাবক্লাস করতে পারেন, মডিউল কম্বাইন করতে পারেন, আপনি চাইলে এক ক্লাস থেকে মেথড গ্র্যাব করে অন্য ক্লাসে অ্যাটাচ করতে পারেন। ডেকোরেটর প্যাটার্ন, তবে, আপনাকে কিছুটা ভিন্ন কিছু দেয়। আপনি শুধু ইনস্ট্যান্স ভেরিয়েবল এবং মেথড কল ব্যবহার করছেন, যেকোনো অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজের বিল্ডিং ব্লক। সেই ফান্ডামেন্টাল থেকে, আপনি রানটাইমে ফ্লেক্সিবল বিহেভিয়ার পেতে পারেন – কোডও ওভারকমপ্লিকেট না করে।