الگوی طراحی Iterator در سوئیفت
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 5 دقیقه

الگوی طراحی Iterator در سوئیفت

در این مقاله الگوی طراحی تکرار یا iterator را با استفاده از برخی توالی های سفارشی و مطابق با IteratorProtocol از کتابخانه استاندارد سوئیفت، برسی میکنیم.

الگوی طراحی تکرار یا iterator در کتابخانه استاندارد سوئیفت به وفور مورد استفاده قرار می گیرد و همچنین پروتکل هایی در آن وجود دارد که در صورت نیاز به ایجاد iterator به شما کمک می کند، اما صادقانه بگویم: من هرگز این الگو را مستقیماً پیاده نکرده ام!

حقیقت این است که احتمالاً در 99 درصد موارد هرگز مجبور نخواهید بود این الگوی طراحی را مستقیما پیاده سازی کنید زیرا پشتیبانی از iterator ها مستقیماً در سوئیفت وجود دارد. همیشه به جای پیاده سازی مستقیم این الگوی طراحی از توالی ها (sequences)، آرایه ها (arrays) و فرهنگ لغت ها (dictionaries) استفاده کنید ، اما خوب است که بدانید که همه چیز در زیر چگونه کار می کند ، اینطور نیست؟

الگوی طراحی تکرار یا iterator چیست؟

همانطور که از نامش پیداست، این الگوی طراحی به شما امکان می دهد تا مجموعه ای از عناصر را تکرار کنید. در اینجا تعریفی از کتاب معروف gang of four داریم:

راهی برای دسترسی پی در پی به عناصر یک شیء مجتمع بدون افشای نمای اصلی آن ارائه می دهد.

به طور خلاصه iterator یک رابط در اختیار قرار می دهد که به شما این امکان را می دهد که مجموعه ها را بدون توجه به نحوه پیاده سازی آنها در پس زمینه تکرار کنید. در اینجا مثالی ساده از تئوری فوق با استفاده از یک iterator رشته داریم:

protocol StringIterator {
    func next() -> String?
}

class ArrayStringIterator: StringIterator {

    private let values: [String]
    private var index: Int?

    init(_ values: [String]) {
        self.values = values
    }

    private func nextIndex(for index: Int?) -> Int? {
        if let index = index, index < self.values.count - 1 {
            return index + 1
        }
        if index == nil, !self.values.isEmpty {
            return 0
        }
        return nil
    }

    func next() -> String? {
        if let index = self.nextIndex(for: self.index) {
            self.index = index
            return self.values[index]
        }
        return nil
    }
}
protocol Iterable {
    func makeIterator() -> StringIterator
}

class DataArray: Iterable {

    private var dataSource: [String]

    init() {
        self.dataSource = ["🐶", "🐔", "🐵", "🦁", "🐯", "🐭", "🐱", "🐮", "🐷"]
    }

    func makeIterator() -> StringIterator {
        return ArrayStringIterator(self.dataSource)
    }
}
let data = DataArray()
let iterator = data.makeIterator()

while let next = iterator.next() {
    print(next)
}

همانطور که می بینید دو پروتکل اصلی و یک پیاده سازی بسیار ساده برای هر دو وجود دارد. کلاس DataArray ما اکنون مانند یک آرایه واقعی عمل می کند، عناصر زیرین را می توان با استفاده از یک حلقه تکرار کرد. بیایید تئوری را کنار بگذاریم و مثال بالا را با استفاده از اجزای واقعی کتابخانه استاندارد سوئیفت پیاده سازی کنیم.

دنباله های سفارشی در سوئیفت

سوئیفت دارای یک پروتکل توالی داخلی است که به شما در ایجاد iterator ها کمک می کند. پیاده سازی دنباله شخصی شما در سوئیفت به معنای پنهان کردن ساختار های داده شما با ایجاد یک شی iterator سفارشی است. فقط باید شاخص (index) فعلی را ذخیره کرده و هر بار که تابع بعدی فراخوانی می شود، عنصر بعدی را مطابق با آن بازگردانید.

struct Emojis: Sequence {
    let animals: [String]

    func makeIterator() -> EmojiIterator {
        return EmojiIterator(self.animals)
    }
}
struct EmojiIterator: IteratorProtocol {

    private let values: [String]
    private var index: Int?

    init(_ values: [String]) {
        self.values = values
    }

    private func nextIndex(for index: Int?) -> Int? {
        if let index = index, index < self.values.count - 1 {
            return index + 1
        }
        if index == nil, !self.values.isEmpty {
            return 0
        }
        return nil
    }

