SoC (Separation of Concerns) مخفف اصلی است که بر اساس آن یک سیستم نرمافزاری به حوزههای مسئولیت ایزوله تقسیم میشود. به گفته مارتین فاولر، تقسیم مسئولیت عنصر کلیدی کد قابل نگهداری است. اصل SoC به توسعهدهندگان اجازه میدهد یک لایه از برنامه را بدون تأثیر بر بقیه تغییر دهند، که این امر به ویژه در توسعه تیمی موبایل مهم است.
نکات اصلی
SoC مخفف Separation of Concerns به معنای «تقسیم مسئولیت» یا «جداسازی حوزههای علاقه» است. در زمینه برنامهنویسی، اصطلاح concern به هر قابلیت جداشدنی اشاره دارد: نمایش رابط کاربری، پردازش کلیکها، اعتبارسنجی دادهها، ارتباط شبکهای یا کار با پایگاه داده. اصل SoC گروهبندی کد را حول این حوزهها به گونهای تجویز میکند که تغییرات در یکی بر دیگری تأثیر نگذارد.
مخفف SoC به طور گسترده در ادبیات فنی، بحثهای معماری و مستندات فریمورکها استفاده میشود. برای مثال، در مستندات Android Architecture Components بارها به SoC به عنوان انگیزهای برای جداسازی ViewModel و View اشاره شده است. در جامعه iOS این اصطلاح هنگام بحث درباره مشکل Massive View Controller — نتیجه مستقیم فقدان SoC — استفاده میشود.
درک این نکته مهم است که SoC یک اقدام یکباره نیست، بلکه یک فرآیند مداوم است. با رشد برنامه، حوزههای مسئولیت جدیدی ظاهر میشوند و معماری نیاز به بازبینی دارد. یک پایگاه کد خوب چندین تکرار تقسیم را طی میکند تا به وضعیت پایداری برسد که در آن هر concern ایزوله و قابل مدیریت است.
Separation of Concerns و مخفف آن SoC یک اصل واحد را نشان میدهند. تفاوت فقط در زمینه استفاده است: نام کامل در اسناد رسمی، مطالب آموزشی و هنگام توضیح مفهوم برای توسعهدهندگان جدید به کار میرود. SoC در بحثهای فنی، بازبینی کد و مستنداتی که اختصار مهم است، راحتتر استفاده میشود.
در محیط حرفهای هر دو اصطلاح قابل جایگزینی هستند. یک توسعهدهنده میگوید «اینجا SoC نقض شده» یا «این Separation of Concerns را نقض میکند» — معنا تغییر نمیکند. با این حال، در آگهیهای شغلی و الزامات معماری بیشتر نام کامل دیده میشود، در حالی که در چتها و بازبینی کد — مخفف. دانستن هر دو گزینه برای ورود راحت به صنعت ضروری است.
سردرگمی اصطلاحی وجود دارد: مخفف SoC همچنین در زمینه سختافزار برای System-on-a-Chip (سیستم روی یک تراشه) استفاده میشود. در توسعه موبایل، زمینه همیشه از محیط مشخص است — اگر بحث به معماری کد مربوط باشد، منظور Separation of Concerns است. در این مقاله SoC در همه جا به اصل تقسیم مسئولیت اشاره دارد.
معماری سه لایه — رایجترین روش پیادهسازی SoC در برنامههای موبایل. کد را به Presentation (UI)، Domain (منطق کسبوکار) و Data (کار با منابع) تقسیم میکند. هر لایه شامل انواع کلاسهای مشخصی است و از طریق رابطها از همسایگان ایزوله میشود. این رویکرد برای پروژههای iOS، Android و Flutter به یک اندازه مؤثر است.
View و ViewModel لایه ارائه را تشکیل میدهند. View مسئول نمایش رابط و انتقال رویدادهای کاربر است. ViewModel وضعیت صفحه را ذخیره و دادههای دریافتی از لایه Domain را به فرمت آماده برای نمایش تبدیل میکند. ViewModel هیچ ارجاعی به Activity، Fragment یا UIViewController ندارد — این امر SoC بین UI و منطق را تضمین میکند.
به عنوان مثال، در Android Jetpack ViewModel از چرخش صفحه جان سالم به در میبرد، در حالی که UI بازسازی میشود. بدون SoC باید وضعیت را در Activity ذخیره میکردیم و مدیریت چرخه حیات را با دادهها مخلوط میکردیم. ViewModel این کار را به صورت ایزوله حل میکند و اجرای خالص اصل تقسیم مسئولیت را نشان میدهد.
Use Cases شامل قوانین کسبوکار مستقل از پلتفرم هستند. این لایه Android SDK، iOS UIKit یا Flutter framework را وارد نمیکند. Use Case دادهها را از Repository دریافت، منطق کسبوکار را روی آنها اعمال و نتیجه را برمیگرداند. به لطف SoC، یک Use Case میتواند در صفحات و پلتفرمهای مختلف استفاده مجدد شود.
مثال کلاسیک — ValidateAndSaveUseCase برای فرم ثبتنام. صحت ایمیل و رمز عبور را بررسی، برای ذخیرهسازی UserRepository را فراخوانی و ValidationResult را برمیگرداند. نه UI و نه پایگاه داده از قوانین اعتبارسنجی خبر ندارند — آنها در یک جا متمرکز شدهاند که تغییر آنها را آسان میکند.
Repository منابع داده را از بقیه برنامه انتزاع میکند. ViewModel نمیداند دادهها از کجا میآیند — REST API، GraphQL، پایگاه داده محلی یا کش. Repository تصمیم میگیرد از کدام منبع استفاده کند و این منطق را پشت یک رابط پنهان میکند. این SoC بین دریافت داده و مصرف آن است.
DataSource تقسیم عمیقتری را فراهم میکند: RemoteDataSource فقط مسئول درخواستهای HTTP است، LocalDataSource — برای کار با Room، CoreData یا SharedPreferences. Repository آنها را ترکیب و استراتژیهای کش را اعمال میکند. هر DataSource را میتوان مستقل جایگزین کرد که هنگام مهاجرت بین سرورها یا پایگاههای داده حیاتی است.
چنین سیستم چند سطحی DataSource SoC را در سطح زیرساخت پیادهسازی میکند: ارتباط شبکه، ذخیرهسازی محلی و کش — concerns جداگانه، هر کدام با منطق و چرخه حیات خود. هنگام تغییر کلاینت HTTP فقط RemoteDataSource تغییر میکند و Repository و لایههای بالاتر دست نخورده باقی میمانند که ارزش عملی تقسیم مسئولیت را تأیید میکند.
MVP (Model-View-Presenter) — یکی از اولین الگوهایی که به صراحت SoC را در توسعه موبایل پیادهسازی میکند. Presenter شامل منطق است و View را از طریق رابط مدیریت میکند. View غیرفعال است — فقط آنچه Presenter میگوید نمایش میدهد. تقسیمبندی تست را ساده میکند: Presenter بدون شبیهساز تست میشود و View آنقدر ساده میماند که چیزی برای شکستن در آن نیست.
MVVM اتصال واکنشی را اضافه کرد: View از طریق Observable یا StateFlow تغییرات ViewModel را دنبال میکند. ViewModel هیچ ارجاعی به View ندارد که خطر نشت حافظه را از بین میبرد و concerns را قویتر جدا میکند. در Android MVVM به لطف Jetpack ViewModel و LiveData به استاندارد تبدیل شد، در iOS — به لطف Combine و RxSwift.
Clean Architecture رابرت مارتین SoC را به تقسیم رادیکال به حلقهها میرساند. حلقه خارجی (فریمورکها و درایورها) به داخلی (موجودیتها) وابسته است، نه برعکس. در عمل، پروژههای موبایل به ندرت هر چهار حلقه را پیادهسازی میکنند — لایههای Domain و Data در اطراف Presentation کافی است. اما خود اصل وابستگی «به داخل» مزایای قابل توجهی در هنگام تغییر فریمورکها میدهد.
// View — فقط نمایش، بدون منطق
final class LoginViewController: UIViewController {
let viewModel: LoginViewModel
func loginTapped() {
viewModel.login(emailField.text, passwordField.text)
}
}
// ViewModel — شامل منطق صفحه است، UIKit را نمیشناسد
final class LoginViewModel {
private let loginUseCase: LoginUseCase
func login(email: String?, password: String?) {
loginUseCase.execute(email, password)
}
}
// Use Case — منطق کسبوکار، مستقل از پلتفرم
final class LoginUseCase {
private let repo: AuthRepository
func execute(email: String?, password: String?) {
guard let e = email, let p = password else { return }
repo.authenticate(e, p)
}
}
مثال سه سطح SoC را نشان میدهد: LoginViewController فقط رویدادها را منتقل میکند، LoginViewModel وضعیت را مدیریت میکند، LoginUseCase حاوی قوانین کسبوکار است. هر کلاس به طور مستقل تست میشود و تغییر فریمورک UI بر Use Case تأثیر نمیگذارد.
Massive View Controller — رایجترین نقض SoC در iOS. کلاسی که UI را مدیریت، درخواستهای شبکه را پردازش، JSON را تجزیه و دادهها را ذخیره میکند، اصل را در همه سطوح نقض میکند. راهحل — خارج کردن هر مسئولیت به یک کامپوننت جداگانه: NetworkingService، JSONParser، CoreDataStack، و فقط مدیریت View را به ViewController واگذار کردن.
در Android مشکل مشابه — God Activity یا God Fragment. یک فعالیت که دادهها را بارگیری، فرمها را اعتبارسنجی، دیالوگها را نشان میدهد و UI را بهروزرسانی میکند. درمان با معرفی ViewModel و Repository که مدیریت وضعیت و دادهها را بر عهده میگیرند. ViewModel همچنین از دادهها در برابر از دست رفتن هنگام چرخش صفحه محافظت میکند.
سومین نقض — مخلوط کردن کد پلتفرم و کسبوکار. مثلاً قرار دادن درخواست HTTP مستقیم در SwiftUI View یا Android Composable. این کار کد را غیرقابل حمل و تست کردن را دشوار میکند. رویکرد صحیح — خارج کردن درخواست به Repository که از طریق Use Case فراخوانی میشود و View فقط نتیجه را دنبال میکند. هر عنصر سیستم وظیفه خود را حل میکند و از مرزهای آن خارج نمیشود.
سوالات متداول
خیر. SoC یک اصل کلیتر از تقسیم سیستم به حوزههای مسئولیت است. SOLID مجموعهای از پنج قانون مشخص برای طراحی شیگرا است. اولین اصل SOLID (Single Responsibility) یک مورد خاص از SoC در سطح یک کلاس است.
از قاعده یک دلیل برای تغییر (Single Responsibility) استفاده کنید. اگر یک کلاس به دلیل تغییر UI، فرمت داده و قوانین کسبوکار تغییر میکند — SoC نقض شده است. ابزارهایی مانند ArchTest (Android) و StrictConcurrency (iOS) به شناسایی خودکار چنین نقضهایی کمک میکنند.
در تئوری لایههای اضافی فراخوانیهای غیرمستقیم اضافه میکنند، اما در عمل تأثیر بر عملکرد برنامه موبایل ناچیز است. کامپایلر بسیاری از فراخوانیها را درونخطی میکند و بهینهسازیهای JIT و AOT سربار را حذف میکنند. قابلیت نگهداری کد بسیار بیشتر از آنچه در انتزاعها از دست میرود، سود میبرد.
با استخراج درخواستهای شبکه از UI به Repository شروع کنید. سپس منطق کسبوکار را به Use Cases منتقل کنید. برای اتصال لایهها از تزریق وابستگی استفاده کنید. تغییرات را به صورت تکراری انجام دهید و کد جدید را با تست بپوشانید — این تضمین میکند که بازسازی، عملکرد موجود را خراب نمیکند.
در نمونههای اولیه میتوان SoC را برای سرعت نقض کرد. اما اگر نمونه اولیه به توسعه محصول تبدیل شود، هزینههای بازسازی ممکن است از مزیت شروع سریع فراتر رود. بهینه — حفظ حداقل تقسیم (UI و دادهها) حتی در نمونه اولیه، تا در هنگام راهاندازی مجبور به بازنویسی همه چیز از صفر نشوید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید