09. SOLID va Dependency Injection (DI)
Arxitektura
iOS Arxitektura 9: SOLID Prinsiplari va Dependency Injection — Har bir Arxitekturaning Asosi
Holat: Ushbu qo'llanma 2026-yil oktabr holatiga ko'ra yangilangan. Swift 6 va iOS 17+ zamonaviy yondashuvlari,
@Observable, vaasync/awaithisobga olingan holda tayyorlandi.
Mundarija
- SOLID nima va nima uchun muhim?
- S — Single Responsibility Principle
- O — Open/Closed Principle
- L — Liskov Substitution Principle
- I — Interface Segregation Principle
- D — Dependency Inversion Principle
- Dependency Injection (DI) turlari
- DI Container yaratish
- Protocol-Oriented Programming (POP)
- Amaliy loyiha: SOLID refactoring
- Xulosa
- Manbalar
1. SOLID nima va nima uchun muhim? {#solid-nima}
Barcha arxitektura pattern'lari (MVC, MVVM, VIPER, Clean Architecture) SOLID prinsiplari USTIGA qurilgan. SOLID ni bilmasdan turib hech bir arxitekturani to'liq tushunib bo'lmaydi. Bu 5 ta tamoyil Uncle Bob (Robert C. Martin) tomonidan tizimga solingan va ular dasturiy ta'minot dizaynining asosiy ustunlari hisoblanadi.
Bu tamoyillarni qo'llash orqali biz qanday tizimga erishamiz?
- Tushunish va o'qish oson bo'lgan kod.
- O'zgartirish kiritish xavfsizroq va osonroq.
- Qayta foydalanish (reusability) darajasi yuqori.
- Test qilish (Unit testing) uchun qulay arxitektura.
Muhim: SOLID qoidalar emas, balki prinsiplar. Ularni hamma joyda ham 100% qo'llash shart emas (over-engineering dan qochish kerak), lekin ular muammolarni qanday hal qilish kerakligini ko'rsatuvchi eng to'g'ri mayoqdir.
S-O-L-I-D Prinsiplari Vizual Xaritasi
2. S — Single Responsibility Principle (Yagona javobgarlik) {#s-srp}
Qoida: Har bir klass faqat va faqat BITTA vazifa (javobgarlik) bajarishi kerak. Ya'ni, klassni o'zgartirish uchun faqat bitta sabab bo'lishi kerak.
Yomon yondashuv: Bitta UIViewController yoki ob'ekt ichida UI chizish, internetdan ma'lumot yuklash (networking), ma'lumotni saqlash, validatsiya qilish.
Yaxshi yondashuv: Har bir vazifa uchun alohida ob'ektlar.
import Foundation
import Observation
import SwiftUI
// YOMON MISOL (Massive View Controller / Object)
@Observable
class BadUserViewModel {
var users: [String] = []
// UI va Networking va Validation bir joyda
@MainActor
func fetchUsers() async {
// Networking logic
let url = URL(string: "https://api.example.com/users")!
do {
let (data, _) = try await URLSession.shared.data(from: url)
let result = try JSONDecoder().decode([String].self, from: data)
// Validation logic
let validUsers = result.filter { !$0.isEmpty }
// State update
self.users = validUsers
} catch {
print(error)
}
}
}
// YAXSHI MISOL (Single Responsibility Principle)
// 1. Networking uchun alohida javobgar
protocol UserFetching {
func fetchUsers() async throws -> [String]
}
class UserService: UserFetching {
func fetchUsers() async throws -> [String] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([String].self, from: data)
}
}
// 2. Validation uchun alohida javobgar
struct UserValidator {
func filterValidUsers(_ users: [String]) -> [String] {
return users.filter { !$0.isEmpty }
}
}
// 3. Faqat UI state uchun javobgar
@Observable
@MainActor
class GoodUserViewModel {
var users: [String] = []
private let userService: UserFetching
private let validator: UserValidator
init(userService: UserFetching = UserService(), validator: UserValidator = UserValidator()) {
self.userService = userService
self.validator = validator
}
func loadUsers() async {
do {
let fetchedUsers = try await userService.fetchUsers()
self.users = validator.filterValidUsers(fetchedUsers)
} catch {
print("Xato yuz berdi: \(error)")
}
}
}
3. O — Open/Closed Principle (Ochiq/Yopiq) {#o-ocp}
Qoida: Dasturiy ta'minot obyektlari (klass, modul, funksiyalar) kengaytirish uchun ochiq (Open), lekin o'zgartirish uchun yopiq (Closed) bo'lishi kerak.
Ya'ni, tizimga yangi funksionallik qo'shish uchun eski ishlab turgan kodni o'zgartirmaslik kerak (chunki eski kodni buzib qo'yish mumkin). Buning o'rniga yangi kod yozish orqali kengaytirish kerak. Buni protocollar va polimorfizm orqali amalga oshiramiz.
import Foundation
// YOMON MISOL: Yangi to'lov turi qo'shilsa, PaymentManager ni o'zgartirishga to'g'ri keladi
class BadPaymentManager {
func processPayment(type: String, amount: Double) {
if type == "CreditCard" {
print("Credit Card: \(amount)")
} else if type == "ApplePay" {
print("Apple Pay: \(amount)")
}
// Yangi to'lov turi "PayPal" kelib qolsa, bu faylni O'ZGARTIRISH (modify) kerak bo'ladi.
}
}
// YAXSHI MISOL (Open/Closed Principle)
// 1. Abstraksiya (Protocol) yaratamiz
protocol PaymentProcessor {
func process(amount: Double)
}
// 2. Kengaytmalar (Extensions) - Har bir to'lov turi alohida klass
class CreditCardProcessor: PaymentProcessor {
func process(amount: Double) {
print("Credit Card orqali to'landi: $\(amount)")
}
}
class ApplePayProcessor: PaymentProcessor {
func process(amount: Double) {
print("Apple Pay orqali to'landi: $\(amount)")
}
}
// 3. Yangi to'lov turi (Eski kodlarga teginmasdan qo'shiladi)
class PayPalProcessor: PaymentProcessor {
func process(amount: Double) {
print("PayPal orqali to'landi: $\(amount)")
}
}
// 4. Boshqaruvchi - hech qachon o'zgarmaydi!
class GoodPaymentManager {
let processors: [PaymentProcessor]
init(processors: [PaymentProcessor]) {
self.processors = processors
}
func processAll(amount: Double) {
for processor in processors {
processor.process(amount: amount)
}
}
}
4. L — Liskov Substitution Principle (Liskov almashinuvi) {#l-lsp}
Qoida: Subclass (voris klass) o'zining Parent (ota klass) o'rnida ishlata olinishi kerak va dasturning ishlashi buzilmasligi kerak. iOS'da bu asosan Protocol conformance'da ko'rinadi.
Eng mashhur yomon misol bu To'rtburchak (Rectangle) va Kvadrat (Square) muammosi.
import Foundation
// YOMON MISOL
class Rectangle {
var width: Double = 0
var height: Double = 0
var area: Double { return width * height }
}
// Kvadrat To'rtburchakdan voris oladi. Kvadratning tomonlari teng.
class Square: Rectangle {
override var width: Double {
didSet { super.height = width }
}
override var height: Double {
didSet { super.width = height }
}
}
func printArea(of rectangle: Rectangle) {
rectangle.width = 4
rectangle.height = 5
// Biz 20 chiqishini kutamiz. Ammo bu Square bo'lsa, natija 25 chiqadi! Dastur mantig'i buzildi.
print(rectangle.area)
}
// YAXSHI MISOL (Liskov Substitution Principle)
// Ota sinf o'rnida protocol ishlatamiz
protocol Shape {
var area: Double { get }
}
struct BetterRectangle: Shape {
var width: Double
var height: Double
var area: Double { return width * height }
}
struct BetterSquare: Shape {
var side: Double
var area: Double { return side * side }
}
// Har ikkisi o'z mantig'iga ega va Shape o'rnida bemalol ishlaydi.
func printShapeArea(_ shape: Shape) {
print(shape.area) // Hech qanday mantiq buzilmaydi
}
5. I — Interface Segregation Principle (Interfeyslarni ajratish) {#i-isp}
Qoida: Katta va "semiz" protocol (interfeys) ni kichikroq, ixtisoslashgan protocol'larga bo'lish kerak. Foydalanuvchilar o'zlariga kerak bo'lmagan metodlarni amalga oshirishga (implement) majbur bo'lmasliklari kerak.
iOS'da buning eng yaxshi misoli UITableView da ko'rinadi. Apple bitta katta protocol qilish o'rniga uni UITableViewDataSource va UITableViewDelegate ga bo'lgan.
import Foundation
// YOMON MISOL: Semiz Protocol
protocol WorkerProtocol {
func work()
func eat()
func sleep()
}
// Robot ishlay oladi, lekin ovqat yemaydi va uxlamaydi.
// Lekin protocol majbur qilganligi uchun bo'sh metodlar qo'shishga majbur.
struct RobotWorker: WorkerProtocol {
func work() { print("Robot ishlamoqda") }
func eat() { fatalError("Robot ovqat yemaydi!") } // Buzilgan mantiq
func sleep() { fatalError("Robot uxlamaydi!") }
}
// YAXSHI MISOL (Interface Segregation Principle)
protocol Workable {
func work()
}
protocol Feedable {
func eat()
}
protocol Sleepable {
func sleep()
}
// Odam barchasini qila oladi
struct HumanWorker: Workable, Feedable, Sleepable {
func work() { print("Odam ishlamoqda") }
func eat() { print("Odam ovqatlanmoqda") }
func sleep() { print("Odam uxlamoqda") }
}
// Robot faqat ishlaydi. Unga kerak emas metodlarni berish majburiy emas.
struct BetterRobotWorker: Workable {
func work() { print("Robot ishlamoqda") }
}
6. D — Dependency Inversion Principle (Qaramlikni inversiya qilish) {#d-dip}
Qoida:
- Yuqori qatlam modullari (masalan, UI, ViewModel) past qatlam modullariga (masalan, Network, Database) bevosita bog'liq bo'lmasligi kerak.
- Ikkalasi ham ABSTRAKSIYAGA (Protocol'ga) bog'liq bo'lishi kerak.
Bu arxitekturani ajratish (Decoupling) dagi eng muhim prinsip va Dependency Injection'ning asosi hisoblanadi.
Dependency Inversion Principle va Abstraksiya
import Foundation
// YOMON MISOL (Concrete type qaramligi)
class APIService {
func fetchData() -> String { return "Data from Internet" }
}
class ViewModel {
// ViewModel to'g'ridan-to'g'ri APIService sinfiga qaram.
// Uni test qilish juda qiyin, chunki API ga ulanishni mock qilib bo'lmaydi.
private let apiService = APIService()
func load() {
print(apiService.fetchData())
}
}
// YAXSHI MISOL (Dependency Inversion)
// 1. Abstraksiya (Protocol)
protocol DataFetching {
func fetchData() -> String
}
// 2. Low-level modul protocol'ni amalga oshiradi
class NetworkService: DataFetching {
func fetchData() -> String { return "Data from API" }
}
// 3. High-level modul (ViewModel) faqat abstraksiyaga qaram
class BetterViewModel {
private let dataFetcher: DataFetching
// Protokolni Constructor orqali qabul qiladi
init(dataFetcher: DataFetching) {
self.dataFetcher = dataFetcher
}
func load() {
print(dataFetcher.fetchData())
}
}
// Test qilish uchun juda oson:
class MockNetworkService: DataFetching {
func fetchData() -> String { return "Mock Data for Testing" }
}
// let testVM = BetterViewModel(dataFetcher: MockNetworkService())
7. Dependency Injection (DI) turlari {#di-turlari}
Dependency Injection bu biron obyektga (qaramlikka) o'zi yaratishi o'rniga tashqaridan berib yuborish (inject qilish) usulidir. Buni asosan 3 xil usulda qilish mumkin.
a) Constructor Injection (ENG YAXSHI YONDASHUV) Qaramlik init (konstruktor) orqali beriladi. Obyekt yaratilishidan oldin barcha qaramliklar tayyor bo'lishi shart. O'zgaruvchi let bo'lishi va mutatsiyaga uchramasligi katta yutuq.
protocol OrderServiceProtocol { func fetchOrders() }
class OrderViewModel {
private let orderService: OrderServiceProtocol
init(orderService: OrderServiceProtocol) {
self.orderService = orderService
}
}
b) Property Injection (Setter Injection) Qaramlik property orqali beriladi. Qachonki init orqali berishning iloji bo'lmasa (masalan, Storyboard ishlatganda yoki qaramlik keyinroq tayyor bo'lganda) ishlatiladi. Xavfli jihati: unwrap qilish kerak yoki majburiy ochish (!) talab qilinadi.
import UIKit
protocol OrderViewModelProtocol { func loadOrders() }
class OrderViewController: UIViewController {
// Qachondir tashqaridan beriladi deb kutamiz
var viewModel: OrderViewModelProtocol!
override func viewDidLoad() {
super.viewDidLoad()
viewModel.loadOrders() // agar nil bo'lsa, Crash!
}
}
c) Method Injection Qaramlik faqat bitta metod bajarilishi uchun kerak bo'lganda beriladi. Butun ob'ekt darajasida saqlanmaydi.
protocol DataFormattingProtocol { func format(_ data: [String]) -> String }
class ReportGenerator {
// Ma'lumotlarni formatlash uchun qaramlik metod orqali berildi
func generateReport(data: [String], formatter: DataFormattingProtocol) -> String {
return formatter.format(data)
}
}
8. DI Container yaratish {#di-container}
Ilova kattalashgan sari, qaramliklarni qo'lda uzatib yurish qiyinlashadi. Buni osonlashtirish uchun DI Container yoki Service Locator pattern ishlatiladi. Kichikroq loyihalar uchun 3-party kutubxonalar (Swinject kabi) o'rniga o'zimiz oddiy DI Container yozishimiz mumkin.
import Foundation
// Abstraksiyalar
protocol AuthServiceProtocol { func login() }
protocol AnalyticsServiceProtocol { func logEvent() }
// Amalga oshirishlar
class AuthService: AuthServiceProtocol { func login() { print("Logged in") } }
class AnalyticsService: AnalyticsServiceProtocol { func logEvent() { print("Event Logged") } }
// DI Container (Factory bilan aralash yondashuv)
final class DIContainer {
static let shared = DIContainer()
private var services: [String: Any] = [:]
private init() {}
// Qaramlikni ro'yxatdan o'tkazish
func register<Service>(type: Service.Type, component: Any) {
let key = String(describing: type)
services[key] = component
}
// Qaramlikni olish
func resolve<Service>(type: Service.Type) -> Service {
let key = String(describing: type)
guard let service = services[key] as? Service else {
fatalError("\(key) ro'yxatdan o'tmagan!")
}
return service
}
}
// Ishlatilishi:
// 1. App start bo'lganda ro'yxatdan o'tkazamiz
// DIContainer.shared.register(type: AuthServiceProtocol.self, component: AuthService())
// 2. Kerak bo'lganda chaqirib olamiz
// let authService = DIContainer.shared.resolve(type: AuthServiceProtocol.self)
Senior eslatmasi: Hozirgi kunda Swift Macros yordamida
@Dependencykabi annotation'lar yordamida ancha toza DI tizimlari yaratilmoqda (masalan Point-Free'ningswift-dependencieskutubxonasi).
9. Protocol-Oriented Programming (POP) {#pop-swift}
Swift - Protocolga asoslangan (POP) til bo'lib, SOLID tamoyillarini amalga oshirish uchun ajoyib vositalarni beradi.
Protocol Composition (Protocollarni birlashtirish) Interface Segregation (I) tamoyilini amalga oshirganda biz protocollarni maydalagandik. Agar bizga ikkala xususiyat ham kerak bo'lsa, & orqali birlashtira olamiz.
protocol UserDataFetching { func fetch() }
protocol UserDataSaving { func save() }
// Typealias bilan kompozitsiya
typealias UserDataManaging = UserDataFetching & UserDataSaving
class UserRepository: UserDataManaging {
func fetch() { print("Fetched") }
func save() { print("Saved") }
}
Protocol Extensions (Standart implementatsiya) Bu Open/Closed (O) tamoyiliga ajoyib misol. Protocolga extension yozib, hamma uchun ishlaydigan standart kod yoza olamiz.
protocol Describable {
var title: String { get }
}
extension Describable {
func describe() {
print("Tavsif: \(title)")
}
}
struct Book: Describable {
let title: String
// describe() metodini yozish shart emas, u extensiondan keladi
}
10. Amaliy loyiha: SOLID refactoring {#amaliy-loyiha}
Tasavvur qiling, bizda juda og'ir va xunuk bir ViewModel bor (SRP, DIP buzilgan). Biz buni SOLID tamoyillari asosida tozalaymiz.
Yomon (Oldin)
import Foundation
// Barcha SOLID qoidalari buzilgan!
@MainActor
class ProductViewModel: ObservableObject {
@Published var products: [String] = []
// DIP buzilgan (Concrete type qaramligi)
let dbManager = CoreDataDBManager()
func fetchAndSaveProducts() async {
// SRP buzilgan: Networking ham shu yerda
let url = URL(string: "https://api.test/products")!
let (data, _) = try! await URLSession.shared.data(from: url)
let fetchedProducts = try! JSONDecoder().decode([String].self, from: data)
// SRP buzilgan: Parsing va Formatting
let formatted = fetchedProducts.map { $0.uppercased() }
self.products = formatted
// SRP buzilgan: DB saqlash ham shu yerda
for p in formatted {
dbManager.saveToDB(item: p)
}
}
}
class CoreDataDBManager {
func saveToDB(item: String) { print("Saved") }
}
Yaxshi (Keyin - Refactored by SOLID)
Biz bu jarayonni bir nechta modullarga va protocollarga bo'lib chiqamiz.
import Foundation
import Observation
// 1. O va D tamoyillari uchun abstraksiyalar (Interface Segregation)
protocol ProductNetworkFetching {
func fetchProducts() async throws -> [String]
}
protocol ProductFormatting {
func format(_ products: [String]) -> [String]
}
protocol ProductSaving {
func saveProduct(_ item: String)
}
// 2. Har bir modul Single Responsibility (S) tamoyili bilan amalga oshiriladi
class ProductNetworkService: ProductNetworkFetching {
func fetchProducts() async throws -> [String] {
let url = URL(string: "https://api.test/products")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([String].self, from: data)
}
}
class DefaultProductFormatter: ProductFormatting {
func format(_ products: [String]) -> [String] {
return products.map { $0.uppercased() }
}
}
class CoreDataStorage: ProductSaving {
func saveProduct(_ item: String) {
// CoreData save logic
print("\(item) DB ga saqlandi")
}
}
// 3. ViewModel - qaramliklarni Constructor orqali qabul qiladi (DI)
@Observable
@MainActor
final class BetterProductViewModel {
var products: [String] = []
// D tamoyili: Faqat abstraksiyaga bog'liq (Protocol)
private let networkService: ProductNetworkFetching
private let formatter: ProductFormatting
private let storage: ProductSaving
init(networkService: ProductNetworkFetching,
formatter: ProductFormatting,
storage: ProductSaving) {
self.networkService = networkService
self.formatter = formatter
self.storage = storage
}
func loadAndSave() async {
do {
let fetched = try await networkService.fetchProducts()
let formatted = formatter.format(fetched)
self.products = formatted
for item in formatted {
storage.saveProduct(item)
}
} catch {
print("Xato yuz berdi: \(error.localizedDescription)")
}
}
}
// Unit Test uchun juda oson:
class MockProductNetworkService: ProductNetworkFetching {
func fetchProducts() async throws -> [String] {
return ["iPhone", "MacBook"]
}
}
// let vm = BetterProductViewModel(networkService: MockProductNetworkService(), ...)
Ushbu refactoring orqali biz BetterProductViewModel ni test qilishni juda osonlashtirdik, har bir qismni alohida ajratib oldik va tizimga yangi tarmoq xizmati (masalan, Firebase) qo'shish kerak bo'lsa, mavjud kodni o'zgartirmasdan, yangi protokol implementatsiyasini qo'shib yuborish (O - Open/Closed) mumkin bo'ldi.
Xulosa {#xulosa}
SOLID qoidalari shunchaki intervyuda so'raladigan nazariya emas. Ular sifatli, uzoq yashovchi va kengayuvchi iOS loyihalarni yozish uchun zarur fundamentdir.
- SRP kodni maydalab tozalaydi.
- OCP kelajakda buzilishlardan saqlaydi.
- LSP o'rniga-o'rin ishlash xavfsizligini kafolatlaydi.
- ISP faqat kerakli vazifalarni beradi.
- DIP esa barchasini protocollar orqali moslashuvchan, testbop qilib ulaydi (Dependency Injection yordamida).