چهار ستون برنامه‌نویسی شیءگرا

سه شنبه 9 دی 1399

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

چهار ستون برنامه‌نویسی شیءگرا

در این مقاله چهار ستون را معرفی می‌کنیم که اصول طراحی نرم‌افزار برای کمک به شما در نوشتن کدهای تمیز شی‌ءگرا هستند.

چهار ستون برنامه‌نویسی شیءگرا عبارتند از:

انتزاع (Abstraction)

کپسوله سازی (Encapsulation)

وراثت (Inheritance)

پلی‌مورفیسم (Polymorphism)

بیاید نگاهی دقیق به هر یک از آن‌ها بیاندازیم.

Abstraction در برنامه‌نویسی شیءگرا

Abstract چیزی به معنای پنهان کردن جزئیات پیاده‌سازی درون چیزی است؛ گاهی اوقات نمونه اولیه، گاهی اوقات یک تابع. بنابراین وقتی شما تابعی را فراخوانی می‌کنید نیازی نیست که دقیقا بفهمید چه کاری انجام می‌دهد.

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

با جزئیات خاص abstracting می‌توانید یک کد پایه قابل استفاده مجدد، قابل درک، و به راحتی قابل تغییر ایجاد کنید. بگذارید برایتان مثالی بزنیم:

function hitAPI(type){
	if (type instanceof InitialLoad) {
		// Implementation example
	} else if (type instanceof NavBar) {
		// Implementation example
	} else {
		// Implementation example
	}
}

آیا می‌توانید در این مثال ببینید که چطور باید دقیقا همان چیزی را که برای استفاده سفارشی خود نیاز دارید را پیاده‌سازی کنید؟

هر API جدیدی که باید به آن برسید به یک بلوک جدید احتیاج دارد، و این کد سفارشی خود را دارد. این abstract نیست زیرا شما باید نگران پیاده سازی هر نوع جدیدی باشید که اضافه می‌کنید. قابل استفاده مجدد نیست و نگهداری آن یک کابوس است.

در مورد چیزی مانند کد زیر چطور؟

hitApi('www.kealanparr.com', HTTPMethod.Get)

اکنون فقط می‌توانید یک URL و اینکه از کدام HTTP method می‌خواهید استفاده کنید را به تابع خود ارسال کنید و کار شما تمام است.

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

این همان چیزی است که Abstraction نامیده می‌شود. یافتن مواردی که در کد شما مشابه هستند و یک تابع یا آبجکت جنریک برای به‌ کار گیری در مکان‌های مختلف با موارد مختلف ارائه می‌شود.

در اینجا یک مثال نهایی خوب از Abstraction وجود دارد: تصور کنید در حال ایجاد دستگاهی برای تهیه قهوه برای کاربران خود هستید. دو رویکرد وجود دارد:

نحوه ساخت آن با Abstraction

داشتن دکمه‌ای با عنوان "درست کردن قهوه"

نحوه ساخت آن بدون Abstraction

داشتن دکمه‌ای با عنوان "جوشاندن آب"

داشتن دکمه‌ای با عنوان "افزودن آب سرد به کتری"

داشتن دکمه‌ای با عنوان "افزودن یه قاشق پودر قهوه به یک فنجان تمیز"

داشتن دکمه‌ای با عنوان "تمیز کردن هر فنجان کثیف"

و همه دکمه‌های دیگر

این یک مثال بسیار ساده است. اما اولین رویکرد، منطق را در دستگاه خلاصه می‌کند. اما رویکرد دوم کاربر را مجبور می‌کند که بفهمد چگونه قهوه را درست کند و اساسا قهوه خودش را درست کند.

ستون بعدی روشی را به ما نشان می‌دهد که می‌توانیم با استفاده از Encapsulation (کپسوله‌سازی) Abstraction را به دست آوریم.

Encapsulation در برنامه‌نویسی شیءگرا

