اصول برنامه نویسی یا بهتره بگم Principle های برنامه نویسی زیر بنا و بخش مهمی از برنامه نویسی بشمار میان و رعایت این اصول باعث میشه تا یک قدم به Senior شدن نزدیک تر بشیم و به ما یاد میده تا کدی بنویسیم که تمیز، خوانا و قابل توسعه باشه.
فقط مباحث فنی برای تبدیل شدن به یک برنامه نویس متمایز کافی نیست و نحوه فکر کردن، حل مسئله و توانایی نوشتن کدی تمیز، خوانا و قابل توسعه هست که یک برنامه نویس را از سایر متمایز میکنه.
تو این مقاله قصد نداریم تا سیر تا پیاز تک تک این Principle ها رو آموزش بدیم، بلکه قصد داریم تا در یک مقاله 20 تا از مهم ترین اصول و Principle های برنامه نویسی را با توضیحی شفاف و مثالی ساده برای شما لیست کنیم تا همیشه در دسترستان باشه و به خوبی تک تک این اصول را به شما یاد بده. این مفاهیم را ما در دوره آموزش فرانت اند که یک دوره کاملا متفاوت و مدرن هست هم بطور کامل آموزش دادیم.
Abstraction
قانون و مفهوم Abstraction در برنامه نویسی یک مفهوم بنیادی از Computer Science و Software Engineering برای کاهش پیچیدگی یک سیستم و در جهت راحت تر کردن کار با اون سیستم هستش. به عنوان مثال یک ماشین را تصور کنید، شما برای استفاده از یک ماشین صرفا باید بلد باشید تا با پدال های گاز، کلاچ، ترمز و دنده کار کنید و نیازی به این ندارید که بدونید سیستم سوخت رسانی ماشین یا گیربکس یا موتور ماشین چطور کار میکنه و صرفا با بلد بودن کار با یکسری ابزار جزئی میتونید به راحتی با اون سیستم پیچیده که در این مثال یک ماشین هست کار کنید.
در برنامه نویسی هم کار به همین شکل هست و ما خیلی وقت ها با ابزارهایی کار میکنیم که اون ابزار در پشت صحنه خودش ممکنه تا چند لایه عمیق پیچیدگی و جزئیات داشته باشه. نیازی نیست تا ما بصورت مستقیم با cpu کار کنیم یا 0 و 1 های قابل درک سخت افزار و ماشین را بنویسیم و صرفا استفاده از زبانی مانند جاوااسکریپت میتونیم همین کار را انجام بدیم و چیزی که مدنظرمان هست را کدنویسی کنیم!
DRY
قانون و مفهوم DRY در برنامه نویسی مخفف (don’t repeat yourself) و به معنای خودت را تکرار نکن هستش و همانطور که از اسمش پیداست به ما میگه که تحت هیچ شرایطی نباید یک کد 2 جا تکرار بشه ( یعنی کپی و پیستش نکن و بجاش تبدیلش کن به یک ماژول یا کامپوننتی که میتونی هرجایی که خواستی ازش استفاده کنی )
هر تکه ای از کد باید یک تعریف واحد، بدون ابهام و مشخصی در سیستم شما داشته باشه و اگر قصد داریم تا یک تکه کد را در جاهای مختلف استفاده کنیم، بهتره که این تکه کد از یک رفرنس و منبع واحد بیاد.
این Principle یک هدف ساده و واضح داره و اونهم اینه که کدهایی تمیزتر، خواناتر و قابل نگهداری بنویسیم. فرض کنید که تکه کدی داریم که باید در 3 جای مختلف استفاده بشه و ما این تکه کد را بدون اینکه تبدیلش کنیم به یک ماژول یا کامپوننت، میاییم بصورت دستی توی هر 3 جا مینویسیمش و بعد از مدتی تصمیم میگیریم تا تغییری ساده در یکی از جاهایی که از این کد استفاده کردیم بدیم. حالا اگه یادمون باشه که این کد 2 جای دیگه هم نوشته شده، میریم و در تک تک اون جاهای دیگه هم اون تغییر را اعمال میکنیم، اما اگر یادمون نباشه صرفا کد را در یکجا تغییر میدیم و 2 جای باقی مانده همچنان در ورژن قدیمی خودشون کار میکنند.
با جلوگیری از تکرار کدها و تبدیلشون به ماژول ها یا کامپوننت های قابل استفاده مجدد، هم در زمان صرفه جویی میکنیم ( یکبار اون کد را ویرایش میکنیم و این ویرایش ما در جاهای دیگه ای که این کد استفاده شده هم اعمال میشه ) و هم اینکه مطمئن میشیم که مبادا تغییری از قلم بیوفته.
KISS
قانون و مفهوم KISS در برنامه نویسی مخفف (keep it simple, stupid) و به معنای "احمق جان ساده نگهش دار" هستش 😬. این مفهوم و جلمه توسط یک مهندس نیروی هوایی به نام Kelly Johnson گفته شد و مخاطب این جمله آقای Johnson سایر مهندسان نیروی هوایی که درحال کار برروی طراحی یک جت جنگنده بودن بود و به مهندسان تیم خودش گفت که مکانیک و سیستم طراحی این جت ها باید طوری باشه که در شرایط جنگی و با امکانات پایه ای که در میدان نبر وجود داره حتی بدست یک مهندس تازه کار بشه تعمیرشون کرد.
ایده این مفهوم هم اینه که یک سیستم باید در عین حالی که قدرتمند، مدرن و کارامد هستش، ساده و شفاف هم کار کنه و طوری ساخته شده باشه که بشه به راحتی و در زمانی مناسب اونرو توسعه داد.
YAGNI
YAGNI مخفف "You Aren't Gonna Need It." به معنای "قرار نیست نیازت بشه" و جزوی از Agile Software Development هست و خیلی خلاصه به ما میگه وقتمون را برای توسعه ویژگی ها و کدی که قرار نیست نیازمون بشه حروم نکنیم.
گاهی اوقات ما یک تکه کد ساده نیاز داریم تا در زمان حال کار مارو راه بندازه و اینطور مواقع باید صرفا برروی نوشتن اون کد تمرکز کنیم. درسته که باید همیشه کدی تمیز، خوانا و قابل توسعه بنویسیم و طوری بنویسیم که در آینده هم بشه به راحتی گسترشش داد و ویژگی های جدیدی بهش اضافه کرد، اما نباید هم فراموش کنیم که خیلی وقت ها هم قرار نیست اون ویژگی خیلی گسترده بشه و در زمان حال هم اصلا اهمیتی نداره این موضوع.
در این مفهوم ما باید متمرکز بمونیم روی پیاده سازی اون کد و ویژگی که مسئولش هستیم و اون کد یا ویژگی را در زمان خواسته شده پیاده سازی کنیم و سعی نکنیم یک غول به تمام معنا از اون ویژگی در بیاریم و به یک سال آینده ای که شاید اصلا پیش نیاد فکر کنیم.
LoD
مفهوم LoD در برنامه نویسی یا همان the Law of Demeter معمولا در OOP مورد استفاده قرار میگیره و به زبانی عامیانه به ما میگه که "با غریبه ها نباید صحبت کنیم" و به زبان تخصصی تر به این معناست که یک آبجکت باید فقط با دوستان نزدیک خودش ارتباط برقرار کنه. مزیت LoD هم بیشتر کردن قابلیت نگهداری سیستم یا کد با دوری کردن از ارتباط مستقیم آبجکت های نامرتبط با یکدیگر هستش.
در زمان کار با یک آبجکت اگر یکی از سناریو های زیر پیش بیاد ما در حال نقض LoD هستیم:
- وقتی متدهارو بصورت زنجیره ای ( قطاری شکل ) پشت سر هم call میشن.
- وقتی آبجکت ما یک instance از class فعلی هست.
- وقتی آبجکت از طریق parameters به متد پاس داده شده
- وقتی که آبجکت بصورت Global در دسترسه
برای یک مثال بهتر، بیایید زمانی را تصور کنیم که مشتری میخواد پولی را به حساب بانکی واریز کنه. در اینجا ما احتمالا 3 تا class با نام های Bank, Customer و Wallet داشته باشیم.
در اینجا اگر آبجکت Bank بطور مستقیم با غریبه ما یعنی Wallet صحبت کنه اصل LoD نقض میشه:
اما اگر بجای ارتباط مستقیم با Wallet از طریق نزدیک ترین آشنا و رفیقش یعنی Customer اقدام کنیم، هم به راحتی در هر زمانی میتونیم منطق و کد درون class هارو تغییر بدیم و هم اینکه آبجکته Bank ما نگران اتفاقاتی که داخل Customer میوفته باشه:
SoC
قانون SoC در برنامه نویسی مخفف "Separation of Concerns" هست و به معنای تفکیک کد به بخش های کوچک تر هستش. در تعریف این اصل عبارت concern به چشم میخوره که داره به وظیفه اشاره میکنه و معنای کامل تر جمله یعنی Separation of Concerns به ما میگه که یک تکه کد بزرگ را طبق وظایف کوچکتری که ازش تشکیل شده جداسازی و به بخش های کوچک تر تقسیم کن.
به عنوان مثال یک Todo App ساده را تصور کنید، در این مثال ما با تقسیم این اپلیکیشن به بخش ها و کامپوننت های کوچک تری مثل TodoList, SingleTodo, NewTodo میتونیم اصل SoC را پیاده کنیم و اپ Todo خودمون را به بخش های مختلف و کوچک تری که هر بخش داره طبق وظیفه خودش کار میکنه تقسیم کنیم.
SOLID
مفهوم و قانون SOLID در برنامه نویسی از 5 مفهوم ساخته شده و هدفش هم اینه که به توسعه دهنده ها کمک کنه تا کدی منعطف تر، تمیز تر و با قابلیت نگهداری و توسعه بیشتر بنویسند.این 5 مفهوم هم عبارت اند از موارد زیر:
1. Single Responsibility Principle (SRP)
این مفهوم به ما میگه که یک کلاس یا حتی یک فانکشن فقط و فقط باید یک کار مشخص را انجام بده و فقط و فقط هم یک دلیل برای تغییر داشته باشه. هدف از این کار چیه ؟ هدف اینه که خوانایی کد بیشتر بشه و خیلی شفاف متوجه کاری که اون فانکشن یا کلاس انجام میده بشیم و از همه مهم تر هم اینکه در زمان حل کردن باگ ها زودتر متوجه این قضیه بشیم که آیا این فانکشن یا کلاس ما تاثیری در بوجود آمدن این باگ داره یا نه.
2. Open/Closed Principle (OCP)
این مفهوم به ما میگه که یک کلاس یا حتی یک فانکشن همیشه باید قابل توسعه باشه و ما بتونیم اونرو گسترش بدیم و ویژگی ها و امکانات جدید بهش اضافه کنیم. اما نباید قابل اصلاح سورس کد اولیه باشه!
یک فانکشن یا کلاس یکبار ایجاد و تست میشه و وقتی ایجاد شد و تست شد سورس کد اولیه و اصل اون کد هیچوقت نباید تغییر کنه اما باید قابلیت توسعه و افزوده شدن ویژگی های جدید را طبق همون سورس اولیه داشته باشه.
این مفهوم اطمینان حاصل میکنه تا به باگ های حاصل از دستکاری کدهایی که قبل تر نوشتیم بر نخوریم.
3. Liskov Substitution Principle (LSP)
این مفهوم به ما میگه آبجکت های یک superclass باید با آبجکت های subclass های خودش بدون ایجاد ارور و تداخل در کار اصلی کد قابل جایگزینی باشه. یعنی چی ؟ بیایید یک مثال ساده را باهم ببینیم:
ما یک class با نام Bird
داریم که به هر پرنده ای قابلیت پرواز کردن را میده:
class Bird {
fly() {
console.log("This bird can fly!");
}
}
در ادامه هم یک subclass با نام Penguin ( پنگوئن ) داریم که داره از کلاس Bird ما نشات میگیره:
class Penguin extends Bird {
fly() {
throw new Error("Cannot fly!")
}
}
این subcluss ما که پنگوئن باشه درسته که یک پرندست اما این پرنده ما قابلیت پرواز رو نداره و این اروری که داره برمیگردونه داره متد fly
که در کلاس اصلی ما یعنی کلاس Bird
بود را بازنویسی میکنه و در اینجا بخاطر این بازنویسی و از بین رفتن عملکرد اصلی برنامه، اصل LSP هم داره نقض میشه.
در مثال فوق برای حل این مشکل و رعایت اصل LSP باید کد بالا را به شکل زیر در بیاریم:
class Bird {
}
class FlyingBird extends Bird {
fly() {
console.log("This bird can fly!");
}
}
class NonFlyingBird extends Bird {
}
class Eagle extends FlyingBird {
}
class Penguin extends NonFlyingBird {
}
function makeBirdFly(bird) {
if (bird instanceof FlyingBird) {
bird.fly();
} else {
console.log("This bird can't fly!");
}
}
const myEagle = new Eagle();
const myPenguin = new Penguin();
makeBirdFly(myEagle); // Outputs: "This bird can fly!"
makeBirdFly(myPenguin); // Outputs: "This bird can't fly!"
4. Interface Segregation Principle (ISP)
این مفهوم به ما میگه که interface های پیچیده و در ابعاد بزرگ که وظایف زیادی دارند و کارهای زیادی را انجام میدن نسازید ( اصطلاحا بهشون fat interfaces هم میگیم ).
معمولا وقتی یک interface بیش از حد بزرگ و پیچیدست، کلاسی که داره اونهارو پیاده سازی میکنه ممکنه مجبور باشه متدهایی را اضافه کنه که هیچوقت شاید ازشون استفاده نشه.
بیایید برای این مفهوم به یک مثال ساده از دنیای واقعی نگاه کنیم، فرض کنید که شما یک ریموت کنترلی دارید که هم توانایی کار با کولر، چراغ ها، درب پارکینگ و تلوزیون را داره و این در حالیه که شما صرفا یک تلوزیون دارید! یک تلوزیون با ریموت کنترلی که کلی دکمه و آپشن بدون استفاده داره و جز سر در گمی موقع کار کردن باهاش مزیت دیگه ای را در اختیار شما نمیزاره!
5. Dependency Inversion Principle (DIP)
برای توضیح دادن این مفهوم ترجیح میدم که اصطلاحات انگلیسی را ترجمه نکنم و بجاش در ادامه اونهارو با یک مثال ملموس توضیح بدم ( در کل بهتره که اصطلاحات برنامه نویسی را بدون برگرداندن یاد بگیریم )
هدف از این مفهوم کاهش وابستگی های بین ماژول های مختلفه تا اگر در یکی از بخش ها تغییری بوجود اومد اپ ما بطور مستقیم تاثیر نپذیره این از تغییر و این قانون به ما میگه که ماژول های High-Level نباید به ماژول های Low-Level وابسته باشن و بلکه هر دوی این ماژول ها باید به Abstractions وابسته باشن ( 🤨 ) و Abstractions هم نباید به جزئیات وابسته باشه و بلکه جزئیات باید به Abstractions وابسته باشه ( چی شد 😑 )
خیلی سخت شد، بریم یکم شفاف تر کنیم این موارد را و این قانون را با یک مثال ملموس تر یاد بگیریم:
بیایید یک تلوزیون را به عنوان یک ماژول Low-level در نظر بگیریم و ریموت کنترل این تلوزیون را هم به عنوان یک ماژول High-level در نظر بگیریم.
در این مثال اگر خبری از Principle ما یعنی Dependency Inversion نباشه ریموت کنترل این تلوزیون فقط و فقط برای همین مدل تلوزیون که ما در اختیار داریم طراحی میشه و این ریموت کنترل قراره وابسته به این مدل خاص از تلوزیون باشه و اگر ما یک تلوزیون جدید از یک برند دیگه یا حتی از همان برند قبلی خودمان اما مدلی جدید تر بخریم، دیگه اون ریموت کنترلی قبلی روی این تلوزیون کار نخواهد کرد.
اما اگر Principle در این مثال رعایت شده باشه، این ریموت کنترل ما بجای اینکه وابسته به یک مدل خاصی از تلوزیون باشه، وابسته به board یا به برند یا یک چیز کلی تر میشه ( همون Abstraction ). با وابسته نبودن یک ماژول High-level مثل ریموت کنترل به یک ماژول Low-level مثل تلوزیون و بجاش وابسته بودن هردوی اینها به یک Abstraction که تشبیهش کردیم به تکنولوژی board اون تلوزیون این Principle رعایت خواهد شد و با تعویض تلوزیون به مشکل نخواهیم خورد.
حالا بریم یک مثال از دنیای برنامه نویسی برای این Principle ببینیم:
قصد داریم تا ماژولی بنویسیم که داده های مدنظر مارو در دیتابیس ذخیره میکنه، بدون رعایت این Principle کد ما به شکل زیر خواهد بود:
// High-level module directly depending on a low-level module
class DataStore {
constructor() {
this.db = new MySQLDatabase();
}
save(data) {
this.db.insert(data);
}
}
class MySQLDatabase {
insert(data) {
console.log(`Inserting data into MySQL database: ${data}`);
}
}
مشکل کد بالا چیه ؟ مشکلش اینه که DataStore
بطور مستقیم به MySQLDatabase
وابستست و اگر ما یکروزی بخواهیم از یک دیتابیس جدید استفاده کنیم مجبور میشیم تا کد DataStore
را مجدد بنویسیم!
اما همین کد بالا با رعایت این Principle چطور خواهد بود ؟ بریم باهم ببینیم:
// Abstraction
class Database {
insert(data) {
throw new Error("This method should be overridden by subclasses");
}
}
// High-level module depending on abstraction
class DataStore {
constructor(database) {
this.db = database; // Dependency is injected
}
save(data) {
this.db.insert(data);
}
}
// Low-level modules implementing the abstraction
class MySQLDatabase extends Database {
insert(data) {
console.log(`Inserting data into MySQL database: ${data}`);
}
}
class MongoDB extends Database {
insert(data) {
console.log(`Inserting data into MongoDB database: ${data}`);
}
}
در کد بالا هردوی ماژول های High-level و Low-level بجای وابستگی به همدیگه به Abstraction خودشون وابسته هستند.
GRASP
قانون و مفهوم GRASP در برنامه نویسی مخفف General Responsibility Assignment Software Patterns هست. این مفهوم مجموعه ای از 9 قاعده مهم هست که به ما کمک میکنه تا مسئولیت یک کلاس را در object-oriented design تعیین کنیم و هدفش هم بهبود خوانایی کد، آسان تر شدن نگهداری اون کد و همچنین قابل استفاده مجدد ساختن اون کد هستش.
1. Information Expert
مسئولیتی را به یک کلاس بدید که بهترین و بیشترین اطلاعات را برای انجام اون وظیفه را در اختیار داره. وقتی که یک کلاس دارای بیشترین اطلاعات و آزادی عمل برای انجام یک کار بخصوص هست میتونه به بهترین شکل ممکن Task مدنظر را انجام بده.
2. Creator
Creator pattern به ما کمک میکنه تا تصمیم بگیریم کدام class مسئولیت ایجاد instance جدید از یک کلاس دیگه را به عهده داشته باشه و برای گرفتن این تصمیم باید یکسری موارد را برسی کنیم که عبارت اند از:
- Contains: اگر کلاس B کلاس A را به عنوان بخشی از ساختار خودش داره، پس کلاس B باید مسئول ساخت یک Instance جدید از A باشه. به عنوان مثال به کلاسی با نام
Library
فکر کنید ممکنه شاملBook
باشه، پس کلاسLibrary
باید مسئول ساخت یکBook
جدید باشه. - Aggregates: کلاس B اگر Aggregate میکنه کلاس A رو به این معنیه که B یک مجموعه یا یک container پیچیده ای هست که شامل چندین آبجکت A هستش.برای درک بهتر یک
ShoppingCart
را در نظر بگیرید کهItems
را Aggregate میکنه و مسئول مدیریت این Item ها هستش، اما همچنان این Item ها میتونن خارج ازShoppingCart
هم باشن. - Records Instances of A: اگر کلاس B رکورد های Instance کلاس A را نگه میداره ( مثل یک دیتابیس برای آبجکت ها ) پس B باید A را بسازه. به عنوان مثال یک
CourseManager
ممکنه چندین course را دنبال و مدیریت کنه. پس همینCourseManager
هم باید بتونه یکCourse
جدید بسازه. - Closely Uses A: اگر کلاس B بطور مکرر و در راه های متفاوت از کلاس A استفاده میکنه، پس برای B منطقی تر و کاربردی تره که A را بسازه.
- Initialization Data: اگر کلاس B دیتای مورد نیاز A جهت ایجاد شدن را نگهداری میکنه، پس B باید A را ایجاد کنه. به عنوان مثال
CarFactory
همه اطلاعات لازم جهت ایجادCar
را داره، پسCarFactory
بایدCar
جدید بسازه.
اگر زمانی که میخواهیم تعیین کنیم که کدام کلاس مسئول ایجاد Instance جدید از یک کلاس دیگه باشه موارد فوق را در نظر بگیریم، باعث میشه کدی بنویسیم که کمترین وابستگی را داره و در شفاف ترین ساختار ممکن داره کار میکنه.
3. Controller
Controller pattern هدفش اینه که چه کسی قراره مسئول مدیریت System Event هایی مثل تعاملات و درخواست های کاربر باشه. در معماری های MVC دیدیم که Controller مسئول تعاملات کاربر ( View ) و منطق سیستم یا سرویس داده های سیستم ( Model ) هستش.
4. Low Coupling
مسئولیت کلاس هارو طوری تعیین کنید تا کمترین وابستگی ممکن را داشته باشن تا اگر یکی از کلاس های ما دسخوش تغییرات شد، سایر کلاس ها کمترین تاثیر را بگیرن.
5. High Cohesion
مسئولیت هارو طوری واگذار کنید که نظم و انسجام در بالاترین حد خودش باقی بمونه و این بدان معناست که Task ها و داده های مشابه را در یک کلاس باید نگه داریم.
6. Polymorphism
استفاده از polymorphic operations زمانیکه جایگزین های مرتبطی برای یک رفتار وجود داره. حالا polymorphic یا بهتره بگم Polymorphism چیه ؟ Polymorphism یک مبحث برنامه نویسیه که به آبجکت ها اجازه میده به عنوان یک Instance از کلاس والدشون در نظر گرفته بشن ( بجای کلاس واقعی خودشون )
برای شفاف تر شدن مورد 6 یعنی Polymorphism بیایید یک مثال ملموس تر راهم باهم ببینیم:
فرض کنید میخواهیم کلاسی از حیوانات مختلف بسازیم ک هر حیوان یک صدای مخصوص به خودش را داره و در عین حال هم یک تایپ صدای ناشناخته ( برای ما انسان ها ) دارن که عمومیه و برای ما انسان ها تحت هر شرایطی و فارق از اینکه اون حیوان سگ هست یا گربه، صدای حیوان در نظر گرفته میشه:
// Define a superclass called Animal
class Animal {
makeSound() {
console.log("Some generic sound");
}
}
// Define a subclass called Dog that extends Animal
class Dog extends Animal {
makeSound() {
console.log("Bark");
}
}
// Define another subclass called Cat that extends Animal
class Cat extends Animal {
makeSound() {
console.log("Meow");
}
}
// Function to make any animal sound
function makeAnimalSound(animal) {
animal.makeSound(); // This will invoke the makeSound method appropriate to the object's class
}
// Create instances of Dog and Cat
const myDog = new Dog();
const myCat = new Cat();
// Use polymorphism to treat dogs and cats as animals
makeAnimalSound(myDog); // Outputs: Bark
makeAnimalSound(myCat); // Outputs: Meow
در مثال بالا، کلاس Animal
به عنوان یک کلاس پایه با متد makeSound
رفتار میکنه و کلاس های Dog
و Cat
هم makeSound
را با صدای مخصوص به خود اون حیوان بازنویسی میکنند. فانکشن makeAnimalSound
هم یک پارامتر Animal
به عنوان ورودی میگیره و در نهایت متد makeSound
اون Animal
را اجرا میکنه و به لطف Polymorphism متد صحیح طبق تایپ اصلی آبجکت ما ( Dog
یا Cat
) بودن اجرا میشه.
7. Indirection
از یک کلاس معمولی برای واسطه شدن بین 2 کلاس و در جهت کاهش وابستگی بین اون 2 کلاس استفاده کنید تا دیزاین سیستم منعطف نگه داشته بشه و وابستگی الکی و بطور کل وابستگی که متعلق به یک کلاس نیست، برای اون کلاس پیش نیاد.
8. Protected Variations
از ایجاد تغییر توسط یک Element برروی یک Element های دیگه ای مثل (objects, systems, subsystems) جلوگیری کنید. با انجام این کار اطمینان حاصل میکنیم که ایجاد تغییر در یک بخش از سیستم نیازمنده کمترین تغییر برروی سایر بخش ها هست و همچنین side effect بدی را برروی بخش دیگری نخواهد گذاشت.
در مثال زیر ما کلاسی را داریم که روش پرداخت کاربر را تشخیص و در نهایت طبق روش پرداخت کاری را انجام میده:
class PaymentProcessor {
processPayment(amount, paymentType) {
if (paymentType === 'CreditCard') {
// Process credit card payment
console.log(`Processing ${amount} via Credit Card`);
} else if (paymentType === 'PayPal') {
// Process PayPal payment
console.log(`Processing ${amount} via PayPal`);
}
// Additional payment methods would require more conditions here
}
}
// Usage
const paymentProcessor = new PaymentProcessor();
paymentProcessor.processPayment(100, 'CreditCard');
مشکل کد بالا اینه که اگر بخواهیم یک روش پرداخت جدید ایجاد کنیم، باید PaymentProcessor
را هم ویرایش کنیم و این ممکنه برای ما باگ های متعددی را ایجاد کنه. اما با اصلاح کد بالا به شکل زیر میتونیم این مشکل را حل کنیم:
class PaymentMethod {
processPayment(amount) {
throw new Error("Method not implemented.");
}
}
class CreditCardPayment extends PaymentMethod {
processPayment(amount) {
console.log(`Processing ${amount} via Credit Card`);
}
}
class PayPalPayment extends PaymentMethod {
processPayment(amount) {
console.log(`Processing ${amount} via PayPal`);
}
}
class PaymentProcessor {
constructor(paymentMethod) {
this.paymentMethod = paymentMethod;
}
processPayment(amount) {
this.paymentMethod.processPayment(amount);
}
}
// Usage
const creditCardPayment = new CreditCardPayment();
const paymentProcessor = new PaymentProcessor(creditCardPayment);
paymentProcessor.processPayment(100);
const paypalPayment = new PayPalPayment();
paymentProcessor.paymentMethod = paypalPayment;
paymentProcessor.processPayment(200);
جمع بندی:
تمام سعی خودم را کردم تا چندتا از مهم ترین قانون ها و Principle های برنامه نویسی را اینجا به زبانی ساده براتون توضیح بدم. برخی از این مفاهیمی که باهم خوندیم کمی پیچیده بودن و اصطلاحات انگلیسی داشتند که هم نمیشد به پارسی برگرداند و هم ترجمشون کار اشتباهی بودش و بجاش سعی کردم ابهامات را با مثال هایی از دنیای واقعی و نمونه کدهایی ملموس براتون شفاف کنم.
به احتمال خیلی زیاد بدون اینکه متوجه خیلی از این اصول های برنامه نویسی باشید، از خیلیاشون تا به الان استفاده کردید و حالا با خوندن این مقاله یک اسمی هم براشون پیدا کردید و چیزی که از قبل میدونستید براتون مستحکم تر شده 😃