    mutating func next() -> String? {
        if let index = self.nextIndex(for: self.index) {
            self.index = index
            return self.values[index]
        }
        return nil
    }
}
let emojis = Emojis(animals: ["🐶", "🐔", "🐵", "🦁", "🐯", "🐭", "🐱", "🐮", "🐷"])
for emoji in emojis {
    print(emoji)
}

بنابراین پروتکل دنباله یک نمونه کلی از پروتکل iterator سفارشی ما است که در مثال اول استفاده شده است. پروتکل IteratorProtocol تا حدودی شبیه پروتکل iterator رشته ای است که قبلاً استفاده شد اما سوئیفتی تر و البته عمومی تر است.

بنابراین این عالی است. در نهایت شما می دانید که چگونه یک دنباله سفارشی را ایجاد کنید. اگر می خواهید ساختار داده های خود را پنهان کرده و یک رابط عمومی قابل تکرار ارائه دهید ، این الگوی طراحی بسیار مناسب است. تصور کنید چه اتفاقی می افتد اگر بخواهید از دیکشنری به جای آرایه برای ذخیره ایموجی های نامگذاری شده بدون iterator آنها استفاده کنید.

همچنین یک چیز بسیار کاربردی دیگر در کتابخانه استاندارد سوئیفت وجود دارد که مایلم در مورد آن صحبت کنم. درست است ، یک abstraction بالاتر:

مجموعه های سفارشی در سوئیفت

مجموعه ها یک قدم فراتر از توالی ها هستند. به عناصر داخل آنها می توان از طریق subscript دسترسی داشت ، آنها همچنین دارای شاخص ابتدا یا startIndex و شاخص پایان یا endIndex هستند، به علاوه به عناصر جداگانه مجموعه می توان چندین بار دسترسی داشت. خوب به نظر می رسد؟

گاهی اوقات ایجاد یک مجموعه سفارشی می تواند مناسب باشد، برای مثال وقتی می خواهید مقادیر اختیاری را حذف کنید. مکانیسم دلخواه طبقه بندی شده را تصور کنید، برای هر دسته ای مجموعه ای از موارد دلخواه را دارید، بنابراین باید با موارد خالی و غیر موجود برخورد کنید. با یک مجموعه سفارشی می توانید آن کد اضافی را در ساختار داده سفارشی خود پنهان کنید و یک رابط تمیز برای بقیه برنامه خود ارائه دهید.

class Favorites {

    typealias FavoriteType = [String: [String]]

    private(set) var list: FavoriteType

    public static let shared = Favorites()

    private init() {
        self.list = FavoriteType()
    }
}
extension Favorites: Collection {

    typealias Index = FavoriteType.Index
    typealias Element = FavoriteType.Element

    var startIndex: Index {
        return self.list.startIndex
    }
    var endIndex: Index {
        return self.list.endIndex
    }

    subscript(index: Index) -> Iterator.Element {
        return self.list[index]
    }

    func index(after i: Index) -> Index {
        return self.list.index(after: i)
    }
}
extension Favorites {

    subscript(index: String) -> [String] {
        return self.list[index] ?? []
    }

    func add(_ value: String, category: String) {
        if var values = self.list[category] {
            guard !values.contains(value) else {
                return
            }
            values.append(value)
            self.list[category] = values
        }
        else {
            self.list[category] = [value]
        }
    }

    func remove(_ value: String, category: String) {
        guard var values = self.list[category] else {
            return
        }
        values = values.filter { $0 == value }

        if values.isEmpty {
            self.list.removeValue(forKey: category)
        }
        else {
            self.list[category] = values
        }
    }
}
Favorites.shared.add("apple", category: "fruits")
Favorites.shared.add("pear", category: "fruits")
Favorites.shared.add("apple", category: "fruits")

Favorites.shared["fruits"]

Favorites.shared.remove("apple", category: "fruits")
Favorites.shared.remove("pear", category: "fruits")
Favorites.shared.list

مثالی که برسی کردیم کاربردی ندارد اما نشان می دهد که چرا مجموعه ها در مقایسه با توالی های خالص پیشرفته تر هستند. اگر به الگوی طراحی Iterator علاقه مند هستید می توانید مقالات دیگر و همچنین پروتکل های کاربردی و انواع داده های سفارشی که در کتابخانه استاندارد سوئیفت وجود دارند را نیز برسی کنید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@armanabkar
آرمان آبکار
Software Developer

توسعه دهند موبایل (iOS) و فرانت اند

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید