একটি অবজেক্ট-ওরিয়েন্টেড ডিজাইন প্যাটার্ন ইমপ্লিমেন্ট করা (Implementing an Object-Oriented Design Pattern)
স্টেট প্যাটার্ন (state pattern) একটি অবজেক্ট-ওরিয়েন্টেড ডিজাইন প্যাটার্ন। এই প্যাটার্নের মূল ভিত্তি হলো, আমরা একটি ভ্যালুর সম্ভাব্য অভ্যন্তরীণ অবস্থা বা state-গুলোর একটি সেট নির্ধারণ করি। এই state-গুলোকে এক সেট স্টেট অবজেক্ট (state objects) দ্বারা প্রকাশ করা হয় এবং ভ্যালুটির state অনুযায়ী তার আচরণ পরিবর্তিত হয়। আমরা একটি ব্লগ পোস্টের struct-এর উদাহরণ নিয়ে কাজ করব, যার একটি ফিল্ড তার state ধরে রাখবে, এবং এই state অবজেক্টটি হবে "ড্রাফট" (draft), "রিভিউ" (review) বা "পাবলিশড" (published) এই তিনটির মধ্যে একটি।
স্টেট অবজেক্টগুলো কিছু কার্যকারিতা (functionality) শেয়ার করে: Rust-এ আমরা অবশ্যই অবজেক্ট এবং ইনহেরিটেন্সের পরিবর্তে struct এবং trait ব্যবহার করি। প্রতিটি স্টেট অবজেক্ট তার নিজের আচরণের জন্য এবং কখন অন্য state-এ পরিবর্তিত হবে তা নিয়ন্ত্রণের জন্য দায়ী। যে ভ্যালুটি স্টেট অবজেক্ট ধারণ করে, সে state-গুলোর বিভিন্ন আচরণ বা কখন তাদের মধ্যে পরিবর্তন হবে সে সম্পর্কে কিছুই জানে না।
স্টেট প্যাটার্ন ব্যবহারের সুবিধা হলো, যখন প্রোগ্রামের ব্যবসায়িক প্রয়োজনীয়তা (business requirements) পরিবর্তিত হয়, তখন আমাদের state ধারণকারী ভ্যালুর কোড বা সেই ভ্যালু ব্যবহারকারী কোড পরিবর্তন করতে হবে না। আমাদের শুধুমাত্র কোনো একটি স্টেট অবজেক্টের ভেতরের কোড আপডেট করে তার নিয়ম পরিবর্তন করতে হবে অথবা প্রয়োজনে আরও স্টেট অবজেক্ট যোগ করতে হবে।
প্রথমে আমরা স্টেট প্যাটার্নটি একটি প্রচলিত অবজেক্ট-ওরিয়েন্টেড পদ্ধতিতে ইমপ্লিমেন্ট করব, তারপর আমরা এমন একটি পদ্ধতি ব্যবহার করব যা Rust-এ কিছুটা বেশি স্বাভাবিক। চলুন, স্টেট প্যাটার্ন ব্যবহার করে একটি ব্লগ পোস্টের ওয়ার্কফ্লো ধাপে ধাপে ইমপ্লিমেন্ট করা যাক।
চূড়ান্ত কার্যকারিতাটি দেখতে এইরকম হবে:
- একটি ব্লগ পোস্ট একটি খালি ড্রাফট হিসাবে শুরু হয়।
- ড্রাফট লেখা শেষ হলে, পোস্টটির একটি রিভিউয়ের জন্য অনুরোধ করা হয়।
- পোস্টটি অনুমোদিত (approved) হলে, এটি পাবলিশড হয়ে যায়।
- শুধুমাত্র পাবলিশড ব্লগ পোস্টগুলোই প্রিন্ট করার জন্য কন্টেন্ট ফেরত দেয়, যাতে অননুমোদিত পোস্টগুলো ভুলবশত পাবলিশড না হয়ে যায়।
একটি পোস্টে অন্য কোনো পরিবর্তন করার চেষ্টা করলে তার কোনো প্রভাব থাকবে না। উদাহরণস্বরূপ, যদি আমরা রিভিউয়ের অনুরোধ করার আগেই একটি ড্রাফট ব্লগ পোস্ট অনুমোদন করার চেষ্টা করি, পোস্টটি একটি অপ্রকাশিত ড্রাফট হিসেবেই থাকবে।
একটি প্রচলিত অবজেক্ট-ওরিয়েন্টেড প্রচেষ্টা (A Traditional Object-oriented Attempt)
একই সমস্যা সমাধানের জন্য কোড গঠন করার অসীম উপায় রয়েছে, প্রতিটিরই ভিন্ন ভিন্ন সুবিধা-অসুবিধা (trade-offs) আছে। এই সেকশনের ইমপ্লিমেন্টেশনটি অনেকটা প্রচলিত অবজেক্ট-ওরিয়েন্টেড ধরনের, যা Rust-এ লেখা সম্ভব, কিন্তু এটি Rust-এর কিছু শক্তিশালী দিকের সুবিধা নেয় না। পরে, আমরা একটি ভিন্ন সমাধান দেখাব যা এখনও অবজেক্ট-ওরিয়েন্টেড ডিজাইন প্যাটার্ন ব্যবহার করে কিন্তু এমনভাবে গঠন করা হয়েছে যা অবজেক্ট-ওরিয়েন্টেড অভিজ্ঞতাসম্পন্ন প্রোগ্রামারদের কাছে কিছুটা অপরিচিত মনে হতে পারে। আমরা দুটি সমাধানের তুলনা করে দেখব যে অন্য ল্যাঙ্গুয়েজের কোডের চেয়ে ভিন্নভাবে Rust কোড ডিজাইন করার সুবিধা-অসুবিধাগুলো কী।
লিস্টিং ১৮-১১ এই ওয়ার্কফ্লোটিকে কোড আকারে দেখায়: এটি blog
নামে একটি লাইব্রেরি ক্রেটে আমরা যে API ইমপ্লিমেন্ট করব তার একটি উদাহরণ। এটি এখনও কম্পাইল হবে না কারণ আমরা blog
ক্রেটটি ইমপ্লিমেন্ট করিনি।
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
post.request_review();
assert_eq!("", post.content());
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
আমরা ব্যবহারকারীকে Post::new
দিয়ে একটি নতুন ড্রাফট ব্লগ পোস্ট তৈরি করার সুযোগ দিতে চাই। আমরা ব্লগ পোস্টে টেক্সট যোগ করার অনুমতি দিতে চাই। যদি আমরা অনুমোদনের আগে অবিলম্বে পোস্টের কন্টেন্ট পাওয়ার চেষ্টা করি, আমাদের কোনো টেক্সট পাওয়া উচিত নয় কারণ পোস্টটি এখনও একটি ড্রাফট। আমরা প্রদর্শনের উদ্দেশ্যে কোডে assert_eq!
যোগ করেছি। এর জন্য একটি চমৎকার ইউনিট টেস্ট হতে পারত यह নিশ্চিত করা যে একটি ড্রাফট ব্লগ পোস্ট content
মেথড থেকে একটি খালি স্ট্রিং ফেরত দেয়, কিন্তু আমরা এই উদাহরণের জন্য টেস্ট লিখছি না।
এরপর, আমরা পোস্টের রিভিউয়ের জন্য একটি অনুরোধ সক্ষম করতে চাই, এবং রিভিউয়ের জন্য অপেক্ষারত অবস্থায় আমরা চাই content
একটি খালি স্ট্রিং ফেরত দিক। যখন পোস্টটি অনুমোদন পাবে, তখন এটি পাবলিশড হওয়া উচিত, যার মানে content
কল করা হলে পোস্টের টেক্সট ফেরত দেওয়া হবে।
লক্ষ্য করুন যে আমরা ক্রেট থেকে শুধুমাত্র Post
টাইপের সাথেই ইন্টারঅ্যাক্ট করছি। এই টাইপটি স্টেট প্যাটার্ন ব্যবহার করবে এবং একটি মান ধারণ করবে যা তিনটি স্টেট অবজেক্টের মধ্যে একটি হবে, যা একটি পোস্টের বিভিন্ন অবস্থা—ড্রাফট, রিভিউ, বা পাবলিশড—উপস্থাপন করবে। এক state থেকে অন্য state-এ পরিবর্তন Post
টাইপের অভ্যন্তরে পরিচালিত হবে। আমাদের লাইব্রেরির ব্যবহারকারীরা Post
ইনস্ট্যান্সের উপর যে মেথডগুলো কল করে তার প্রতিক্রিয়ায় state-গুলো পরিবর্তিত হয়, কিন্তু তাদের সরাসরি state পরিবর্তন পরিচালনা করতে হয় না। এছাড়াও, ব্যবহারকারীরা state-গুলো নিয়ে ভুল করতে পারে না, যেমন রিভিউয়ের আগে একটি পোস্ট পাবলিশ করা।
Post
সংজ্ঞায়িত করা এবং ড্রাফট অবস্থায় একটি নতুন ইনস্ট্যান্স তৈরি করা
চলুন লাইব্রেরির ইমপ্লিমেন্টেশন শুরু করা যাক! আমরা জানি আমাদের একটি পাবলিক Post
struct প্রয়োজন যা কিছু কন্টেন্ট ধারণ করবে, তাই আমরা struct-টির সংজ্ঞা এবং একটি Post
-এর ইনস্ট্যান্স তৈরি করার জন্য একটি সংশ্লিষ্ট পাবলিক new
ফাংশন দিয়ে শুরু করব, যেমনটি লিস্টিং ১৮-১২-তে দেখানো হয়েছে। আমরা একটি প্রাইভেট State
trait-ও তৈরি করব যা একটি Post
-এর জন্য সমস্ত স্টেট অবজেক্টের যে আচরণ থাকতে হবে তা সংজ্ঞায়িত করবে।
তারপর Post
একটি state
নামের প্রাইভেট ফিল্ডে Option<T>
-এর ভিতরে Box<dyn State>
-এর একটি ট্রেইট অবজেক্ট ধারণ করবে স্টেট অবজেক্ট রাখার জন্য। আপনি একটু পরেই দেখতে পাবেন কেন Option<T>
প্রয়োজন।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
}
trait State {}
struct Draft {}
impl State for Draft {}
State
trait বিভিন্ন পোস্ট state দ্বারা শেয়ার করা আচরণকে সংজ্ঞায়িত করে। স্টেট অবজেক্টগুলো হলো Draft
, PendingReview
, এবং Published
, এবং তারা সবাই State
trait ইমপ্লিমেন্ট করবে। আপাতত, trait-টির কোনো মেথড নেই, এবং আমরা কেবল Draft
state সংজ্ঞায়িত করে শুরু করব কারণ আমরা চাই একটি পোস্ট এই state-এ শুরু হোক।
যখন আমরা একটি নতুন Post
তৈরি করি, আমরা এর state
ফিল্ডটিকে একটি Some
মান দিয়ে সেট করি যা একটি Box
ধারণ করে। এই Box
একটি Draft
struct-এর নতুন ইনস্ট্যান্সের দিকে নির্দেশ করে। এটি নিশ্চিত করে যে যখনই আমরা Post
-এর একটি নতুন ইনস্ট্যান্স তৈরি করব, এটি একটি ড্রাফট হিসাবে শুরু হবে। যেহেতু Post
-এর state
ফিল্ডটি প্রাইভেট, তাই অন্য কোনো state-এ একটি Post
তৈরি করার কোনো উপায় নেই! Post::new
ফাংশনে, আমরা content
ফিল্ডটিকে একটি নতুন, খালি String
-এ সেট করি।
পোস্ট কন্টেন্টের টেক্সট সংরক্ষণ করা
আমরা লিস্টিং ১৮-১১-তে দেখেছি যে আমরা add_text
নামের একটি মেথড কল করতে এবং এটিকে একটি &str
পাস করতে সক্ষম হতে চাই যা ব্লগ পোস্টের টেক্সট কন্টেন্ট হিসাবে যোগ করা হবে। আমরা এটি একটি মেথড হিসাবে ইমপ্লিমেন্ট করি, content
ফিল্ডটিকে pub
হিসাবে প্রকাশ করার পরিবর্তে, যাতে পরে আমরা এমন একটি মেথড ইমপ্লিমেন্ট করতে পারি যা content
ফিল্ডের ডেটা কীভাবে পড়া হবে তা নিয়ন্ত্রণ করবে। add_text
মেথডটি বেশ সহজবোধ্য, তাই চলুন লিস্টিং ১৮-১৩-এর ইমপ্লিমেন্টেশনটি impl Post
ব্লকে যোগ করি।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
trait State {}
struct Draft {}
impl State for Draft {}
add_text
মেথডটি self
-এর একটি মিউটেবল রেফারেন্স নেয় কারণ আমরা যে Post
ইনস্ট্যান্সের উপর add_text
কল করছি তা পরিবর্তন করছি। তারপর আমরা content
-এর String
-এর উপর push_str
কল করি এবং সংরক্ষিত content
-এ যোগ করার জন্য text
আর্গুমেন্টটি পাস করি। এই আচরণটি পোস্টটি কোন state-এ আছে তার উপর নির্ভর করে না, তাই এটি স্টেট প্যাটার্নের অংশ নয়। add_text
মেথডটি state
ফিল্ডের সাথে একেবারেই ইন্টারঅ্যাক্ট করে না, তবে এটি আমরা যে আচরণ সমর্থন করতে চাই তার অংশ।
একটি ড্রাফট পোস্টের কন্টেন্ট খালি নিশ্চিত করা
এমনকি আমরা add_text
কল করে আমাদের পোস্টে কিছু কন্টেন্ট যোগ করার পরেও, আমরা চাই content
মেথডটি একটি খালি স্ট্রিং স্লাইস ফেরত দিক কারণ পোস্টটি এখনও ড্রাফট state-এ আছে, যেমনটি লিস্টিং ১৮-১১-এর ৭ নম্বর লাইনে দেখানো হয়েছে। আপাতত, চলুন content
মেথডটি সবচেয়ে সহজ জিনিস দিয়ে ইমপ্লিমেন্ট করি যা এই প্রয়োজনীয়তা পূরণ করবে: সর্বদা একটি খালি স্ট্রিং স্লাইস ফেরত দেওয়া। আমরা এটি পরে পরিবর্তন করব যখন আমরা একটি পোস্টের state পরিবর্তন করার ক্ষমতা ইমপ্লিমেন্ট করব যাতে এটি পাবলিশড হতে পারে। এখন পর্যন্ত, পোস্টগুলো কেবল ড্রাফট state-এ থাকতে পারে, তাই পোস্ট কন্টেন্ট সর্বদা খালি থাকা উচিত। লিস্টিং ১৮-১৪ এই স্থানধারক (placeholder) ইমপ্লিমেন্টেশনটি দেখায়।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
""
}
}
trait State {}
struct Draft {}
impl State for Draft {}
এই content
মেথডটি যোগ করার সাথে, লিস্টিং ১৮-১১-এর ৭ নম্বর লাইন পর্যন্ত সবকিছু উদ্দেশ্য অনুযায়ী কাজ করে।
একটি রিভিউয়ের অনুরোধ পোস্টের স্টেট পরিবর্তন করে
এরপরে, আমাদের একটি পোস্টের রিভিউয়ের অনুরোধ করার জন্য কার্যকারিতা যোগ করতে হবে, যা এর state Draft
থেকে PendingReview
-এ পরিবর্তন করবে। লিস্টিং ১৮-১৫ এই কোডটি দেখায়।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
""
}
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
}
আমরা Post
-কে request_review
নামে একটি পাবলিক মেথড দিই যা self
-এর একটি মিউটেবল রেফারেন্স নেবে। তারপর আমরা Post
-এর বর্তমান state-এর উপর একটি অভ্যন্তরীণ request_review
মেথড কল করি, এবং এই দ্বিতীয় request_review
মেথডটি বর্তমান state-কে কনজিউম (consume) করে এবং একটি নতুন state ফেরত দেয়।
আমরা State
trait-এ request_review
মেথডটি যোগ করি; যে সমস্ত টাইপ trait-টি ইমপ্লিমেন্ট করে তাদের এখন request_review
মেথড ইমপ্লিমেন্ট করতে হবে। লক্ষ্য করুন যে মেথডের প্রথম প্যারামিটার হিসাবে self
, &self
, বা &mut self
থাকার পরিবর্তে, আমাদের আছে self: Box<Self>
। এই সিনট্যাক্সটির মানে হলো মেথডটি কেবল তখনই বৈধ যখন এটি টাইপ ধারণকারী একটি Box
-এর উপর কল করা হয়। এই সিনট্যাক্সটি Box<Self>
-এর মালিকানা নেয়, পুরানো state-কে অবৈধ করে দেয় যাতে Post
-এর state ভ্যালুটি একটি নতুন state-এ রূপান্তরিত হতে পারে।
পুরানো state কনজিউম করার জন্য, request_review
মেথডটিকে state ভ্যালুর মালিকানা নিতে হবে। এখানেই Post
-এর state
ফিল্ডে Option
-এর ভূমিকা আসে: আমরা state
ফিল্ড থেকে Some
ভ্যালুটি বের করে নিতে এবং তার জায়গায় একটি None
রেখে দিতে take
মেথড কল করি, কারণ Rust আমাদের struct-এ খালি ফিল্ড রাখতে দেয় না। এটি আমাদের Post
থেকে state
ভ্যালুটি borrow করার পরিবর্তে move করার সুযোগ দেয়। তারপর আমরা পোস্টের state
ভ্যালুটি এই অপারেশনের ফলাফলে সেট করব।
আমাদের state
ভ্যালুর মালিকানা পেতে self.state = self.state.request_review();
-এর মতো কোড দিয়ে সরাসরি সেট করার পরিবর্তে state
-কে সাময়িকভাবে None
হিসাবে সেট করতে হবে। এটি নিশ্চিত করে যে আমরা পুরানো state
ভ্যালুটি একটি নতুন state-এ রূপান্তরিত করার পরে Post
আর সেটি ব্যবহার করতে পারবে না।
Draft
-এর উপর request_review
মেথডটি একটি নতুন PendingReview
struct-এর একটি নতুন, বক্সড ইনস্ট্যান্স ফেরত দেয়, যা সেই state-কে প্রতিনিধিত্ব করে যখন একটি পোস্ট রিভিউয়ের জন্য অপেক্ষা করছে। PendingReview
struct-টিও request_review
মেথড ইমপ্লিমেন্ট করে কিন্তু কোনো রূপান্তর করে না। বরং, এটি নিজেকেই ফেরত দেয় কারণ যখন আমরা এমন একটি পোস্টে রিভিউয়ের অনুরোধ করি যা ইতিমধ্যে PendingReview
state-এ আছে, তখন এটি PendingReview
state-এ থাকা উচিত।
এখন আমরা স্টেট প্যাটার্নের সুবিধা দেখতে শুরু করতে পারি: Post
-এর উপর request_review
মেথডটি তার state
ভ্যালু যাই হোক না কেন একই থাকে। প্রতিটি state তার নিজের নিয়মের জন্য দায়ী।
আমরা Post
-এর content
মেথডটি যেমন আছে তেমনই রাখব, একটি খালি স্ট্রিং স্লাইস ফেরত দিয়ে। আমরা এখন একটি Post
-কে Draft
state-এর পাশাপাশি PendingReview
state-এও রাখতে পারি, কিন্তু আমরা PendingReview
state-এও একই আচরণ চাই। লিস্টিং ১৮-১১ এখন ১০ নম্বর লাইন পর্যন্ত কাজ করে!
content
-এর আচরণ পরিবর্তনকারী approve
মেথড যোগ করা
approve
মেথডটি request_review
মেথডের মতোই হবে: এটি state
-কে সেই মানে সেট করবে যা বর্তমান state বলে যে অনুমোদিত হলে তার থাকা উচিত, যেমনটি লিস্টিং ১৮-১৬-তে দেখানো হয়েছে।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
""
}
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
// --snip--
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
// --snip--
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
আমরা State
trait-এ approve
মেথড যোগ করি এবং State
ইমপ্লিমেন্টকারী একটি নতুন struct, Published
state, যোগ করি।
PendingReview
-এর উপর request_review
যেভাবে কাজ করে তার মতোই, যদি আমরা একটি Draft
-এর উপর approve
মেথড কল করি, তবে এর কোনো প্রভাব থাকবে না কারণ approve
self
ফেরত দেবে। যখন আমরা PendingReview
-এর উপর approve
কল করি, তখন এটি Published
struct-এর একটি নতুন, বক্সড ইনস্ট্যান্স ফেরত দেয়। Published
struct-টি State
trait ইমপ্লিমেন্ট করে, এবং request_review
ও approve
উভয় মেথডের জন্য, এটি নিজেকেই ফেরত দেয় কারণ পোস্টটি সেইসব ক্ষেত্রে Published
state-এ থাকা উচিত।
এখন আমাদের Post
-এর content
মেথডটি আপডেট করতে হবে। আমরা চাই content
থেকে ফেরত আসা মানটি Post
-এর বর্তমান state-এর উপর নির্ভর করুক, তাই আমরা Post
-কে তার state
-এর উপর সংজ্ঞায়িত একটি content
মেথডে কাজটা सौंप (delegate) দেব, যেমনটি লিস্টিং ১৮-১৭-তে দেখানো হয়েছে।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
// --snip--
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
যেহেতু লক্ষ্য হলো এই সমস্ত নিয়ম State
ইমপ্লিমেন্টকারী struct-গুলোর ভিতরে রাখা, তাই আমরা state
-এর মানের উপর একটি content
মেথড কল করি এবং পোস্ট ইনস্ট্যান্সটি (অর্থাৎ self
) একটি আর্গুমেন্ট হিসাবে পাস করি। তারপর আমরা state
মানের উপর content
মেথড ব্যবহার করে যে মান ফেরত আসে তা ফেরত দিই।
আমরা Option
-এর উপর as_ref
মেথড কল করি কারণ আমরা মানের মালিকানার পরিবর্তে Option
-এর ভিতরের মানের একটি রেফারেন্স চাই। যেহেতু state
একটি Option<Box<dyn State>>
, যখন আমরা as_ref
কল করি, তখন একটি Option<&Box<dyn State>>
ফেরত আসে। যদি আমরা as_ref
কল না করতাম, আমরা একটি ত্রুটি পেতাম কারণ আমরা ফাংশন প্যারামিটারের ধার করা &self
থেকে state
মুভ করতে পারি না।
তারপর আমরা unwrap
মেথড কল করি, যা আমরা জানি কখনও প্যানিক করবে না কারণ আমরা জানি Post
-এর মেথডগুলো নিশ্চিত করে যে সেই মেথডগুলো শেষ হলে state
-এ সর্বদা একটি Some
মান থাকবে। এটি সেই случайগুলোর মধ্যে একটি যা আমরা ৯ অধ্যায়ে "যেসব ক্ষেত্রে আপনার কাছে কম্পাইলারের চেয়ে বেশি তথ্য থাকে" অংশে আলোচনা করেছি যখন আমরা জানি যে একটি None
মান কখনও সম্ভব নয়, যদিও কম্পাইলার এটি বুঝতে সক্ষম নয়।
এই মুহুর্তে, যখন আমরা &Box<dyn State>
-এর উপর content
কল করি, তখন &
এবং Box
-এর উপর ডিরেফ কোয়ার্সন (deref coercion) কার্যকর হবে যাতে content
মেথডটি শেষ পর্যন্ত State
trait ইমপ্লিমেন্টকারী টাইপের উপর কল করা হয়। এর মানে হলো আমাদের State
trait সংজ্ঞায় content
যোগ করতে হবে, এবং সেখানেই আমরা কোন state আছে তার উপর নির্ভর করে কোন কন্টেন্ট ফেরত দিতে হবে তার লজিক রাখব, যেমনটি লিস্টিং ১৮-১৮-তে দেখানো হয়েছে।
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
// --snip--
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}
// --snip--
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
struct Published {}
impl State for Published {
// --snip--
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
আমরা content
মেথডের জন্য একটি ডিফল্ট ইমপ্লিমেন্টেশন যোগ করি যা একটি খালি স্ট্রিং স্লাইস ফেরত দেয়। এর মানে আমাদের Draft
এবং PendingReview
struct-এ content
ইমপ্লিমেন্ট করার প্রয়োজন নেই। Published
struct content
মেথডকে ওভাররাইড করবে এবং post.content
-এর মান ফেরত দেবে। যদিও সুবিধাজনক, State
-এর content
মেথড Post
-এর content
নির্ধারণ করা State
এবং Post
-এর দায়িত্বের মধ্যেকার সীমানাকে অস্পষ্ট করে তুলছে।
লক্ষ্য করুন যে আমাদের এই মেথডে লাইফটাইম অ্যানোটেশন প্রয়োজন, যেমনটি আমরা ১০ অধ্যায়ে আলোচনা করেছি। আমরা একটি post
-এর রেফারেন্স একটি আর্গুমেন্ট হিসাবে নিচ্ছি এবং সেই post
-এর একটি অংশের রেফারেন্স ফেরত দিচ্ছি, তাই ফেরত দেওয়া রেফারেন্সের লাইফটাইম post
আর্গুমেন্টের লাইফটাইমের সাথে সম্পর্কিত।
এবং আমরা শেষ করেছি—লিস্টিং ১৮-১১ এখন পুরোটাই কাজ করে! আমরা ব্লগ পোস্ট ওয়ার্কফ্লোর নিয়মাবলী দিয়ে স্টেট প্যাটার্ন ইমপ্লিমেন্ট করেছি। নিয়ম সম্পর্কিত লজিক Post
-এ ছড়িয়ে ছিটিয়ে থাকার পরিবর্তে স্টেট অবজেক্টগুলোর মধ্যে থাকে।
কেন একটি Enum নয়? (Why Not An Enum?)
আপনি হয়তো ভাবছেন কেন আমরা বিভিন্ন সম্ভাব্য পোস্ট state-কে ভ্যারিয়েন্ট হিসাবে নিয়ে একটি
enum
ব্যবহার করিনি। এটি অবশ্যই একটি সম্ভাব্য সমাধান; চেষ্টা করে দেখুন এবং চূড়ান্ত ফলাফল তুলনা করে দেখুন কোনটি আপনার পছন্দ! একটি enum ব্যবহারের একটি অসুবিধা হলো, enum-এর মান পরীক্ষা করে এমন প্রতিটি জায়গায় প্রতিটি সম্ভাব্য ভ্যারিয়েন্ট পরিচালনা করার জন্য একটিmatch
এক্সপ্রেশন বা অনুরূপ কিছুর প্রয়োজন হবে। এটি এই ট্রেইট অবজেক্ট সমাধানের চেয়ে বেশি পুনরাবৃত্তিমূলক (repetitive) হতে পারে।
স্টেট প্যাটার্নের সুবিধা-অসুবিধা (Trade-offs of the State Pattern)
আমরা দেখিয়েছি যে Rust অবজেক্ট-ওরিয়েন্টেড স্টেট প্যাটার্ন ইমপ্লিমেন্ট করতে সক্ষম, যাতে প্রতিটি state-এ একটি পোস্টের বিভিন্ন ধরণের আচরণকে এনক্যাপসুলেট করা যায়। Post
-এর মেথডগুলো বিভিন্ন আচরণ সম্পর্কে কিছুই জানে না। আমরা যেভাবে কোডটি সাজিয়েছি, তাতে একটি পাবলিশড পোস্টের বিভিন্ন আচরণ জানার জন্য আমাদের কেবল একটি জায়গায় তাকাতে হবে: Published
struct-এর উপর State
trait-এর ইমপ্লিমেন্টেশন।
যদি আমরা একটি বিকল্প ইমপ্লিমেন্টেশন তৈরি করতাম যা স্টেট প্যাটার্ন ব্যবহার করে না, আমরা হয়তো Post
-এর মেথডগুলোতে বা এমনকি main
কোডেও match
এক্সপ্রেশন ব্যবহার করতাম যা পোস্টের state পরীক্ষা করে এবং সেই জায়গাগুলোতে আচরণ পরিবর্তন করে। এর মানে হতো একটি পোস্ট পাবলিশড অবস্থায় থাকার সমস্ত প্রভাব বোঝার জন্য আমাদের বেশ কয়েকটি জায়গায় তাকাতে হতো।
স্টেট প্যাটার্নের সাথে, Post
মেথড এবং যেখানে আমরা Post
ব্যবহার করি সেখানে match
এক্সপ্রেশনের প্রয়োজন হয় না, এবং একটি নতুন state যোগ করার জন্য, আমাদের কেবল একটি নতুন struct যোগ করতে হবে এবং সেই একটি struct-এর উপর trait মেথডগুলো এক জায়গায় ইমপ্লিমেন্ট করতে হবে।
স্টেট প্যাটার্ন ব্যবহার করে ইমপ্লিমেন্টেশনটি আরও কার্যকারিতা যোগ করার জন্য প্রসারিত করা সহজ। স্টেট প্যাটার্ন ব্যবহার করে কোড রক্ষণাবেক্ষণের সরলতা দেখতে, এই পরামর্শগুলোর কয়েকটি চেষ্টা করুন:
- একটি
reject
মেথড যোগ করুন যা পোস্টের statePendingReview
থেকেDraft
-এ ফিরিয়ে আনে। - state
Published
-এ পরিবর্তন করার আগেapprove
-এর দুটি কল প্রয়োজন করুন। - ব্যবহারকারীদের শুধুমাত্র
Draft
state-এ থাকা অবস্থায় টেক্সট কন্টেন্ট যোগ করার অনুমতি দিন। ইঙ্গিত: স্টেট অবজেক্টকে কন্টেন্ট সম্পর্কে কী পরিবর্তন হতে পারে তার জন্য দায়ী করুন, কিন্তুPost
পরিবর্তন করার জন্য দায়ী নয়।
স্টেট প্যাটার্নের একটি অসুবিধা হলো, যেহেতু state-গুলো state-গুলোর মধ্যে রূপান্তর ইমপ্লিমেন্ট করে, তাই কিছু state একে অপরের সাথে সংযুক্ত (coupled)। যদি আমরা PendingReview
এবং Published
-এর মধ্যে Scheduled
-এর মতো আরেকটি state যোগ করি, তবে আমাদের PendingReview
-এর কোড পরিবর্তন করে Scheduled
-এ রূপান্তর করতে হবে। যদি একটি নতুন state যোগ করার সাথে PendingReview
-কে পরিবর্তন করার প্রয়োজন না হতো তবে কম কাজ হতো, কিন্তু তার মানে হতো অন্য একটি ডিজাইন প্যাটার্নে স্যুইচ করা।
আরেকটি অসুবিধা হলো আমরা কিছু লজিক ডুপ্লিকেট করেছি। কিছু ডুপ্লিকেশন দূর করার জন্য, আমরা State
trait-এ request_review
এবং approve
মেথডগুলোর জন্য ডিফল্ট ইমপ্লিমেন্টেশন তৈরি করার চেষ্টা করতে পারি যা self
ফেরত দেয়। তবে, এটি কাজ করবে না: State
-কে একটি ট্রেইট অবজেক্ট হিসাবে ব্যবহার করার সময়, trait জানে না যে সুনির্দিষ্ট self
ঠিক কী হবে, তাই রিটার্ন টাইপ কম্পাইল টাইমে জানা যায় না। (এটি পূর্বে উল্লিখিত dyn
কম্প্যাটিবিলিটি নিয়মগুলোর মধ্যে একটি।)
অন্যান্য ডুপ্লিকেশনের মধ্যে রয়েছে Post
-এর উপর request_review
এবং approve
মেথডগুলোর অনুরূপ ইমপ্লিমেন্টেশন। উভয় মেথডই Post
-এর state
ফিল্ডের সাথে Option::take
ব্যবহার করে, এবং যদি state
Some
হয়, তবে তারা র্যাপড (wrapped) ভ্যালুর একই মেথডের ইমপ্লিমেন্টেশনে কাজটা सौंप দেয় এবং state
ফিল্ডের নতুন মানটি ফলাফলে সেট করে। যদি Post
-এ এই প্যাটার্ন অনুসরণকারী অনেক মেথড থাকতো, তবে আমরা পুনরাবৃত্তি দূর করার জন্য একটি ম্যাক্রো সংজ্ঞায়িত করার কথা বিবেচনা করতে পারতাম (২০তম অধ্যায়ে "ম্যাক্রো" দেখুন)।
অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজের জন্য ঠিক যেভাবে স্টেট প্যাটার্ন সংজ্ঞায়িত করা হয়েছে সেভাবে ইমপ্লিমেন্ট করে, আমরা Rust-এর শক্তিশালী দিকগুলোর ততটা পূর্ণ সুবিধা নিচ্ছি না যতটা আমরা নিতে পারতাম। চলুন blog
ক্রেটে কিছু পরিবর্তন দেখি যা অবৈধ state এবং রূপান্তরকে কম্পাইল-টাইম ত্রুটিতে পরিণত করতে পারে।
State এবং আচরণকে টাইপ হিসাবে এনকোড করা (Encoding States and Behavior as Types)
আমরা আপনাকে দেখাব কীভাবে স্টেট প্যাটার্নটি নতুনভাবে চিন্তা করে একটি ভিন্ন ধরনের সুবিধা-অসুবিধা (trade-offs) পাওয়া যায়। state এবং রূপান্তরগুলোকে সম্পূর্ণরূপে এনক্যাপসুলেট করার পরিবর্তে যাতে বাইরের কোডের তাদের সম্পর্কে কোনো জ্ঞান না থাকে, আমরা state-গুলোকে বিভিন্ন টাইপে এনকোড করব। ফলস্বরূপ, Rust-এর টাইপ চেকিং সিস্টেম একটি কম্পাইলার ত্রুটি জারি করে এমন জায়গায় ড্রাফট পোস্ট ব্যবহার করার প্রচেষ্টা প্রতিরোধ করবে যেখানে কেবল পাবলিশড পোস্ট অনুমোদিত।
চলুন লিস্টিং ১৮-১১-এর main
-এর প্রথম অংশটি বিবেচনা করি:
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
post.request_review();
assert_eq!("", post.content());
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
আমরা এখনও Post::new
ব্যবহার করে ড্রাফট state-এ নতুন পোস্ট তৈরি এবং পোস্টের কন্টেন্টে টেক্সট যোগ করার ক্ষমতা সক্ষম করি। কিন্তু একটি ড্রাফট পোস্টের উপর একটি content
মেথড থাকার পরিবর্তে যা একটি খালি স্ট্রিং ফেরত দেয়, আমরা এমনভাবে তৈরি করব যাতে ড্রাফট পোস্টের content
মেথডটি একেবারেই না থাকে। এভাবে, যদি আমরা একটি ড্রাফট পোস্টের কন্টেন্ট পাওয়ার চেষ্টা করি, আমরা একটি কম্পাইলার ত্রুটি পাব যা আমাদের বলবে যে মেথডটি বিদ্যমান নেই। ফলস্বরূপ, আমাদের জন্য প্রোডাকশনে ভুলবশত ড্রাফট পোস্টের কন্টেন্ট প্রদর্শন করা অসম্ভব হয়ে যাবে কারণ সেই কোড কম্পাইলই হবে না। লিস্টিং ১৮-১৯ একটি Post
struct এবং একটি DraftPost
struct-এর সংজ্ঞা এবং প্রতিটির উপর মেথড দেখায়।
pub struct Post {
content: String,
}
pub struct DraftPost {
content: String,
}
impl Post {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str {
&self.content
}
}
impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
Post
এবং DraftPost
উভয় struct-এরই একটি প্রাইভেট content
ফিল্ড আছে যা ব্লগ পোস্টের টেক্সট সংরক্ষণ করে। struct-গুলোর আর state
ফিল্ড নেই কারণ আমরা state-এর এনকোডিং struct-গুলোর টাইপে স্থানান্তরিত করছি। Post
struct একটি পাবলিশড পোস্টকে প্রতিনিধিত্ব করবে, এবং এর একটি content
মেথড আছে যা content
ফেরত দেয়।
আমাদের এখনও একটি Post::new
ফাংশন আছে, কিন্তু Post
-এর একটি ইনস্ট্যান্স ফেরত দেওয়ার পরিবর্তে, এটি DraftPost
-এর একটি ইনস্ট্যান্স ফেরত দেয়। যেহেতু content
প্রাইভেট এবং Post
ফেরত দেয় এমন কোনো ফাংশন নেই, তাই এই মুহূর্তে Post
-এর একটি ইনস্ট্যান্স তৈরি করা সম্ভব নয়।
DraftPost
struct-এর একটি add_text
মেথড আছে, তাই আমরা আগের মতোই content
-এ টেক্সট যোগ করতে পারি, কিন্তু লক্ষ্য করুন যে DraftPost
-এর কোনো content
মেথড সংজ্ঞায়িত নেই! তাই এখন প্রোগ্রাম নিশ্চিত করে যে সমস্ত পোস্ট ড্রাফট পোস্ট হিসাবে শুরু হয়, এবং ড্রাফট পোস্টগুলোর কন্টেন্ট প্রদর্শনের জন্য উপলব্ধ থাকে না। এই সীমাবদ্ধতাগুলো এড়ানোর যেকোনো প্রচেষ্টা একটি কম্পাইলার ত্রুটির কারণ হবে।
তাহলে আমরা কীভাবে একটি পাবলিশড পোস্ট পাব? আমরা এই নিয়মটি প্রয়োগ করতে চাই যে একটি ড্রাফট পোস্ট পাবলিশড হওয়ার আগে অবশ্যই রিভিউ এবং অনুমোদিত হতে হবে। পেন্ডিং রিভিউ state-এ থাকা একটি পোস্টেরও কোনো কন্টেন্ট প্রদর্শন করা উচিত নয়। চলুন এই সীমাবদ্ধতাগুলো ইমপ্লিমেন্ট করি একটি নতুন struct, PendingReviewPost
যোগ করে, DraftPost
-এর উপর request_review
মেথড সংজ্ঞায়িত করে যা একটি PendingReviewPost
ফেরত দেবে এবং PendingReviewPost
-এর উপর একটি approve
মেথড সংজ্ঞায়িত করে যা একটি Post
ফেরত দেবে, যেমনটি লিস্টিং ১৮-২০-তে দেখানো হয়েছে।
pub struct Post {
content: String,
}
pub struct DraftPost {
content: String,
}
impl Post {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str {
&self.content
}
}
impl DraftPost {
// --snip--
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn request_review(self) -> PendingReviewPost {
PendingReviewPost {
content: self.content,
}
}
}
pub struct PendingReviewPost {
content: String,
}
impl PendingReviewPost {
pub fn approve(self) -> Post {
Post {
content: self.content,
}
}
}
request_review
এবং approve
মেথডগুলো self
-এর মালিকানা নেয়, ফলে DraftPost
এবং PendingReviewPost
ইনস্ট্যান্সগুলো কনজিউম করে এবং সেগুলোকে যথাক্রমে একটি PendingReviewPost
এবং একটি পাবলিশড Post
-এ রূপান্তরিত করে। এভাবে, আমরা request_review
কল করার পরে কোনো অবশিষ্ট DraftPost
ইনস্ট্যান্স রাখব না, এবং এভাবেই চলতে থাকবে। PendingReviewPost
struct-এর কোনো content
মেথড সংজ্ঞায়িত নেই, তাই এর কন্টেন্ট পড়ার চেষ্টা করলে DraftPost
-এর মতো একটি কম্পাইলার ত্রুটি হবে। যেহেতু একটি পাবলিশড Post
ইনস্ট্যান্স পাওয়ার একমাত্র উপায় হলো PendingReviewPost
-এর উপর approve
মেথড কল করা, এবং PendingReviewPost
পাওয়ার একমাত্র উপায় হলো DraftPost
-এর উপর request_review
মেথড কল করা, তাই আমরা এখন ব্লগ পোস্ট ওয়ার্কফ্লোটিকে টাইপ সিস্টেমে এনকোড করেছি।
কিন্তু আমাদের main
-এও কিছু ছোট পরিবর্তন করতে হবে। request_review
এবং approve
মেথডগুলো যে struct-এর উপর কল করা হয় তা পরিবর্তন না করে নতুন ইনস্ট্যান্স ফেরত দেয়, তাই আমাদের ফেরত আসা ইনস্ট্যান্সগুলো সংরক্ষণ করার জন্য আরও let post =
শ্যাডোইং অ্যাসাইনমেন্ট যোগ করতে হবে। আমরা ড্রাফট এবং পেন্ডিং রিভিউ পোস্টগুলোর কন্টেন্ট খালি স্ট্রিং হওয়ার ব্যাপারে অ্যাসারশনও রাখতে পারব না, আর আমাদের সেগুলোর প্রয়োজনও নেই: আমরা আর এমন কোড কম্পাইল করতে পারব না যা সেই state-গুলোতে পোস্টের কন্টেন্ট ব্যবহার করার চেষ্টা করে। main
-এর আপডেট করা কোড লিস্টিং ১৮-২১-এ দেখানো হয়েছে।
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
let post = post.request_review();
let post = post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
post
-কে পুনরায় অ্যাসাইন করার জন্য main
-এ আমাদের যে পরিবর্তনগুলো করতে হয়েছে তার মানে হলো এই ইমপ্লিমেন্টেশনটি আর ঠিক অবজেক্ট-ওরিয়েন্টেড স্টেট প্যাটার্ন অনুসরণ করে না: state-গুলোর মধ্যে রূপান্তর আর সম্পূর্ণরূপে Post
ইমপ্লিমেন্টেশনের মধ্যে এনক্যাপসুলেট করা নেই। তবে, আমাদের লাভ হলো যে অবৈধ state-গুলো এখন টাইপ সিস্টেম এবং কম্পাইল টাইমে ঘটে যাওয়া টাইপ চেকিংয়ের কারণে অসম্ভব! এটি নিশ্চিত করে যে কিছু বাগ, যেমন একটি অপ্রকাশিত পোস্টের কন্টেন্ট প্রদর্শন, প্রোডাকশনে যাওয়ার আগেই আবিষ্কৃত হবে।
এই সেকশনের শুরুতে প্রস্তাবিত কাজগুলো লিস্টিং ১৮-২১-এর পরের blog
ক্রেটের উপর চেষ্টা করে দেখুন এই ভার্সনের কোডের ডিজাইন সম্পর্কে আপনার কী মনে হয়। লক্ষ্য করুন যে কিছু কাজ এই ডিজাইনে ইতিমধ্যে সম্পন্ন হয়ে থাকতে পারে।
আমরা দেখেছি যে যদিও Rust অবজেক্ট-ওরিয়েন্টেড ডিজাইন প্যাটার্ন ইমপ্লিমেন্ট করতে সক্ষম, তবে Rust-এ অন্যান্য প্যাটার্নও উপলব্ধ আছে, যেমন টাইপ সিস্টেমে state এনকোড করা। এই প্যাটার্নগুলোর ভিন্ন ভিন্ন সুবিধা-অসুবিধা রয়েছে। যদিও আপনি অবজেক্ট-ওরিয়েন্টেড প্যাটার্নগুলোর সাথে খুব পরিচিত হতে পারেন, Rust-এর বৈশিষ্ট্যগুলোর সুবিধা নেওয়ার জন্য সমস্যাটি নতুনভাবে চিন্তা করা সুবিধা প্রদান করতে পারে, যেমন কম্পাইল টাইমে কিছু বাগ প্রতিরোধ করা। অবজেক্ট-ওরিয়েন্টেড প্যাটার্নগুলো Rust-এ সর্বদা সেরা সমাধান হবে না কারণ কিছু বৈশিষ্ট্য, যেমন মালিকানা (ownership), যা অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজগুলোতে নেই।
সারাংশ (Summary)
এই অধ্যায়টি পড়ার পরে আপনি Rust-কে একটি অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজ মনে করেন কি না তা নির্বিশেষে, আপনি এখন জানেন যে আপনি Rust-এ কিছু অবজেক্ট-ওরিয়েন্টেড বৈশিষ্ট্য পেতে ট্রেইট অবজেক্ট ব্যবহার করতে পারেন। ডাইনামিক ডিসপ্যাচ আপনার কোডকে কিছুটা রানটাইম পারফরম্যান্সের বিনিময়ে কিছু নমনীয়তা দিতে পারে। আপনি এই নমনীয়তা ব্যবহার করে অবজেক্ট-ওরিয়েন্টেড প্যাটার্ন ইমপ্লিমেন্ট করতে পারেন যা আপনার কোডের রক্ষণাবেক্ষণে সহায়তা করতে পারে। Rust-এর অন্যান্য বৈশিষ্ট্যও রয়েছে, যেমন মালিকানা, যা অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজগুলোতে নেই। একটি অবজেক্ট-ওরিয়েন্টেড প্যাটার্ন সর্বদা Rust-এর শক্তিশালী দিকগুলোর সুবিধা নেওয়ার সেরা উপায় হবে না, তবে এটি একটি উপলব্ধ বিকল্প।
এরপরে, আমরা প্যাটার্নগুলো দেখব, যা Rust-এর আরেকটি বৈশিষ্ট্য যা প্রচুর নমনীয়তা সক্ষম করে। আমরা বই জুড়ে সংক্ষেপে সেগুলোর দিকে নজর দিয়েছি কিন্তু এখনও তাদের সম্পূর্ণ ক্ষমতা দেখিনি। চলুন শুরু করা যাক!