اصل Single Responsibility به زبان ساده!
محمد کلاته
1405/05/04
تاریخ انتشار : 1402/05/20
تاریخ آپدیت : 1405/05/03
فرض کن وارد یک فروشگاه میشی و میبینی فقط یک نفر اونجا کار میکنه. هم باید جواب تلفن رو بده، هم سفارشها رو ثبت کنه، هم حسابداری انجام بده، هم اجناس رو از انبار بیاره و هم بستهها رو برای مشتری ارسال کنه.
تا وقتی فروشگاه خلوت باشه، شاید این روش جواب بده. اما همین که تعداد مشتریها زیاد بشه یا یکی از کارها تغییر کنه، همهچیز بههم میریزه.
مثلاً اگه روش حسابداری فروشگاه عوض بشه، باز همون فردی درگیر کار میشه که باید سفارشها رو هم ثبت کنه. اگه سیستم ارسال کالا تغییر کنه، باز هم کار بقیه بخشها مختل میشه.
در برنامهنویسی هم دقیقاً چنین اتفاقی میافته. وقتی یک کلاس را مسئول چند کار متفاوت میکنیم، تغییر دادن و نگهداری آن سختتر میشه. اصل Single Responsibility Principle یا بهاختصار SRP برای جلوگیری از همین مشکل بهوجود اومده.
اصل Single Responsibility یعنی چی؟
تعریف ساده این اصل اینه:
هر کلاس باید یک مسئولیت مشخص داشته باشه و فقط به یک دلیل اصلی تغییر کنه.
یعنی بهتره یک کلاس رو مسئول چند بخش نامرتبط برنامه نکنیم.
مثلاً کلاسی که مسئول محاسبه مبلغ سفارشهاست، بهتره همزمان مسئول ذخیره اطلاعات در دیتابیس و ارسال پیامک به مشتری نباشه. اینها سه مسئولیت متفاوت هستند و هرکدوم ممکنه به دلیل متفاوتی تغییر کنن.
منظور از «یک دلیل برای تغییر» هم اینه که همه کدهای داخل یک کلاس، به یک موضوع مشخص مربوط باشن.
مثلاً اگه قوانین محاسبه قیمت تغییر کرد، کلاس محاسباتی تغییر کنه. اگه روش ذخیره اطلاعات عوض شد، کلاس مربوط به دیتابیس تغییر کنه و اگه متن پیامک تغییر کرد، کلاس ارسال پیامک تغییر کنه.
چرا به اصل Single Responsibility نیاز داریم؟
فرض کن یک کلاس به نام OrderService داریم که کارهای زیر را انجام میده:
- مبلغ سفارش را محاسبه میکنه.
- سفارش رو در دیتابیس ذخیره میکنه.
- پیامک تأیید سفارش را برای مشتری میفرسته.
در نگاه اول شاید این کار منطقی به نظر برسه. بالاخره همه این کارها به سفارش مربوطن دیگه!!
اما مشکل از جایی شروع میشه که یکی از این بخشها تغییر کنه.
مثلاً:
- مدیر فروشگاه تصمیم میگیره روش محاسبه تخفیف رو عوض کنه.
- تیم فنی تصمیم میگیره دیتابیس برنامه را تغییر بده.
- بخش فروش میخواد متن پیامک تأیید سفارش رو عوض کنه.
برای انجام هر سه تغییر باید سراغ یک کلاس بریم. یعنی یک کلاس، سه دلیل کاملاً متفاوت برای تغییر داره.
هرچی مسئولیتهای بیشتری داخل یک کلاس جمع کنیم، احتمال اینکه تغییر یک بخش روی بخشهای دیگه اثر بذاره بیشتر میشه.
یک اشتباه رایج درباره SRP
بعضیها فکر میکنند اصل Single Responsibility میگه:
هر کلاس باید فقط یک متد داشته باشه!!
این برداشت درست نیست.
یک کلاس میتونه چند متد مختلف داشته باشه، به شرطی که همه اون متدها در خدمت یک مسئولیت مشخص باشن.
مثلاً یک کلاس محاسبه قیمت میتونه متدهای زیر رو داشته باشه:
- محاسبه قیمت اولیه
- محاسبه تخفیف
- محاسبه مالیات
- محاسبه مبلغ نهایی
این کلاس چند متد داره، اما همه متدها مربوط به یک مسئولیتن: محاسبه قیمت سفارش.
پس هدف SRP این نیست که برنامه رو به صدها کلاس بسیار کوچک و بیمعنی تقسیم کنیم. هدف اینه که هر کلاس تمرکز و مسئولیت مشخصی داشته باشه.
مثال نقض اصل Single Responsibility
در مثال زیر، کلاس OrderService سه مسئولیت متفاوت داره:
public class Order
```
{
public decimal Price { get; set; }
}
public class OrderService
{
public decimal CalculateTotal(List orders)
{
decimal total = 0;
```
foreach (var order in orders)
{
total += order.Price;
}
return total;
}
public void SaveOrder(Order order)
{
// ذخیره سفارش در دیتابیس
}
public void SendConfirmationSMS(Order order)
{
// ارسال پیامک تأیید سفارش
}
```
}
```
متد CalculateTotal مبلغ سفارشها را محاسبه میکنه.
متد SaveOrder مسئول ذخیره اطلاعات در دیتابیسه.
متد SendConfirmationSMS هم پیامک تأیید را برای مشتری میفرسته.
هر سه تا کار مربوط به سفارشن، اما مسئولیت یکسانی ندارن.
تغییر قوانین محاسبه قیمت نباید روی کد ذخیره اطلاعات اثر بذاره. تغییر دیتابیس هم نباید ما رو مجبور کنه کد ارسال پیامک رو دوباره بررسی کنیم.
رعایت اصل Single Responsibility
برای رعایت SRP میتونیم هر مسئولیت رو داخل کلاس مخصوص خودش قرار بدیم:
public class OrderCalculator
```
{
public decimal CalculateTotal(List orders)
{
decimal total = 0;
```
foreach (var order in orders)
{
total += order.Price;
}
return total;
}
```
}
```
کلاس OrderCalculator فقط مسئول محاسبات مربوط به سفارشهاست.
public class OrderRepository
```
{
public void Save(Order order)
{
// ذخیره سفارش در دیتابیس
}
}
```
کلاس OrderRepository فقط مسئول ذخیره و بازیابی اطلاعات سفارشه.
public class OrderSMSService
```
{
public void SendConfirmation(Order order)
{
// ارسال پیامک تأیید سفارش
}
}
```
کلاس OrderSMSService هم فقط ارسال پیامکهای مربوط به سفارش رو انجام میده.
حالا اگه روش ذخیره اطلاعات تغییر کنه، فقط کلاس OrderRepository رو تغییر میدیم.
اگه متن یا روش ارسال پیامک عوض بشه، فقط سراغ OrderSMSService میریم.
اگه قوانین محاسبه قیمت تغییر کنه، کلاس OrderCalculator تغییر میکنه.
به این ترتیب، هر کلاس مسئولیت مشخص خودش رو داره و تغییرات بخشهای مختلف برنامه کمتر با هم قاطی میشن.
مزایای رعایت اصل Single Responsibility
وقتی هر کلاس مسئولیت مشخصی داشته باشه، معمولاً چندتا اتفاق خوب میافته.
۱. کد راحتتر فهمیده میشه
وقتی اسم کلاسی OrderCalculator باشه، تقریباً مشخصه که باید دنبال چه کدی داخلش بگردیم.
اما کلاسی با نام کلی OrderManager یا OrderService ممکنه هر کاری انجام بده؛ از محاسبه قیمت گرفته تا ارسال پیامک و کار با دیتابیس.
۲. تغییر دادن برنامه سادهتر میشه
وقتی مسئولیتها از هم جدا باشن، برای تغییر یک بخش لازم نیست چندتا قسمت نامرتبط رو دوباره بررسی کنیم.
البته SRP تضمین نمیکنه که هیچ تغییری روی بخشهای دیگه اثر نذاره، اما احتمال درگیر شدن کدهای نامرتبط رو خیلی کمتر میکنه.
۳. تست کردن کلاسها راحتتر میشه
طبیعتاً تست کردن کلاسی که فقط قیمت رو محاسبه میکنه، سادهتر از تست کردن کلاسیه که هم محاسبه انجام میده، هم به دیتابیس وصل میشه و هم پیامک میفرسته.
هرچی مسئولیت یک کلاس مشخصتر باشه، نوشتن تست برای اون هم سادهتره.
۴. نگهداری کد راحتتر میشه
در پروژههای واقعی، بیشتر وقت برنامهنویس صرف نوشتن کد جدید نمیشه؛ بخش زیادی از زمان صرف پیدا کردن، اصلاح کردن و تغییر دادن کدهای قبلی میشه.
کلاسهایی که مسئولیت مشخصی دارن، در آینده راحتتر پیدا، بررسی و اصلاح میشن.
جمعبندی
اصل Single Responsibility میگه هر کلاس باید یک مسئولیت مشخص و یک دلیل اصلی برای تغییر داشته باشه.
این اصل نمیگه هر کلاس فقط باید یک متد داشته باشه یا حتماً خیلی کوچک باشه. یک کلاس میتونه چند متد داشته باشه، به شرطی که همه اون متدها به یک مسئولیت مربوط باشند.
هر وقت دیدی یک کلاس هم محاسبه انجام میده، هم اطلاعات ذخیره میکنه، هم پیام میفرسته و هم چند کار نامرتبط دیگه انجام میده، احتمالاً وقتشه مسئولیتهای اون کلاس رو از هم جدا کنی.
هدف نهایی SRP اینه که کدهایی بنویسیم که فهمیدن، تغییر دادن و نگهداریشون راحتتر باشه.
```