تعریف encapsulation عمل محصورسازی چیزی است. از بین بردن دسترسی به قسمت‌هایی از کدتان و خصوصی کردن موارد دقیقا همان چیزی است که Encapsulation انجام می‌دهد (اغلب اوقات افراد از آن به عنوان مخفی کردن داده یاد می‌کنند).

Encapsulation به این معناست که هر آبجکت در کد شما باید وضعیت خود را کنترل کند. وضعیت "اسنپ‌شات" فعلی آبجکت شماست. کلیدها، متدهای موجود بر روی آبجکت‌ها، پراپرتی‌های بولین (Boolean) و غیره. اگر بخواهید یک بولین را دوباره ست کنید یا کلیدی را از آبجکت حذف کنید، همه آن‌ها تغییراتی در وضعیت شما هستند.

بخش‌هایی از کد که می‌توانند در دسترس باشند را محدود کنید. اگر مورد نیاز نیستند، آن‌ها را از دسترس خارج کنید.

پراپرتی‌های خصوصی با استفاده از closureها در جاوااسکریپت به دست می‌آیند. مثالی در زیر آورده شده است:

var Dog = (function () {

	// Private
	var play = function () {
		// play implementation
	};
    
	// Private
	var breed = "Dalmatian"
    
	// Public
	var name = "Rex";

	// Public
	var makeNoise = function () {
 		return 'Bark bark!';
	};

 	return {
		makeNoise: makeNoise,
		name: name
 	};
})();

اولین کاری که ما انجام دادیم ساخت تابعی بود که بلافاصله فراخوانی می‌شود (یا اصطلاحا به صورت IIFE فراخوانی می‌شود). این آبجکتی می‌سازد که هر کسی می‌تواند به آن دسترسی داشته باشد اما برخی از جزئیات را پنهان می‌کند. شما نمی‌توانید play را فراخوانی کنید و به breed دسترسی ندارید زیرا ما آن را در آبجکت نهایی با return نمایش نداده‌ایم.

این الگوی خاص در بالا Revealing Module Pattern نامیده می‌شود، اما این فقط یک نمونه از نحوه دستیابی به Encapsulation است.

ما می‌خواهیم بیشتر روی ایده Encapsulation تمرکز کنیم (زیرا این مهم‌تر از یادگیری یک الگو و شمارش Encapsulation است).

تأمل کنید، و بیشتر به این فکر کنید که چگونه می‌توانید داده‌ها و کد خود را پنهان کنید و آ‌ن‌ها را جدا کنید. ماژول‌سازی و داشتن مسئولیت واضح برای شیءگرایی یک امر کلیدی است.

چرا باید حریم خصوصی را ترجیح دهیم؟ چرا همه چیز نباید سراسری باشد؟

بسیاری از بخش‌های غیرمرتبط کد از طریق متغیر سراسری به یکدیگر وابسته و متصل می‌شوند.

در صورت استفاده مجدد از نام، احتمالا متغیرها را override می‌کنید، که می‌تواند منجر به باگ یا رفتار غیر قابل پیش‌بینی شود.

استدلال در کد دشوار می‌شود و آنچه که برای متغیرها و تغییر حالت در حال خواندن و نوشتن است را دنبال می‌کنید.

با جداسازی خطوط طولانی کد به توابع جداگانه کوچک‌تر Encapsulation می‌تواند اعمال شود. این توابع را در ماژول‌ها جدا کنید. ما داده‌ها را در مکانی که هیچ چیز دیگری نیاز به دسترسی به آن ندارد پنهان می‌کنیم، و آنچه را که مورد نیاز است را به روشنی آشکار می‌سازیم.

این خلاصه Encapsulation است. داده‌های خود را به چیزی، چه کلاس باشد، آبجکت، ماژول، یا تابع متصل کنید (بایند کنید)، و تمام تلاش خود را انجام دهید تا در حد امکان آن‌ها را به صورت خصوصی نگه دارید.

