اصول OOP به زبان خیلی ساده بهمراه مثال های عملی

اصول OOP به زبان خیلی ساده بهمراه مثال های عملی

این آموزش برای افرادی که با شی گرایی آشنا هستند ولی نیاز دارند برای تازه شدن حافظه شان مطلبی را به صورت سریع مرور کنند و یا افرادی که بعضی از بخش های اصلی OOP برایشان جا نیافتاده است مفید خواهد بود.

شی گرایی چیست؟

شی گرایی روشی است که اطلاعات (Properties) و رفتار (Method) های مربوط به یک دیتا را در یک بسته کنار هم جمع می کند. و به این بسته شی یا Object میگوید. برای مثال من درحال نوشتن یک فروشگاه اینترنتی هستم و تمامی اطلاعات و رفتارهایی که برروی یک کالا میتواند وجود داشته باشد را در یک بسته قرار میدهم. مثلا اطلاعاتی مانند نام ، قیمت و اندازه همچنین رفتارهایی مانند فروختن ، اضافه کردن به انبار و ... .
شی گرایی در سه نقطه از مسیر تولید یک نرم افزار حضور دارد. ابتدا وقتی که قرار است قابلیت ها و عملکردهای سیستم را تحلیل کنیم که به این مرحله Object Oriented Analysis گفته می شود. حالا این تحلیل ها را میخواهیم به یک معماری و ساختار برای برنامه مان تبدیل کنیم که این مرحله هم Object Oriented Design نام دارد. و مرحله ی آخر هم پیاده سازی قابلیت های برنامه تحت معماری طراحی شده است که Object Oriented Programming نام دارد که به اختصار OOP هم به آن می گویند. این بخش معروفترین بخش از موضوع شی گرایی در بین برنامه نویسان هست چرا که عموما مراحل قبلی توسط مدیران پروژه و مدیران فنی انجام می شود.

عنوان تبلیغ: آموزش کامل برنامه نویسی شی گرا (OOP) در PHP

خوب است بدانیم که این پارادایم برنامه نویسی محدود به زبان یا تکنولوژی خاصی نمی شود. البته بعضی از زبانهای برنامه نویسی این پارادایم را پشتیبانی نمی کنند و یا بعضی از بخش های Object Oriented در بعضی زبانها پیاده سازی نشده است.
برای اینکه بتوانیم اصول را به خوبی درک کنیم نیاز است نکات اولیه و ابزارهایی که در Object Oriented در اختیار داریم را بشناسیم و با کمک آنها اصول را مطرح کنیم. این بخش ها و ابزارها عبارتند از:

UML چیست؟

Unified Modeling Language یا به اختصار UML تکنیکی است برای مستند سازی و طراحی سیستم های Object Oriented. مستندات مختلف و متعددی در UML وجود دارد که پرکاربردترین آنها Class Diagram است.
کلاس دیاگرام ها نمایش بصری کلاس ها هستند که به ویژگی ها و رفتارهای هر کلاس، همچنین روابط بین کلاس ها می پردازند. در شکل زیر معرفی یک کلاس در Class Diagram را مشاهده می کنید.

