সি-জাতীয় ভাষাগুলির (সি #, জাভা, ইত্যাদি) শব্দার্থক কারণে ভিজিটর প্যাটার্নের visit
/ নির্মাণগুলি accept
একটি প্রয়োজনীয় মন্দ। দর্শনার্থী প্যাটার্নের লক্ষ্য হ'ল আপনার কোডটি পড়ার প্রত্যাশা মতো আপনার কলটি রুট করতে ডাবল-প্রেরণ ব্যবহার করা।
সাধারণত যখন ভিজিটর প্যাটার্নটি ব্যবহৃত হয়, তখন কোনও বস্তু শ্রেণিবিন্যাস জড়িত থাকে যেখানে সমস্ত নোড বেস Node
ধরণের থেকে প্রাপ্ত হয় , এখন থেকে হিসাবে উল্লেখ করা হয় Node
। সহজাতভাবে, আমরা এটি এটি লিখতে চাই:
Node root = GetTreeRoot();
new MyVisitor().visit(root);
এর মধ্যেই সমস্যা রয়েছে। যদি আমাদের MyVisitor
ক্লাসটি নিম্নলিখিতগুলির মতো সংজ্ঞায়িত হয়:
class MyVisitor implements IVisitor {
void visit(CarNode node);
void visit(TrainNode node);
void visit(PlaneNode node);
void visit(Node node);
}
যদি, রানটাইমে, প্রকৃত প্রকারটি নির্বিশেষে root
, আমাদের কলটি ওভারলোডের মধ্যে চলে যায় visit(Node node)
। প্রকারের ঘোষিত সমস্ত ভেরিয়েবলের ক্ষেত্রে এটি সত্য হবে Node
। কেন? যেহেতু জাভা এবং অন্যান্য সি-এর মতো ভাষাগুলি কোন স্থির প্রকারটিকে বিবেচনা করে বা কোন ওভারলোডটি কল করতে হবে তা স্থির করার সময় প্যারামিটারের স্থিতিশীল প্রকার বা প্রকারটিকে যে ধরণের হিসাবে ঘোষণা করা হয় তা বিবেচনা করে। জাভা প্রতিটি পদ্ধতির কলের জন্য রানটাইমের সময় জিজ্ঞাসা করার জন্য অতিরিক্ত পদক্ষেপ নেয় না, "ঠিক আছে, ডায়নামিক ধরণের root
কী? ওহ, আমি দেখি। এটি একটি TrainNode
। দেখা যাক যে কোনও পদ্ধতিতে MyVisitor
টাইপের একটি পরামিতি গ্রহণ করা আছে কিনা?TrainNode
... "। সংকলনের সময় সংকলক নির্ধারণ করে যে কোন পদ্ধতিটি ডাকা হবে be (জাভা যদি আর্গুমেন্টগুলির গতিশীল প্রকারগুলি পরীক্ষা করে দেখায় তবে পারফরম্যান্সটি ভয়াবহ হতে পারে))
- জাভা আমাদের যখন একটি পদ্ধতি বলা হয় একাউন্টে একটি বস্তুর রানটাইম (অর্থাত গতিশীল) টাইপ করার জন্য যে সরঞ্জামটি দিতে না ভার্চুয়াল পদ্ধতি প্রেরণ । আমরা যখন ভার্চুয়াল পদ্ধতিতে কল করি, কলটি আসলে ফাংশন পয়েন্টার নিয়ে গঠিত মেমরির একটি টেবিলের কাছে যায় । প্রতিটি ধরণের একটি টেবিল থাকে। কোনও নির্দিষ্ট পদ্ধতি যদি কোনও শ্রেণীর দ্বারা ওভাররাইড করা হয় তবে that শ্রেণীর ফাংশন সারণি এন্ট্রিটিতে ওভাররাইড করা ফাংশনের ঠিকানা থাকবে। যদি শ্রেণিটি কোনও পদ্ধতিকে ওভাররাইড না করে তবে এটিতে বেস শ্রেণীর প্রয়োগের জন্য একটি পয়েন্টার থাকবে। এটি এখনও একটি কার্যকারিতা ওভারহেডের অনুপ্রবেশ করে (প্রতিটি পদ্ধতির কলটি মূলত দুটি পয়েন্টারকে ডিফারেন্স করে: একটি টাইপের ফাংশন টেবিলের দিকে নির্দেশ করে এবং অন্যটি নিজেই ফাংশনটির), তবে এটি প্যারামিটারের ধরনগুলি পরীক্ষা করার চেয়ে আরও দ্রুত faster
দর্শনার্থীর প্যাটার্নটির লক্ষ্য হ'ল ডাবল-প্রেরণ সম্পাদন করা - কল টার্গেটের ধরণটি কেবল বিবেচনা করা হয় না ( MyVisitor
, ভার্চুয়াল পদ্ধতিগুলির মাধ্যমে), তবে প্যারামিটারের ধরণ ( Node
আমরা কী ধরণের দিকে তাকিয়ে আছি)? ভিজিটার প্যাটার্নটি আমাদের visit
/ accept
সংমিশ্রণ দ্বারা এটি করতে দেয় ।
আমাদের লাইন এটিতে পরিবর্তন করে:
root.accept(new MyVisitor());
আমরা যা চাই তা আমরা পেতে পারি: ভার্চুয়াল পদ্ধতি প্রেরণের মাধ্যমে আমরা সাবক্লাস দ্বারা প্রয়োগকৃত সঠিক স্বীকৃতি () কলটি প্রবেশ করান - আমাদের উদাহরণে TrainElement
, TrainElement
এর প্রয়োগ বাস্তবায়িত করব accept()
:
class TrainNode extends Node implements IVisitable {
void accept(IVisitor v) {
v.visit(this);
}
}
কি পরিধি ভিতরে এই সময়ে কম্পাইলার কি জানে, TrainNode
এর accept
? এটি জানে যে স্ট্যাটিক ধরণের this
একটিTrainNode
। এটি তথ্যের একটি গুরুত্বপূর্ণ অতিরিক্ত বিচ্ছিন্নতা যা সংকলকটি আমাদের কলারের সুযোগ সম্পর্কে সচেতন ছিল না: সেখানে, এটি সম্পর্কে root
যা জানত তা ছিল এটি একটি Node
। এখন সংকলক জানে যে this
( root
) কেবল একটি নয় Node
, তবে এটি আসলে একটি TrainNode
। ফল ইন, এক লাইন ভিতরে পাওয়া accept()
: v.visit(this)
, সম্পূর্ণরূপে অন্য কিছু মানে। কম্পাইলার এখন একটি জমিদার জন্য চেহারা ইচ্ছার visit()
করে একটি সময় লাগে TrainNode
। যদি এটির সন্ধান না করে তবে এটি কলটি একটি ওভারলোডের ক্ষেত্রে সংকলন করবে যা একটি গ্রহণ করেNode
। যদি উভয়ই বিদ্যমান না থাকে তবে আপনি একটি সংকলন ত্রুটি পাবেন (যদি না আপনার কোনও ওভারলোড লাগে তবে object
)। কার্যকরভাবে এইভাবে আমরা সমস্ত বরাবর যা ইচ্ছা করে তা প্রবেশ করবে: MyVisitor
এর বাস্তবায়ন visit(TrainNode e)
। কোনও ক্যাসেটের প্রয়োজন ছিল না, এবং সবচেয়ে গুরুত্বপূর্ণ, কোনও প্রতিবিম্বের প্রয়োজন ছিল না। সুতরাং, এই প্রক্রিয়াটির ওভারহেড বরং কম: এটি কেবলমাত্র পয়েন্টার রেফারেন্স এবং অন্য কিছু দিয়ে থাকে।
আপনি আপনার প্রশ্নে ঠিক আছেন - আমরা একটি castালাই ব্যবহার করতে পারি এবং সঠিক আচরণ পেতে পারি। যাইহোক, প্রায়শই, আমরা এমনকি নোড কি টাইপ জানি না। নিম্নলিখিত শ্রেণিবদ্ধের ক্ষেত্রে নিন:
abstract class Node { ... }
abstract class BinaryNode extends Node { Node left, right; }
abstract class AdditionNode extends BinaryNode { }
abstract class MultiplicationNode extends BinaryNode { }
abstract class LiteralNode { int value; }
এবং আমরা একটি সাধারণ সংকলক লিখছিলাম যা একটি উত্স ফাইলকে বিশ্লেষণ করে এবং একটি বস্তু স্তরক্রম তৈরি করে যা উপরের স্পেসিফিকেশনটির সাথে সঙ্গতিপূর্ণ। যদি আমরা একজন দর্শনার্থী হিসাবে বাস্তবায়িত শ্রেণিবিন্যাসের জন্য একজন অনুবাদক লিখতাম:
class Interpreter implements IVisitor<int> {
int visit(AdditionNode n) {
int left = n.left.accept(this);
int right = n.right.accept(this);
return left + right;
}
int visit(MultiplicationNode n) {
int left = n.left.accept(this);
int right = n.right.accept(this);
return left * right;
}
int visit(LiteralNode n) {
return n.value;
}
}
কাস্টিং আমাদের সুদূর পেতে হবে না, যেহেতু আমরা ধরনের জানি না left
বা right
এ visit()
পদ্ধতি। আমাদের পার্সার সম্ভবত সম্ভবত Node
শ্রেণিবিন্যাসের মূলের দিকে ইঙ্গিত করা টাইপের একটি বস্তুও ফিরিয়ে আনবে , তাই আমরা নিরাপদে এটি কাস্ট করতে পারি না। সুতরাং আমাদের সাধারণ দোভাষীটি দেখতে পাবেন:
Node program = parse(args[0]);
int result = program.accept(new Interpreter());
System.out.println("Output: " + result);
ভিজিটর প্যাটার্নটি আমাদের খুব শক্তিশালী কিছু করার অনুমতি দেয়: কোনও বস্তু শ্রেণিবিন্যাসের ভিত্তিতে, এটি আমাদেরকে এমন একটি মডুলার ক্রিয়াকলাপ তৈরি করতে দেয় যা হায়ারার্কির কোডটিতে নিজেরাই কোড লাগানোর প্রয়োজন ছাড়াই শ্রেণিবদ্ধ পরিচালনা করে operate দর্শনার্থী প্যাটার্নটি ব্যাপকভাবে ব্যবহৃত হয়, উদাহরণস্বরূপ, সংকলক নির্মাণে। একটি নির্দিষ্ট প্রোগ্রামের সিনট্যাক্স ট্রি প্রদত্ত, অনেক দর্শনার্থী লেখা থাকে যা সেই গাছে পরিচালিত হয়: টাইপ চেকিং, অপ্টিমাইজেশন, মেশিন কোড নির্গমন সবই সাধারণত ভিন্ন ভিন্ন দর্শক হিসাবে প্রয়োগ করা হয়। অপ্টিমাইজেশান দর্শনার্থীর ক্ষেত্রে, এটি ইনপুট ট্রি প্রদত্ত একটি নতুন সিনট্যাক্স ট্রি এমনকি আউটপুটও করতে পারে।
অবশ্যই এর অসুবিধাগুলি রয়েছে: আমরা যদি হায়ারার্কিতে কোনও নতুন প্রকার যুক্ত করি, তবে আমাদের visit()
সেই নতুন ধরণের জন্য IVisitor
ইন্টারফেসে একটি পদ্ধতি যুক্ত করতে হবে এবং আমাদের সকল দর্শকের স্টাব (বা পূর্ণ) প্রয়োগ তৈরি করতে হবে। accept()
উপরে বর্ণিত কারণে আমাদেরও পদ্ধতিটি যুক্ত করতে হবে। যদি পারফরম্যান্সটি আপনার কাছে এর বেশি অর্থ না বোঝায়, দর্শকদের প্রয়োজন ছাড়াই লেখার জন্য সমাধান রয়েছে accept()
তবে এগুলি সাধারণত প্রতিচ্ছবি জড়িত থাকে এবং এইভাবে বেশ বড় ওভারহেড ব্যয় করতে পারে।