يعد "التكرار" أحد أنماط التصميم التي لا يلاحظها المبرمجون في أغلب الأحيان ، لأن تنفيذها ، كقاعدة عامة ، يتم تضمينه مباشرةً في الأدوات القياسية للغة البرمجة. ومع ذلك ، يعد هذا أيضًا أحد الأنماط السلوكية الموضحة في
كتاب "Gang of Four" ، و "GoF" ، و "أنماط التصميم: عناصر من البرنامج الموجه لإعادة الاستخدام" ، وفهمها لا يؤلم الجهاز الخاص به ، وأحيانًا يمكن أن يساعد في شيء ما.
"التكرار" هو وسيلة للوصول المتسلسل إلى جميع عناصر كائن مركب (على وجه الخصوص ، أنواع الحاويات ، مثل مجموعة أو مجموعة).أدوات اللغة القياسية
إنشاء نوع من
الصفيف :
let numbersArray = [0, 1, 2]
... ثم "المشي" من خلال ذلك في
دورة :
for number in numbersArray { print(number) }
... يبدو وكأنه إجراء طبيعي للغاية ، خاصة بالنسبة للغات البرمجة الحديثة مثل
سويفت . ومع ذلك ، وراء الكواليس من هذا الإجراء البسيط هو رمز ينفذ مبادئ نمط التكرار.
في "Swift" ، من أجل التمكن من "تكرار" المتغير باستخدام التكرار ، يجب أن يقوم النوع المتغير بتطبيق
بروتوكول Sequence
. من بين أشياء أخرى ، يتطلب هذا البروتوكول من النوع أن يكون لديه
Iterator
associatedtype
، والذي بدوره يجب أن ينفذ متطلبات بروتوكول
IteratorProtocol
، وكذلك
makeIterator()
method factory factory الذي يعرض "محدد" محدد لهذا النوع:
protocol Sequence { associatedtype Iterator : IteratorProtocol func makeIterator() -> Self.Iterator
يحتوي بروتوكول
IteratorProtocol
، بدوره ، على طريقة واحدة فقط -
next()
، والتي تُرجع العنصر التالي في التسلسل:
protocol IteratorProtocol { associatedtype Element mutating func next() -> Self.Element? }
يبدو مثل "الكثير من التعليمات البرمجية المعقدة" ، لكنه في الواقع ليس كذلك. أدناه سوف نرى هذا.
يقوم نوع
Array
بتنفيذ بروتوكول
Sequence
(ولكن ليس بشكل مباشر ، ولكن من خلال سلسلة
ميراث البروتوكول : يرث
MutableCollection
متطلبات
Collection
، والأخير يرث متطلبات
Sequence
) ، لذلك يمكن مثيلاته ، على وجه الخصوص ، "التكرار" باستخدام
for
-cycles.
أنواع مخصصة
ما الذي يجب القيام به لتتمكن من تكرار نوعك الخاص؟ كما يحدث في كثير من الأحيان ، فمن الأسهل لإظهار مثال.
افترض أن هناك نوعًا يمثل رفًا للكتب يخزن مجموعة معينة من مثيلات الفصل ، والذي بدوره يمثل كتابًا:
struct Book { let author: String let title: String } struct Shelf { var books: [Book] }
لتكون قادرًا على "تكرار" مثيل لفئة
Shelf
، يجب أن تفي هذه الفئة بمتطلبات بروتوكول
Sequence
. على سبيل المثال ، سيكون تطبيق أسلوب
makeIterator()
كافياً فقط ، خاصة وأن متطلبات البروتوكول الأخرى لها
تطبيقات افتراضية . يجب أن تقوم هذه الطريقة بإرجاع مثيل لنوع يقوم بتنفيذ بروتوكول
IteratorProtocol
. لحسن الحظ ، في حالة سويفت ، هذا رمز بسيط للغاية:
struct ShelfIterator: IteratorProtocol { private var books: [Book] init(books: [Book]) { self.books = books } mutating func next() -> Book? {
تم الإعلان عن تغيير الطريقة
next()
الخاصة بنوع
ShelfIterator
، لأن مثيل الكتابة يجب أن يخزن بطريقة ما الحالة المقابلة للتكرار الحالي:
mutating func next() -> Book? { defer { if !books.isEmpty { books.removeFirst() } } return books.first }
يعرض خيار التطبيق هذا دائمًا العنصر الأول في التسلسل ، أو
nil
إذا كان التسلسل فارغًا.
defer
"لف" كتلة التأجيل برمز لتغيير المجموعة التكرارية ، مما يزيل عنصر آخر خطوة تكرارية فور عودة الطريقة.
مثال للاستخدام:
let book1 = Book(author: ". ", title: "") let book2 = Book(author: ". ", title: " ") let book3 = Book(author: ". ", title: " ") let shelf = Shelf(books: [book1, book2, book3]) for book in shelf { print("\(book.author) – \(book.title)") }
بسبب تستند جميع الأنواع المستخدمة (بما في ذلك
Shelf
الأساسي
Shelf
) إلى
دلالات القيم (على عكس المراجع) ، فلا داعي للقلق بشأن قيمة المتغير الأصلي الذي يتم تغييره أثناء التكرار. عند التعامل مع الأنواع استنادًا إلى دلالات الارتباط ، يجب أن تؤخذ هذه النقطة في الاعتبار وتؤخذ في الاعتبار عند إنشاء برامج التكرار الخاصة بك.
وظائف الكلاسيكية
يمكن لـ "التكرار" الكلاسيكي الموصوف في كتاب "Gang of of Four" ، بالإضافة إلى إرجاع العنصر التالي للتسلسل القابل للتكرار ، في أي وقت أيضًا إرجاع العنصر الحالي في عملية التكرار ، العنصر الأول في التسلسل التكراري وقيمة "العلم" الذي يشير إلى ما إذا كان لا يزال هناك العناصر في تسلسل مكرر نسبة إلى خطوة التكرار الحالية.
بالطبع ، سيكون من السهل الإعلان عن بروتوكول وبالتالي توسيع قدرات
IteratorProtocol
القياسية:
protocol ClassicIteratorProtocol: IteratorProtocol { var currentItem: Element? { get } var first: Element? { get } var isDone: Bool { get } }
يتم إرجاع العناصر الأولى والحالية اختياري منذ قد يكون تسلسل المصدر فارغًا.
خيار التنفيذ البسيط:
struct ShelfIterator: ClassicIteratorProtocol { var currentItem: Book? = nil var first: Book? var isDone: Bool = false private var books: [Book] init(books: [Book]) { self.books = books first = books.first currentItem = books.first } mutating func next() -> Book? { currentItem = books.first books.removeFirst() isDone = books.isEmpty return books.first } }
في الوصف الأصلي للنمط ، يغير الأسلوب
next()
الحالة الداخلية للتكرار للانتقال إلى العنصر التالي ويكون من النوع
Void
، ويتم إرجاع العنصر الحالي بواسطة الأسلوب
currentElement()
. في بروتوكول
IteratorProtocol
، تكون هاتان الوظيفتان كما لو تم دمجهما في واحدة.
الحاجة إلى الطريقة
first()
مشكوك فيها أيضًا ، لأن لا يغير التكرار التسلسل الأصلي ، ولدينا دائمًا فرصة الوصول إلى العنصر الأول (إن وجد ، بالطبع).
ونظرًا لأن الطريقة
next()
تُرجع
nil
عند انتهاء التكرار ، فإن الطريقة
isDone()
أيضًا تصبح عديمة الفائدة.
ومع ذلك ، للأغراض الأكاديمية ، من الممكن تمامًا التوصل إلى وظيفة يمكنها استخدام الوظيفة الكاملة:
func printShelf(with iterator: inout ShelfIterator) { var bookIndex = 0 while !iterator.isDone { bookIndex += 1 print("\(bookIndex). \(iterator.currentItem!.author) – \(iterator.currentItem!.title)") _ = iterator.next() } } var iterator = ShelfIterator(books: shelf.books) printShelf(with: &iterator)
المعلنة
iterator
يتم الإعلان عنها بسبب تتغير حالتها الداخلية أثناء تنفيذ الوظيفة. وعندما يتم استدعاء الوظيفة ، يتم نقل مثيل التكرار ليس مباشرة من خلال قيمته الخاصة ، ولكن حسب المرجع.
لا يتم استخدام نتيجة استدعاء الأسلوب
next()
، محاكاة غياب القيمة المرجعة لتطبيق كتاب مدرسي.
بدلا من الاستنتاج
يبدو أن هذا هو كل ما أردت قوله هذه المرة. كل رمز جميل ومتعمد كتابته!
مقالاتي الأخرى عن أنماط التصميم: