Published on

Hammasini bir joyga qo'yish: refaktor mashqi

Authors

Arxitektura patternlarini tushunishning eng yaxshi usuli — ularni bir xil ilovaga bosqichma-bosqich tatbiq etishni ko'rish. Bu darsda chalkash bitta faylni olib, har bir patternni qatlama-qatlam qo'llaysiz.

Ilova oddiy kitob o'qish kuzatuvchi. Har bir qadamda ilovani ishga tushiring — funksionalligi o'zgarmasligi kerak, faqat tuzilma.

1-qadam: boshlang'ich holat — hamma narsa bitta faylda

// ContentView.swift — oldingi holat
import SwiftUI

struct Kitob: Identifiable {
    let id = UUID()
    var sarlavha: String
    var o'qildi: Bool
}

struct ContentView: View {
    @State private var kitoblar: [Kitob] = [
        Kitob(sarlavha: "Swift dasturlash tili", o'qildi: true),
        Kitob(sarlavha: "Toza kod", o'qildi: false),
    ]
    @State private var yangiSarlavha = ""
    @State private var xatoXabar: String? = nil
    @State private var faqatO'qilmaganlar = false

    var filterlangan: [Kitob] {
        faqatO'qilmaganlar ? kitoblar.filter { !$0.o'qildi } : kitoblar
    }

    var body: some View {
        NavigationStack {
            VStack {
                Toggle("Faqat o'qilmaganlar", isOn: $faqatO'qilmaganlar).padding()
                HStack {
                    TextField("Kitob sarlavhasi", text: $yangiSarlavha)
                    Button("Qo'shish") {
                        let tozalangan = yangiSarlavha.trimmingCharacters(in: .whitespaces)
                        guard !tozalangan.isEmpty else {
                            xatoXabar = "Sarlavha bo'sh bo'lishi mumkin emas."
                            return
                        }
                        kitoblar.append(Kitob(sarlavha: tozalangan, o'qildi: false))
                        yangiSarlavha = ""
                        xatoXabar = nil
                    }
                }.padding(.horizontal)
                if let xato = xatoXabar { Text(xato).foregroundStyle(.red) }
                List(filterlangan) { kitob in Text(kitob.sarlavha) }
            }
            .navigationTitle("Kitob kuzatuvchi")
        }
    }
}

2-qadam: modelni alohida faylga ko'chirish

// Kitob.swift — Shared/Models/ papkasida yangi fayl
import Foundation

struct Kitob: Identifiable {
    let id = UUID()
    var sarlavha: String
    var o'qildi: Bool
}
// ContentView.swift dan Kitob ta'rifini o'chirib tashlang

3-qadam: repository qo'shish

// KitobRepository.swift — Features/Books/ da
import Foundation

protocol KitobRepository {
    func kitoblarniYukla() -> [Kitob]
    mutating func kitobQosh(sarlavha: String)
}

// Haqiqiy implementatsiya — hozircha xotira massivi
struct XotiradagiKitobRepository: KitobRepository {
    private var kitoblar: [Kitob] = [
        Kitob(sarlavha: "Swift dasturlash tili", o'qildi: true),
        Kitob(sarlavha: "Toza kod", o'qildi: false),
    ]

    func kitoblarniYukla() -> [Kitob] { kitoblar }

    mutating func kitobQosh(sarlavha: String) {
        kitoblar.append(Kitob(sarlavha: sarlavha, o'qildi: false))
    }
}

4-qadam: view model qo'shish

// KitobKorinishiModeli.swift — Features/Books/ da
import SwiftUI

@Observable
class KitobKorinishiModeli {
    var kitoblar: [Kitob] = []
    var xatoXabar: String? = nil
    var faqatO'qilmaganlar = false

    // Filtrlash — ma'lumot almashtirish, viewga emas bu yerga tegishli
    var filterlanganKitoblar: [Kitob] {
        faqatO'qilmaganlar ? kitoblar.filter { !$0.o'qildi } : kitoblar
    }

    private var repository: any KitobRepository

    init(repository: any KitobRepository = XotiradagiKitobRepository()) {
        self.repository = repository
        self.kitoblar = repository.kitoblarniYukla()
    }

    func kitobQosh(_ sarlavha: String) {
        let tozalangan = sarlavha.trimmingCharacters(in: .whitespaces)
        guard !tozalangan.isEmpty else {
            xatoXabar = "Sarlavha bo'sh bo'lishi mumkin emas."
            return
        }
        repository.kitobQosh(sarlavha: tozalangan)
        kitoblar = repository.kitoblarniYukla()
        xatoXabar = nil
    }
}

5-qadam: viewni tozalash

// ContentView.swift — keyin holat
// Hammasi shu yerda — view faqat UI ni boshqaradi
import SwiftUI

struct ContentView: View {
    @State private var model = KitobKorinishiModeli()
    @State private var yangiSarlavha = ""

    var body: some View {
        NavigationStack {
            VStack {
                Toggle("Faqat o'qilmaganlar", isOn: $model.faqatO'qilmaganlar).padding()
                HStack {
                    TextField("Kitob sarlavhasi", text: $yangiSarlavha)
                    Button("Qo'shish") {
                        model.kitobQosh(yangiSarlavha)
                        yangiSarlavha = ""
                    }
                }.padding(.horizontal)
                if let xato = model.xatoXabar { Text(xato).foregroundStyle(.red) }
                List(model.filterlanganKitoblar) { kitob in Text(kitob.sarlavha) }
            }
            .navigationTitle("Kitob kuzatuvchi")
        }
    }
}

// Preview — mock repository bilan
#Preview {
    ContentView()
}

Natija — fayl tuzilmasi

KitobKuzatuvchi/
├── App/
│   └── KitobKuzatuvchiApp.swift
├── Features/
│   └── Books/
│       ├── ContentView.swiftFaqat UI
│       ├── KitobKorinishiModeli.swiftBiznes logikasi
│       └── KitobRepository.swiftMa'lumot kirishi
└── Shared/
    └── Models/
        └── Kitob.swiftModel

Oldin: 1 fayl, 4 mas'uliyat
Keyin: 4 fayl, har biri 1 mas'uliyat

Nima o'zgardi

OldinKeyin
Validatsiya viewdakitobQosh() da
Filtrlash viewda computed propertyfilterlanganKitoblar view modelda
Ma'lumot viewda @State massivRepository va view modelda
Test yozib bo'lmaydiView modelini mustaqil sinash mumkin

🎯 Topshiriq: o'z ilovangizni refaktor qiling

O'zingiz qurayotgan ilovani 5 qadamda refaktor qiling: Model → Repository → View modeli → Ko'rinish → Papka tuzilmasi. Har bir qadamdan keyin simulator da ishlatib tasdiqlang. Oxirigacha olganda kodni boshqa odamga berishga tayyor bo'ling — hech bir fayl nima qilayotganini tushuntirishga muhtoj bo'lmasdan.

Buy mea coffee