Inheritance (وراثت) در برنامه‌نویسی

Inheritance اجازه می‌دهد یک آبجکت پراپرتی‌ها و متدهای آبجکت دیگر را به دست آورد. در جاوااسکریپت این کار توسط Prototypal Inheritance انجام می‌شود.

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

هر زمان که ما از ارث‌بری استفاده می‌کنیم، سعی می‌کنیم آن را به گونه‌ای تنظیم کنیم که والد و فرزند همبستگی بالایی داشته باشند. همبستگی به میزان مرتبط بودن کد شما مربوط است. مثلا، آیا نوع Bird از نوع DieselEngine توسعه می‌یابد؟

ارث‌بری خود را با درکی ساده و قابل پیش‌بینی نگه دارید. از جایی که کاملا غیرمرتبط است ارث‌بری نکنید فقط به این دلیل که یک متد یا یک پراپرتی که مورد نیازتان است را دارد. وراثت این مشکل خاص را به خوبی حل نمی‌کند.

هنگام استفاده از وراثت، شما باید مورد نیازترین عملکرد را داشته باشید (شما همیشه کاملا به همه چیز نیاز ندارید).

توسعه‌دهندگان اصلی به نام اصل جایگزینی لیسکوف دارند. این اصل می‌گوید اگر می‌توانید از کلاس parent استفاده کنید (بگذارید آن را ParentType بنامیم) در هر جا شما می‌توانید از فرزند استفاده کنید (بیایید آن را ChildType بنامیم)، و ChildType از ParentType ارث‌بری می‌کند، پس شما تست را پاس کرده‌اید.

دلیل اصلی عدم موفقیت این تست، این است که ChildType مواردی را از والد حذف می‌کند. اگر ChildType متدهایی را که از والد ارث‌بری کرده است را حذف کند، این امر منجر به TypeError ای می‌شود که در آن موارد تعریف‌نشده‌ای وجود دارد که شما انتظار دارید نباشد.

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

Polymorphism در برنامه‌نویسی شیءگرا

پلی‌مورفیسم یعنی "شرایط رخ دادن در چندین شکل مختلف". این دقیقا همان چیزی است که مربوط به ستون چهارم و آخرین ستون برنامه‌نویسی شیءگرا است. نوع‌ها (types) در زنجیره وراثت قادر به انجام کارهای مختلف هستند.

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

در نمودار آخر، ممکن است نمونه اولیه‌ای داشته باشیم که Animal نامیده می‌شود و makeNoise را تعریف می‌کند. سپس هر نوع گسترش‌یافته از نمونه اولیه می‌تواند برای انجام کارهای سفارشی خود override شود. چیزی مثل این:

// Let's set up an Animal and Dog example
function Animal(){}
function Dog(){}

Animal.prototype.makeNoise = function(){
	console.log("Base noise");
};

// Most animals we code up have 4. This can be overridden if needed
Animal.prototype.legs = 4;

Dog.prototype = new Animal();

Dog.prototype.makeNoise = function(){
	console.log("Woof woof");  
};

var animal = new Animal();
var dog = new Dog();

animal.makeNoise(); // Base noise
dog.makeNoise();    // Woof woof- this was overridden
dog.legs;           // 4! This was inherited

Dog از Animal ارث‌بری کرده و می‌تواند از پراپرتی پیش‌فرض legs استفاده کند. اما همچنین می‌تواند پیاده‌سازی خود را انجام دهد.

قدرت واقعی پلی‌مورفیسم، به اشتراک‌ گذاشتن رفتارها و اجازه override کردن‌های سفارشی است.

جمع‌بندی

امیدواریم توانسته باشیم چهار ستون اصلی برنامه‌نویسی شیءگرا را برای شما توضیح داده باشیم و متوجه شده باشید که چگونه منجر به کد تمیزتر و قوی‌تر می‌شود.

ایمان مدائنی

نویسنده 1299 مقاله در برنامه نویسان

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

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید