در دنیای برنامه نویسی استانداردترین کدها آن هایی هستند که دارای کمترین تکرار کد باشند. اما موقعیت هایی به وجود میآید که ما مجبور میشویم کدهای یکسان را برای انجام کارهای تقریبا یکسان با تغییرات کوچک بارها و بارها بنویسیم. در این وضعیت باید به دنبال راهکارهایی باشیم که این تکرارها را به حداقل ممکن برسانیم. برای مبارزه با این تکرارها از کلاسها و متدهای abstract یا انتزاعی استفاده میکنیم.
خط تولید یک کارخانه دونات پزی را در نظر بگیرید. دوناتها با شیوه پخت یکسان پخته میشوند. پس از آن سس روکش مناسب با رنگها و طعمهای مختلف مثل خامه رنگی، شکلات، قرصهای رنگی و … روی آنها ریخته میشود. در پایان بسته به نوع سسی که روی آنها ریخته شده بسته بندی میشوند.
میبینید که همه دوناتها با هر طعم و شکلی که باشند شیوه پخت یکسانی دارند. روش نادرست این است که برای هر رنگ دونات یک خط تولید جداگانه ساخته شود. آیا این روش منطقی است؟ روش دیگر این است که خط تولید تا مرحله پخت مشترک باشد. سپس دوناتها در مرحله ریختن سس به خط تولید جداگانه دسته بندی و در پایان بسته بندی شوند. کد زیر خط تولید جداگانه دونات شکلاتی را در کلاس ChocolateDonut نمایش میدهد:
class ChocolateDonut {
public function make() {
return $this
->makeDough()
->moldDonut()
->fryDonut()
->addChocolate()
->packChocolateDonut();
}
protected function makeDough() {
print "make donut dough, first. ";
return $this;
}
protected function moldDonut() {
print "then, mold donut to have a wheel shape. ";
return $this;
}
protected function fryDonut() {
print "fry the donut carefully. ";
return $this;
}
protected function addChocolate() {
print "Add enough chocolate on the donut!";
return $this;
}
protected function packChocolateDonut() {
print "Pack donut with brown Packing! ";
return $this;
}
}
$chocolateDonut = new ChocolateDonut();
$chocolateDonut->make();
برای تهیه دونات با سس توت فرنگی نیز همین مراحل تکرار میشود:
class StrawberryDonut {
public function make() {
return $this
->makeDough()
->moldDonut()
->fryDonut()
->addStrawberrySauce()
->packStrawberryDonut();
}
protected function makeDough() {
print "make donut dough, first. ";
return $this;
}
protected function moldDonut() {
print "then, mold donut to have a wheel shape. ";
return $this;
}
protected function fryDonut() {
print "fry the donut carefully. ";
return $this;
}
protected function addStrawberrySauce() {
print "Add enough strawberry on the donut!";
return $this;
}
protected function packStrawberryDonut() {
print "Pack donut with pink Packing! ";
return $this;
}
}
$strawberryDonut = new StrawberryDonut();
$strawberryDonut->make();
اما گفتیم که نیازی به تکرار کدهای تهیه دونات نیست. چراکه فرآیند تهیه دونات به جز بخش تزئین، یک فرآیند قراردادی و تکراری است. بنابراین بخشهای تکراری کد را از طریق استفاده از abstract به یک قرارداد تبدیل میکنیم:
abstract class DonutTemplate {
public function make() {
return $this
->makeDough()
->moldDonut()
->fryDonut()
->addDressing()
->packDonut();
}
protected function makeDough() {
print "make donut dough, first. ";
return $this;
}
protected function moldDonut() {
print "then, mold donut to have a wheel shape. ";
return $this;
}
protected function fryDonut() {
print "fry the donut carefully. ";
return $this;
}
protected abstract function addDressing();
protected abstract function packDonut();
}
class ChocolateDonut extends DonutTemplate {
protected function addDressing()
{
print "Add enough chocolate on the donut!";
return $this;
}
protected function packDonut()
{
print "Pack donut with brown Packing! ";
return $this;
}
}
class StrawberryDonut extends DonutTemplate {
protected function addDressing()
{
print "Add enough strawberry sauce on the donut!";
return $this;
}
protected function packDonut()
{
print "Pack donut with pink Packing! ";
return $this;
}
}
$chocoDonut = new ChocolateDonut();
$chocoDonut->make();
همانطور که در کد میبینید، یک کلاس abstract کلاسی است که شامل حداقل یک متد abstract باشد. متدهای abstract متدهایی هستند که فاقد پیاده سازی هستند و فقط کلاسهای فرزند را وادار میکنند آن متدها را در خود پیاده سازی کنند. در کد بالا با استفاده از کلمه کلیدی extends کلاس فرزند را وادار کرده ایم از کلاس والد abstract تبعیت کند. در صورتی که کلاس ChocolateDonut، متد addDressing را پیاده سازی نکند، برنامه با ارور مواجه میشود.
از این پس برای ساخت دوناتهای جدید با هر نوع سس تزئینی، کافی است کلاس دونات جدید، کلاس abstract با نام DonutTemplate را پیاده سازی نماید.
نتیجه گیری
در این مطلب به بررسی abstract و روش استفاده از آن پرداختیم. همانطور که مشاهده کردید، استفاده از این روش خطوط کد را کاهش داده و آن را قانونمند و قابل اعتماد میکند. در کار تیمی، ایجاد قراردادها به سرپرست تیم کمک میکند کدهای یکپارچه دریافت کند. در واقع یکی از کاربردهای اصلی abstract زمانی است که میخواهید توسعه دهندگان تیم خود را مجبور کنید تعدادی متد ایجاد کنند و ترجیح میدهید متدهای آنها حتما مطابق با ساختارهای تعیین شده از طرف شما باشد. نکته مهم دیگر این است که یک کلاس میتواند چندین interface را پیاده سازی کند ولی فقط از یک کلاس abstract به ارث ببرد. چرا که interface صرفا مجموعه ای از متدهای abstract است.