ডিজাইন প্যাটার্নসমুহ: সিংগ্যালটোন প্যাটার্ন
() translation by (you can also view the original English article)
এই প্রবন্ধ থেকে আপনি জানতে পারবেন কিভাবে সিংগ্যালটোন ডিজাইন প্যাটার্ন বাস্তবায়ন করতে হয়, আর কখন এবং কিভাবে এই প্যাটার্ন আপনার এপ্লিকেশনে ব্যবহার করবেন। সিংগ্যালটোন নামের মতই, এই মেথড একটি ক্লাশের জন্য একটি এবং শুধুমাত্র একটি অবজেক্ট তৈরির পরামর্শ দেয়।
চলুন দেখা যাক উইকিপিডিয়া এই ডিজাইন প্যাটার্ন সম্পর্কে কি বলে:
সিংগ্যালটোন প্যাটার্ন হচ্ছে একটি ডিজাইন প্যাটার্ন যা নিয়ন্ত্রন করে যেন একটি ক্লাশ থেকে একটি এবং কেবলমাত্র একটিই অবজেক্ট তৈরি হয়। এটা তখনই উপকারী যখন একটি অবজেক্টই সম্পুর্ণ সিসটেমের সব এ্যাকশনের মধ্যে যথাযথভাবে সমন্বয় করতে পারে।
উপরের সংঙ্গায় যেমন বলা হয়েছে, যখন আমরা নিশ্চিত করতে চাই যে কোন ক্লাশের জন্য একটা এবং শুধুমাত্র একটাই অবজেক্ট তৈরি হওয়া দরকার, তখন আমাদের ঐই ক্লাশের জন্য সিংগ্যালটোন ডিজাইন প্যাটার্ন বাস্তবায়ন করা উচিত।
আপনি হয়তো প্রশ্ন করতে পারেন, কেন আমরা এমন একটা ক্লাশ তৈরি করব যার শুধুমাত্র একটাই অবজেক্ট তৈরি হবে। আমি বলব এই রকম অনেক ব্যবহারক্ষেত্র রয়েছে যেখানে এই ডিজাইন প্যাটার্ন ব্যবহার করা যায়। এগুলির মধ্যে আছে: কনফিগারেশন ক্লাশ, সেশন ক্লাশ, ডেটাবেজ ক্লাশ, এবং আরো অনেক।
এই প্রবন্ধের জন্য আমি ডেটাবেজ ক্লাশের উদাহরণ ব্যবহার করব। প্রথমে আমরা দেখব যদি সিংগ্যালটোন প্যাটার্ন ব্যবহার না করি তখন আসলে কি কি সমস্যা হতে পারে।
সমস্যা
ধরাযাক একটা খুব সাধারণ ডেটাবেজ ক্লাশ আছে যখন আমরা তার একটি অবজেক্ট তৈরি করি তখন সে একটি ডেটাবেজ কানেকশন তৈরি করে।
1 |
class database { |
2 |
|
3 |
private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null; |
4 |
|
5 |
public function __construct($dbDetails = array()) { |
6 |
|
7 |
$this->dbName = $dbDetails['db_name']; |
8 |
$this->dbHost = $dbDetails['db_host']; |
9 |
$this->dbUser = $dbDetails['db_user']; |
10 |
$this->dbPass = $dbDetails['db_pass']; |
11 |
|
12 |
$this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass); |
13 |
|
14 |
}
|
15 |
|
16 |
}
|
উপরের উদাহরণ থেকে দেখা যায় যে, আপনি যতবার এই ক্লাশের অবজেক্ট তৈরি করবেন প্রতিবারই একটা করে ডেটাবেজ কানেকশন তৈরি হবে। যদি ডেভেলপার বিভিন্ন জায়গায় এই ক্লাশের অবজেক্ট তৈরি করে, তাহলে কল্পনা করুন ডাটাবেজ সার্ভারের সাথে কতগুলো (অভিন্ন) ডাটাবেজ কানেকশন তৈরি হবে।
সুতরাং নিজের অজান্তেই ডেভেলপার ভুল করছে যা ডাটাবেজের এবং এ্যপ্লিকেশন সার্ভারের গতির উপর একটি বিশাল প্রভাব ফেলবে। চলুন এই জিনিসটাই আমরা এই ক্লাশের অন্য একটা অবজেক্ট তৈরি করে দেখি।
1 |
$dbDetails = array( |
2 |
'db_name' => 'designpatterns', |
3 |
'db_host' => 'localhost', |
4 |
'db_user' => 'root', |
5 |
'db_pass' => 'mysqldba' |
6 |
);
|
7 |
|
8 |
$db1 = new database($dbDetails); |
9 |
var_dump($db1); |
10 |
$db2 = new database($dbDetails); |
11 |
var_dump($db2); |
12 |
$db3 = new database($dbDetails); |
13 |
var_dump($db3); |
14 |
$db4 = new database($dbDetails); |
15 |
var_dump($db4); |
16 |
|
17 |
// Output
|
18 |
object(database)[1] |
19 |
private 'dbName' => string 'designpatterns' (length=14) |
20 |
private 'dbHost' => string 'localhost' (length=9) |
21 |
private 'dbPass' => string 'mysqldba' (length=8) |
22 |
private 'dbUser' => string 'root' (length=4) |
23 |
public 'dbh' => object(PDO)[2] |
24 |
object(database)[3] |
25 |
private 'dbName' => string 'designpatterns' (length=14) |
26 |
private 'dbHost' => string 'localhost' (length=9) |
27 |
private 'dbPass' => string 'mysqldba' (length=8) |
28 |
private 'dbUser' => string 'root' (length=4) |
29 |
public 'dbh' => object(PDO)[4] |
30 |
object(database)[5] |
31 |
private 'dbName' => string 'designpatterns' (length=14) |
32 |
private 'dbHost' => string 'localhost' (length=9) |
33 |
private 'dbPass' => string 'mysqldba' (length=8) |
34 |
private 'dbUser' => string 'root' (length=4) |
35 |
public 'dbh' => object(PDO)[6] |
36 |
object(database)[7] |
37 |
private 'dbName' => string 'designpatterns' (length=14) |
38 |
private 'dbHost' => string 'localhost' (length=9) |
39 |
private 'dbPass' => string 'mysqldba' (length=8) |
40 |
private 'dbUser' => string 'root' (length=4) |
41 |
public 'dbh' => object(PDO)[8] |
আপনি যদি উপরে কোড এবং আউটপুট দেখেন, দেখতে পাবেন যে প্রতিটি অবজেক্টেরই একটি নতুন রিসোর্স আইডি ধার্য করা হচ্ছে, তাই সব অবজেক্টই সম্পূর্ণ নতুন রেফারেন্স, সেক্ষেত্রে পৃথক মেমরিও বরাদ্দ হচ্ছে। তাই অজান্তেই আমাদের এ্যপ্লিকেশন অপ্রয়োজনীয় রিসোর্স দখলে করে নিচ্ছে।
সমাধান
ডেভেলপার কিভাবে আমাদের মূল ফ্রেমওয়ার্ক ব্যবহার করবে তা আমাদের নিয়ন্ত্রনে নেই। কোড রিভিউ প্রসেসের পরে এটা আমাদের নিয়ন্ত্রণে আসে, কিন্তু ডেভেলাপমেন্টের সময় আমরা তাদের পিছনে সবসময় দাড়িয়ে থাকব না।
এধরনের পরিস্থিতি মোকাবেলা করার জন্য, আামাদের মূল ক্লাশ এমন ভাবে প্রস্তুত করতে হবে যেন এই ক্লাশের একাধিক অবজেক্ট তৈরিই না হয়; তার পরিবর্তে ইতিমধ্যে প্রস্তুতকৃত অবজেক্ট যদি থাকে সেটাই দিবে। এক্ষেত্রে আমরা আমাদের মূল ক্লাস জন্য সিংগ্যালটোন প্যাটার্ন বিবেচনা করতে পারি।
এই প্যাটার্ন প্রয়োগ করার সময়, আামাদের লক্ষ্য হবে এই ক্লাশের যেন একটা এবং একটাই অবজেক্ট তৈরি হয়। এখন আমি ক্লাশটি আগে তৈরি করি, তারপর এই ক্লাশের প্রতিটি অংশ নিয়ে পর্যালোচনা করব।
1 |
class database { |
2 |
|
3 |
private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null; |
4 |
private static $instance = null; |
5 |
|
6 |
private function __construct($dbDetails = array()) { |
7 |
|
8 |
// Please note that this is Private Constructor
|
9 |
|
10 |
$this->dbName = $dbDetails['db_name']; |
11 |
$this->dbHost = $dbDetails['db_host']; |
12 |
$this->dbUser = $dbDetails['db_user']; |
13 |
$this->dbPass = $dbDetails['db_pass']; |
14 |
|
15 |
// Your Code here to connect to database //
|
16 |
$this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass); |
17 |
}
|
18 |
|
19 |
public static function connect($dbDetails = array()) { |
20 |
|
21 |
// Check if instance is already exists
|
22 |
if(self::$instance == null) { |
23 |
self::$instance = new database($dbDetails); |
24 |
}
|
25 |
|
26 |
return self::$instance; |
27 |
|
28 |
}
|
29 |
|
30 |
private function __clone() { |
31 |
// Stopping Clonning of Object
|
32 |
}
|
33 |
|
34 |
private function __wakeup() { |
35 |
// Stopping unserialize of object
|
36 |
}
|
37 |
|
38 |
}
|
এখানে একটি ছোট্ট উপসর্গ আছে যা থেকে বুঝা যায় এটা একটা সিংগ্যালটোন ক্লাশ। সবার প্রথমটা হল প্রাইভেট কনস্ট্রাকটর, যা new কীওয়ার্ড দিয়ে অবজেক্ট তৈরি ব্যহত করে। অপর উপসর্গটি হল স্টেটিক মেম্বার ভেরিয়্যাবল যা ইতিমধ্যে প্রস্তুতকৃত অবজেক্টের রেফারেন্স ধারন করে।
1 |
$dbDetails = array( |
2 |
'db_name' => 'designpatterns', |
3 |
'db_host' => 'localhost', |
4 |
'db_user' => 'root', |
5 |
'db_pass' => 'mysqldba' |
6 |
);
|
7 |
|
8 |
$db1 = database::connect($dbDetails); |
9 |
var_dump($db1); |
10 |
$db2 = database::connect($dbDetails); |
11 |
var_dump($db2); |
12 |
$db3 = database::connect($dbDetails); |
13 |
var_dump($db3); |
14 |
$db4 = database::connect($dbDetails); |
15 |
var_dump($db4); |
16 |
|
17 |
// Output
|
18 |
|
19 |
object(database)[1] |
20 |
private 'dbName' => string 'designpatterns' (length=14) |
21 |
private 'dbHost' => string 'localhost' (length=9) |
22 |
private 'dbPass' => string 'mysqldba' (length=8) |
23 |
private 'dbUser' => string 'root' (length=4) |
24 |
public 'dbh' => object(PDO)[2] |
25 |
object(database)[1] |
26 |
private 'dbName' => string 'designpatterns' (length=14) |
27 |
private 'dbHost' => string 'localhost' (length=9) |
28 |
private 'dbPass' => string 'mysqldba' (length=8) |
29 |
private 'dbUser' => string 'root' (length=4) |
30 |
public 'dbh' => object(PDO)[2] |
31 |
object(database)[1] |
32 |
private 'dbName' => string 'designpatterns' (length=14) |
33 |
private 'dbHost' => string 'localhost' (length=9) |
34 |
private 'dbPass' => string 'mysqldba' (length=8) |
35 |
private 'dbUser' => string 'root' (length=4) |
36 |
public 'dbh' => object(PDO)[2] |
37 |
object(database)[1] |
38 |
private 'dbName' => string 'designpatterns' (length=14) |
39 |
private 'dbHost' => string 'localhost' (length=9) |
40 |
private 'dbPass' => string 'mysqldba' (length=8) |
41 |
private 'dbUser' => string 'root' (length=4) |
42 |
public 'dbh' => object(PDO)[2] |
আপনি যদি উভয় বিভাগের আউটপুট তুলনা করেন তাহলে দেখতে পাবেন, সিংগ্যালটোন প্যাটার্নের আউটপুটে, ভিন্ন ভিন্ন অবজেক্টের জন্য রিসোর্স আইডি একই। কিন্তু ডিজাইন প্যাটার্ন ব্যবহারের পূর্বে এমনটি ছিল না।
এন্টি-প্যাটার্ন হিসেবে সিংগ্যালটোন
বিভিন্ন কারনে এই ডিজাইন প্যাটার্নকে এন্টি-প্যাটার্নও বলা হয়ে থাকে, যা আমি নিচে উল্লেখ করেছি:
- এটা তার নিজের সৃষ্টি এবং জীবনচক্র নিয়ন্ত্রণের মাধ্যমে সিংগ্যাল রেসপন্সিবিলিটি নীতি লঙ্ঘন করে।
- এটা এপ্লিকেশনে গ্লোবাল স্টেট প্রবর্তন করে। আমি গ্লোবাল স্টেটেকে খুব খারাপ বলতে পারি কারন যেকোন কোড তার মান পরিবর্তন করতে পারে। তাই ডিবাগের সময়ে এটা খুজে বের করা খুবই কঠিন যে কোডের কোন অংশ গ্লোবাল স্টেটের বর্তমান মানের জন্য দায়ী।
- সিংগ্যালটোন একটি খারাপ ধারণা যদি আপনি ইউনিট টেস্টিং করেন, আর ইউনিট টেস্ট না করাও খারাপ।
উপসংহার
আমি আমার যথাসাধ্য চেষ্টা করেছি সিংগ্যালটোন ডিজাইন প্যাটার্ন ব্যাখ্যা করতে, যা ইন্টারনেটে ব্যাপকভাবে আলোচিত। আশাকরি এই প্রবন্ধ আপনার উপকরে আসবে। আমরা এটাকে এন্টি-প্যাটার্ন ও ডিজাইন প্যাটার্ন উভয় ভাবেই আলোচনা করেছি।
আনুগ্রহ করে আপনার মতামত, পরামর্শ অথবা প্রশ্ন নিচে জানান, এবং আমি যত তাড়াতারি সম্ভব উত্তর দিব। আপনি টুইটারেও আমাকে পেতে পারেন @XpertDevelopers অথবা সরাসরি ইমেইল করতে পারেন।