در Class Diagram ها اطلاعات زیر نمایش داده میشود.
1- بخش بالایی : نام کلاس 
2- بخش وسطی : ویژگی ها یا Property های کلاس
3- بخش پایینی : رفتارها یا method های کلاس
4- علامت منفی (-) : این Property ها یا Method ها Private هستند یعنی تنها در همین کلاس قابل مشاهده و استفاده هستند.
5- علامت مثبت (+) : معرف Property ها یا Method هایی که Public هستند یعنی توسط همه ی کلاس های برنامه قابل مشاهده و استفاده هستند.
6- علامت هش (#) : این علامت بیانگر Property ها و Method هایی هست که به صورت Protected معرفی شده اند و میتوان از طریق خود کلاس و کلاسهایی که فرزندان آن هستند به آنها دسترسی پیدا کرد. (کلاس های فرزند و پدر جلوتر توضیح داده میشود)

💎 برای یادگیری دقیق‌تر UML و آشنایی با نرم افزار های طراحی UML، می‌توانید به بخش آموزشی آشنایی با یو ام ال از دوره‌ی رایگان آموزش برنامه نویسی مراجعه کنید.

Class و Object چیست؟

در ابتدا وقتی خواستیم شی گرایی را توصیف کنیم، گفتیم وقتی ویژگی ها و رفتارهای مربوط به یک چیز خاص را بسته بندی می کنیم به آن بسته ها شی یا Object گفته می شود. اینکه هر بسته باید شامل چه مواردی باشد قوانینی است که در کلاس ها تعریف می شود. Object ها از روی کلاس ها ساخته می شوند و از هر کلاس میتوان بینهایت شی ساخت (اگر در خود کلاس جلوی این قابلیت گرفته نشده باشد. منظور طراحی هایی مانند Singleton Design Pattern است که می توانید برای آشنایی با آن به آموزش Singleton Design Pattern در سایت سکان آکادمی سر بزنید.) و تمامی این Object ها باید قوانین حکم شده در کلاس را رعایت کنند. برای درک بهتر، یک شهرک مسکونی را تصور کنید که تمام خانه های آن از روی یک نقشه ساخته شده اند. در این مثال نقشه همان کلاس است و خانه های ساخته شده Object ها هستند.
به عنوان مثالی برنامه نویسی تر، کلاسی تعریف میکنیم با عنوان User که Class Diagram زیر آن را معرفی میکند.

همانطور که میبینید این کلاس ویژگی هایی دارد شامل ایمیل و نام که برای هر کاربر میتواند مقداری متفاوت باشد. هر کاربری (Object) که از روی این کلاس ساخته می شود می تواند یک ایمیل و یک نام داشته باشد.

در تصویر بالا ما Hamid Reza ، Sara و Ali را به عنوان Object هایی از روی کلاس User ساختیم.

در زبان PHP تعریف کلاس به صورت زیر است:

class User
{
//Some Code
}

که برای این کار از کلمه ی کلیدی Class استفاده میکنیم.

و ساختن Object از تعریف کلاس هم ساده تر است:

//First User
$user1 = new User();
//Second User
$user2 = new User();
//Third User
$user3 = new User();

همانطور که می بینید ما از روی یک کلاس تعداد زیادی Object میتوانیم بسازیم.

Interface

به زبان ساده اینترفیس ها قرار دادهایی هستند که کلاس های تحت آن قرار داد را مجبور می کند تا یکسری رفتارهای مشخص را برای خودشان تعریف کنند. مثلا ما Interface ای را برای User تعریف می کنیم و در آن قید میکنیم که هر کلاسی که این Interface را فراخوانی کرد باید رفتار "()addUser" و "()deleteUser" را هم در کلاس خود پیاده سازی کند.

به کلاس دیاگرام این توضیح نگاه کنید:

در Interface ها، متدها بدنه ندارند و فقط نام ، ورودی و خروجی متد را مشخص می کنند که از چه نوعی باشد. تعریف بدنه و اینکه آن کار مشخص شده به چه صورت انجام شود وظیفه ی کلاس هایی است که از آن Interface استفاده می کنند.

این قابلیت در OOP به ما این امکان را میدهد که برنامه های ما استاندارد تر باشد. برای مثال فرض کنید در تیم ما دو برنامه نویس هستند که یکی از آنها کلاس Admin دیگری کلاس Customer را قرار است توسعه دهند. هر یک از این کلاس های شرایط خاص خود را دارد و پیچیدگی هایی درون آنها وجود دارد ولی بقیه ی برنامه تنها به دو متد اصلی آنها یعنی ()addUser و ()deleteUser نیاز دارد. تعریف Interface به برنامه نویسان اجازه نمیدهد که مثلا متدی به عنوان ()addAdminUser پیاده سازی کنند و یا فراموش کنند که ()deleteUser را تعریف کنند.

Abstract Class

Abstract Class ها یا کلاسهای انتزاعی گونه ای از کلاس ها هستند که شباهت بسیار زیادی با Interface ها دارند. مشابه interface ها این کلاس ها می توانند بعضی از متدها را قرارداد کنند تا کلاسهایی که آنها را فراخوانده اند محبور باشند تعریفی برای آنها ارائه دهند. و همچنین برخلاف Interface ها می توانند متدها و ویژگی هایی را هم برای خودشان تعریف کنند.

نکته ی مهم درباره ی این کلاس ها این است که در بسیاری از زبان ها به ویژه زبان PHP شما نمی توانید از روی این کلاس ها Object مستقلی بسازید.

شی گرایی یا همان Object Oriented که در بخش قبلی با آن تا حد خوبی آشنا شدیم برپایه ی چهار اصل استوار شده است که عبارتند از :

Abstraction یا Data Abstraction

Abstraction یا نام دیگری که به آن می دهند Data Abstraction به این مفهوم است که از بخش هایی از اطلاعات و داده های Object که به برنامه ی ما مربوط نمی شود چشم پوشی کنیم. اجازه بدید این موضوع را با مثالی ساده برایتان توضیح بدهم. ما قرار است برای یک آژانس هواپیمایی برنامه ای بنویسیم که مشتریان آن قرار است بلیطی را برای سفر هوایی خود از آن بخرند. خوب در این مثال یک شی مهم وجود دارد و آن هم هواپیماست. سوالی که باید از خود بپرسیم این است که چه اطلاعاتی از هواپیما برای ما اهمیت پیدا میکند؟ آیا اطلاعاتی از جمله جنس بدنه ، ارتفاع پروازی و ... اهمیت پیدا می کند یا اینکه این هواپیما از چه مبدایی به چه مقصدی می رود و اینکه چه تعداد مسافر می توانند سوار آن بشوند. طبیعتا برنامه ی ما به دسته ی دوم اطلاعات نیاز دارد تا بتوانیم بلیط مناسبی را قبل از پر شدن هواپیما به مشتریمان بفروشیم. پس در لایه ی طراحی برنامه از اطلاعات اضافه (فنی) هواپیما چشم پوشی می کنیم.

نکته ی بسیار مهم در تعریف Data Abstraction این است که از اطلاعات در لایه ی طراحی چشم پوشی می شود.

Inheritance

شاید راحت ترین اصل شی گرایی برای درک و آموختن همین Inheritance (ارث بری) باشد. چرا که در زندگی واقعی بسیار ملموس است. این اصل میگوید که برخی از کلاس ها میتوانند ویژگی ها و یا رفتارهای کلاس های دیگر را به ارث ببرند. یا به زبان برنامه نویسی می توانیم در یک کلاس ویژگی ها و یا رفتاری هایی را تعریف کنیم و در کلاس های دیگری که از آن کلاس ارث برده اند ، از آن ویژگی ها و یا رفتارها بهره ببریم.
به کلاسی که ارث داده است کلاس والد (Parent) یا Super Class و به کلاسی که ارث برده است کلاس فرزند (Child) یا Sub Class می گویند.

البته ناگفته نماند که تنها ویژگی ها و رفتارهایی که کلاس Parent بخواهد در کلاس Child قابل دسترس است.
به مثال ز یر دقت کنید.
- Customer یک User است.

و تمامی Property ها و Method های کلاس User که به صورت Public یا Protected تعریف شده اند در کلاسهای Admin و Customer قابل مشاهده و استفاده هستند.

مجموعه کدهای Class Diagram بالا در زبان PHP به صورت زیر است.

class User
{
private $email;
private $name;

public function __construct($name , $email)
{
$this->setName($name);
$this->setEmail($email);
}

/**
* @param mixed $email
*/
public function setEmail($email)
{
$this->email = $email;
}

/**
* @param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}

/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}

/**
* @return mixed
*/
public function getName()
{
return $this->name;
}
}

class Admin extends User{
private $username;
private $permission;

public function __construct($name, $email)
{
parent::__construct($name, $email);
}

/**
* @param mixed $username
*/
public function setUsername($username)
{
$this->username = $username;
}

/**
* @param mixed $permission
*/
public function setPermission($permission)
{
$this->permission = $permission;
}

/**
* @return mixed
*/
public function getUsername()
{
return $this->username;
}

/**
* @return mixed
*/
public function getPermission()
{
return $this->permission;
}
}

class Customer extends User{
private $username;
private $creditCard;

public function __construct($name, $email)
{
parent::__construct($name, $email);
}

/**
* @param mixed $username
*/
public function setUsername($username)
{
$this->username = $username;
}

/**
* @param mixed $creditCard
*/
public function setCreditCard($creditCard)
{
$this->creditCard = $creditCard;
}

/**
* @return mixed
*/
public function getUsername()
{
return $this->username;
}

/**
* @return mixed
*/
public function getCreditCard()
{
return $this->creditCard;
}
}

استفاده از آنها هم به صورت زیر خواهد بود:

$admin = new Admin('Hamid Reza','hrmadani2@gmail.com');
$customer = new Customer('Ali' , 'ali@sokanacademy.com');

$admin->setUsername('hrmadani');
$admin->setPermission('all');

$customer->setUsername('ali65');
$customer->setCreditCard('5896989852524515');

echo "Admin Name : ". $admin->getName() . ". Admin Username : ". $admin->getUsername(); //Admin Name : Hamid Reza. Admin Username : hrmadani
echo "Customer Name : ". $customer->getName() . ". Customer Username : ". $customer->getUsername(); //Customer Name : ali. Customer Username : ali65

همانطور که مشاهده میکنید متد ()getName که در کلاس پدر تعریف شده بود حالا در شی ساخته شده در کلاس فرزند(Admin) قابل استفاده است.

Polymorphism

اصل بعدی که Object Oriented برپایه ی آن استوار است یکی از زیبایی های این سبک برنامه نویسی است که به آن Polymorphism (پلی مرفیسم = چند ریختی) گفته می شود. دغدغه ی اصلی Polymorphism گسترش پذیری هرچه بهتر و بیشتر برنامه است در کنار این که پشتیبانی و عیب یابی در برنامه را هم خیلی راحت تر خواهد کرد. این اصل به ما اجازه می دهد که بتوانیم یک کار خاص را به شکل های مختلفی توسعه بدهیم. برای مثال در مثال آژانس هواپمایی ما می خواهیم بلیط هواپیما بخریم. ولی هواپیمایی هما به ساختاری از اطلاعات نیاز دارد و روش خاصی برای این کار دارد و هواپیمایی آسمان روش متفاوتی دارد و ساختار داده ای دیگری را از ما می پذیرد. پس درواقع به دو پیاده سازی مختلف نیاز داریم که در برنامه ی مان به یک روش صدایشان بزنیم. در نتیجه ما یک متد مشخص برای خرید بلیط را قرار داد می کنیم و به واسطه ی ورودی نیازمان (سفارش مشتری) متد خرید بلیط در کلاس هما یا آسمان فراخوانی و اجرا می شود.

یکی دیگر از کاربردهای این اصل که شاید بتواند به درک بهتر آن هم کمک کند زمانیست که شما برنامه ای می نویسید و می خواهید به دو دیتابیس مختلف متصل شود. مثلا برنامه شما قرار است با دیتابیس MySQL و MongoDB ارتباط برقرار کند. پس شما به دو دیتابیس مختلف نیاز دارید که در هر یک از آنها متدهایی نیاز است تا کار اتصال و CRUD – منظور چهار عمل اصلی در دیتابیس هاست که مخفف عبارت های Create به معنای ثبت داده ی جدید، Read به معنای خواندن داده های موجود، Update به روزرسانی داده ها و Delete حذف داده ها – را انجام دهد.

مهمترین بازیگر Polymorphism همان interface هایی هستند که در بالاتر با آنها آشنا شدیم.

به Class Diagram زیر دقت کنید.

در Class Diagram بالا چه چیزهایی می بینیم:
- کلاس Abstract ای داریم برای تعریف مشخصات اصلی دیتابیسی که قرار است از آن بهره ببریم.
- Interface ای داریم که کلاس های متصل به خود را مجبور میکند 5 متد مورد نیاز برای برنامه را با نام و مشخصات خواسته شده پیاده سازی کنند. 
- دو کلاس هم تعریف کردیم که در آنها قرار است متدهای مورد نظرمان پیاده سازی شود.

آن چیزی که به موضوع Polymorphism بر می گردد این است که الان یک فرم برای ارتباط با دیتابیس و دو نوع (که میتواند چندین نوع هم باشد) پیاده سازی داریم.

بریم سراغ پیاده سازی Class Diagram بالا:

abstract class DBCommon
{
private $host;
private $db;
private $uid;
private $pass;

public function __construct($host, $db, $uid, $pass)
{
$this->host = $host;
$this->db = $db;
$this->uid = $uid;
$this->pass = $pass;
}
}

interface DBInterface
{
public function dbConnection();

public function insert($data);

public function read($where);

public function update($where);

public function delete($where);
}

class MySQLDriver extends DBCommon implements DBInterface
{
public function __construct($host, $db, $uid, $password)
{
parent::__construct($host, $db, $uid, $password);
}

public function dbConnection()
{
// TODO: Implement dbConnection() method.
}

public function insert($data)
{
// TODO: Implement insert() method.
}

public function read($where)
{
// TODO: Implement read() method.
}

public function update($where)
{
// TODO: Implement update() method.
}

public function delete($where)
{
// TODO: Implement delete() method.
}
}

class MongoDBDriver extends DBCommon implements DBInterface
{

public function __construct($host, $db, $uid, $password)
{
parent::__construct($host, $db, $uid, $password);
}

public function dbConnection()
{
// TODO: Implement dbConnection() method.
}

public function insert($data)
{
// TODO: Implement insert() method.
}

public function read($where)
{
// TODO: Implement read() method.
}

public function update($where)
{
// TODO: Implement update() method.
}

public function delete($where)
{
// TODO: Implement delete() method.
}
}


حالا برای استفاده از این کد نوشته شده در برنامه مان هرجا که باید کاری را با دیتابیس انجام می دادیم کافیست مانند زیر عمل کنیم.

//Database's Configuration
$host = "127.0.0.1";
$db = "myApp";
$uid= "hrmadani";
$pass = "secret";

//If we need to use MySQL
$db = new MySQLDriver($host,$db,$uid,$pass);

//If we need to use MongoDB
$db = new MongoDBDriver($host,$db,$uid,$pass);

//Now the Class is prepared to use
$data = [];

//Connect to selected database
$db->dbConnection();
//Insert Data to selected database
$db->insert($data);

برای توضیح کد بالا هم میتوان گفت که در بخش اول اطلاعات ارتباطی دیتابیس برنامه را می دهیم (البته این کار، به این شکل صحیح نیست و بهتر است پروژه فایل تنظیماتی داشته باشد که این اطلاعات از آن خوانده شود). سپس شی مربوط به یکی از دیتابیس های مورد نظرمان را می سازیم و از این جا به بعد کاری به نوع عملکرد آن دیتابیس نداریم و فقط متد ()dbConnection را فراخوانی می کنیم تا به دیتابیس مورد نظرمان وصل شویم و بقیه ی کارهای توسط کلاس مورد نظر انجام خواهد شد.

Encapsulation

این اصل بیانگر آن است که، کلاس ها پیچیدگی های خود را از محیط بیرون خود مخفی می کنند و فقط ورودی ها و خروجی های مشخصی را تعریف خواهند کرد که با کمک آنها با محیط بیرون خود ارتباط برقرار کنند.

در شکل بالا خطوط قرمز و آبی ورودی های کپسول کد ما هستند و ما بدون اینکه بدانیم در داخل کپسول چه مسیر پر پیچ و خمی رو طی میکنند خروجی های مورد نظرمان را دریافت میکنیم.

معمولا در برنامه ها تعداد زیادی کلاس تعریف میشود که با رعایت این اصل بسیار مهم می توانیم ارتباط بین کلاس ها را منظم کنیم و عیب یابی در برنامه هم راحت تر خواهد بود.

این کار با استفاده از Setter Method ها و Getter Method ها انجام می شود. به کلاس هایی که کدشان را در بخش Inheritance نوشتیم دقت کنید. همگی متدهای Setter ای دارند برای ورودی های کلاس و برای خروجی های کلاس Getter هایی دارند. به این روش ما پیچیدگی های داخل این کلاس ها را از چشم بقیه ی بخش های برنامه مخفی کردیم.

مثلا فرض کنید می خواستیم در این کلاس ها متدی داشته باشیم که منحصر بودن Username در جدول User های دیتابیس را بررسی کند و یا سیاستی داشتیم برای اینکه Username باید شامل حروف و عدد باشد و همچنین نباید از Name کاربر در آنها استفاده شود. و یا مواردی از این دست پیچیدگی ها که فقط به همان کلاس مربوط می شود.

برای بسیاری از افراد مفهوم Encapsulation با Data Abstraction در هم ترکیب میشود و به فهم مناسبی نمی رسند. همانطور که در بخش Data Abstraction گفتم این اصل در زمان طراحی نقش بازی می کند و بخشی از اطلاعات که برای برنامه ی ما کاربردی ندارد را از دید ما مخفی می کند (مثل مسائل فنی هواپیما در زمان خرید بلیط آن) ولی Encapsulation در زمان برنامه نویسی نقش بازی می کند و بسیاری از پیچیدگی های اضافه را که در خارج از یک کلاس لزومی به اطلاع از آنها نیست را از دیدمان مخفی می کند.
مثلا فرآیند روشن شدن خودرو را در نظر بگیرید. راننده اطلاعی ندارد پس از چرخاندن سوییچ چه اتفاقی در موتور خودروی او می افتد ، فقط متوجه می شود که ماشین روشن شده است و باید مراحل بعدی را انجام دهد. این همان Encapsulation است که طی آن ما در جریان پیچیدگی های روشن شدن و عملکرد موتور خودرو قرار نمی گیریم و فقط با یک ورودی مشخص (چرخاندن سوئیچ)، خروجی مشخصی(روشن شدن خودرو) را بدست می آوریم.

امیدوارم این آموزش برایتان مفید بوده باشد و فهم مناسبی از این اصول بدست آورده باشید. اگر سوالی یا ابهامی داشتید خوشحال میشم در بخش نظرات با من در میان بزارید تا در اسرع وقت پاسخ بدم.

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon