"الزائر" هو أحد الأنماط السلوكية الموضحة في الكتاب المدرسي
"Gang of Four" و "GoF" و "أنماط التصميم: عناصر من البرنامج الموجه لإعادة الاستخدام القابل لإعادة الاستخدام" ") .
باختصار ، يمكن أن يكون القالب مفيدًا عندما يكون من الضروري أن يكون قادرًا على تنفيذ أي إجراءات من نفس النوع على مجموعة من الكائنات من أنواع مختلفة غير متصلة ببعضها البعض. أو ، بعبارة أخرى ، لتوسيع وظيفة هذه السلسلة من الأنواع من خلال عملية معينة من نفس النوع أو لها مصدر واحد. في الوقت نفسه ، لا ينبغي أن تتأثر بنية وتنفيذ أنواع الموسعة.
أسهل طريقة لشرح الفكرة هي مثال.
أود أن أبدي تحفظًا على الفور بأن المثال خيالي ويتألف من أغراض أكاديمية. أي هذه المادة تهدف إلى تقديم استقبال OOP ، وليس لمناقشة المشاكل المتخصصة للغاية.أود أيضًا أن ألفت الانتباه إلى حقيقة أن الكود في الأمثلة قد كتب من أجل دراسة تقنية التصميم. أنا على دراية بأوجه القصور (الرمز) وإمكانيات تحسينه للاستخدام في المشاريع الحقيقية.مثال
افترض أن لديك نوعًا فرعيًا من
UITableViewController
يستخدم عدة أنواع فرعية من
UITableViewCell
:
class FirstCell: UITableViewCell { } class SecondCell: UITableViewCell { } class ThirdCell: UITableViewCell { } class TableVC: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(FirstCell.self, forCellReuseIdentifier: "FirstCell") tableView.register(SecondCell.self, forCellReuseIdentifier: "SecondCell") tableView.register(ThirdCell.self, forCellReuseIdentifier: "ThirdCell") } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return FirstCell() return SecondCell() return ThirdCell() } }
لنفترض أن خلايا الأنواع الفرعية المختلفة لها ارتفاعات مختلفة.
بالطبع ، يمكن وضع حساب الارتفاع مباشرة في تنفيذ كل نوع من الخلايا. ولكن ماذا لو كان ارتفاع الخلية لا يعتمد فقط على نوعه الخاص ، ولكن أيضًا على أي ظروف خارجية؟ على سبيل المثال ، يمكن استخدام نوع خلية في جداول مختلفة ذات ارتفاعات مختلفة. في هذه الحالة ، لا نريد مطلقًا أن تكون الفئات الفرعية لـ UITableViewCell مدركة لاحتياجات "superview" أو "وحدة تحكم العرض" الخاصة بهم.
ثم يمكن إجراء حساب الارتفاع في أساليب
UITableViewController
: إما تهيئة
UITableViewCell
مع قيمة الارتفاع ، أو
UITableViewCell
مثيل
UITableViewCell
بنوع فرعي معين وإرجاع قيم مختلفة في طريقة
tableView(_:heightForRowAt:)
. لكن يمكن أن يصبح هذا النهج غير مرن ويتحول إلى سلسلة طويلة من عوامل التشغيل "if" أو بنية "التبديل" الضخمة.
حل المشكلة باستخدام قالب "الزائر"
بالطبع ، ليس فقط قالب "الزائر" قادر على حل هذه المشكلة ، لكنه قادر على القيام بذلك بأناقة.للقيام بذلك ، أولاً ، سننشئ نوعًا ، في الواقع ، سيكون "زائرًا" لأنواع الخلايا وكائنًا تتمثل مسؤوليته فقط في حساب ارتفاع خلية الجدول:
struct HeightResultVisitor { func visit(_ ell: FirstCell) -> CGFloat { return 10.0 } func visit(_ ell: SecondCell) -> CGFloat { return 20.0 } func visit(_ ell: ThirdCell) -> CGFloat { return 30.0 } }
النوع يدرك كل نوع فرعي مستخدم ويعيد القيمة المطلوبة لكل منها.
ثانياً ، يجب أن يكون كل نوع فرعي من
UITableViewCell
قادراً على "استقبال" هذا "الزائر". للقيام بذلك ، سنعلن بروتوكولًا باستخدام طريقة "الاستلام" هذه ، والتي سيتم تنفيذها بواسطة جميع أنواع الخلايا المستخدمة:
protocol HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat } extension FirstCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension SecondCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension ThirdCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } }
داخل الفئة الفرعية
UITableViewController
، يمكن استخدام الوظيفة على النحو التالي:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! HeightResultVisitable return cell.accept(HeightResultVisitor()) }
يمكن أن يكون أفضل!
على الأرجح ، لا نريد أن يكون لدينا مثل هذا الكود المرتبط بشكل صارم بوظيفة محددة. ربما نريد أن نكون قادرين على إضافة وظائف جديدة إلى مجموعة الخلايا لدينا ، ولكن ليس فقط فيما يتعلق بطولها ، ولكن ، على سبيل المثال ، لون الخلفية والنص داخل الخلية ، وما إلى ذلك ، وعدم ربطها بنوع قيمة الإرجاع. سوف تساعد
البروتوكولات ذات النمط associatedtype
(
"البروتوكول ذو النوع المقترن" ، "PAT" ) هنا:
protocol CellVisitor { associatedtype T func visit(_ cell: FirstCell) -> T func visit(_ cell: SecondCell) -> T func visit(_ cell: ThirdCell) -> T }
تنفيذه للعودة ارتفاع الخلية:
struct HeightResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> CGFloat { return 10.0 } func visit(_ cell: SecondCell) -> CGFloat { return 20.0 } func visit(_ cell: ThirdCell) -> CGFloat { return 30.0 } }
على الجانب "المضيف" ، يكفي وجود بروتوكول مشترك وتنفيذه فقط - لأي "زائر" من هذا النوع. فقط الأطراف "الزائرة" ستكون على دراية بأنواع مختلفة من قيم الإرجاع.
بروتوكول "زائر زائر" (في كتاب "GoF" هذا الجانب يسمى "العنصر") من النوع سيأخذ الشكل:
protocol Visitableell where Self: UITableViewCell { func accept<V: CellVisitor>(_ visitor: V) -> VT }
(قد لا يكون هناك أي قيود لنوع التنفيذ. ولكن في هذا المثال ، لا يعقل أن يتم تطبيق هذا البروتوكول من خلال الفئات الفرعية من
UITableViewCell
.)
وتنفيذه في أنواع فرعية من
UITableViewCell
:
extension FirstCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension SecondCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension ThirdCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } }
وأخيرا ، استخدم:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! Visitableell return cell.accept(HeightResultCellVisitor()) }
وبالتالي ، سوف نكون قادرين على إنشاء ، باستخدام تطبيقات مختلفة من "الزائر" ، بشكل عام ، أي شيء تقريبًا ، ولن يكون هناك شيء مطلوب من "جانب الاستقبال" لدعم الوظيفة الجديدة. لن يدرك هذا الطرف ما منحه "الضيف" بالضبط.
مثال آخر
دعونا نحاول تغيير لون خلفية الخلية باستخدام "زائر" مماثل:
struct ColorResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> UIColor { return .black } func visit(_ cell: SecondCell) -> UIColor { return .white } func visit(_ cell: ThirdCell) -> UIColor { return .red } }
مثال على استخدام هذا الزائر:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.contentView.backgroundColor = (cell as! Visitableell).accept(ColorResultCellVisitor()) }
يجب أن يكون شيء ما في هذا الرمز مربكًا ... في البداية ، قيل إن "الزائر" قادر على إضافة وظائف إلى الفصل من الخارج. فهل من الممكن "إخفاء" كل وظيفة تغيير لون الخلفية للخلية ، وليس فقط الحصول على القيمة منه؟ يمكنك ذلك. بعد ذلك ، سيستغرق النوع
associatedtype
القيمة
Void
(aka ()
- tuple فارغة) :
struct BackgroundColorSetter: CellVisitor{ func visit(_ cell: FirstCell) { cell.contentView.backgroundColor = .black } func visit(_ cell: SecondCell) { cell.contentView.backgroundColor = .white } func visit(_ cell: ThirdCell) { cell.contentView.backgroundColor = .red } }
الاستخدام:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { (cell as! Visitableell).accept(BackgroundColorSetter()) }
بدلا من الاستنتاج
قد يعجبك النموذج لأول وهلة تقريبًا ، ومع ذلك ، يجب عليك استخدامه بعناية. غالبًا ما يكون ظهوره في الكود علامة على وجود عيوب أكثر في العمارة. ربما تحاول توصيل الأشياء التي يجب ألا تكون متصلاً. ربما تستحق الوظيفة المضافة وضع مستوى من التجريد أعلى بطريقة أو بأخرى.
بشكل أو بآخر ، يتميز أي نمط تقريبًا بمزاياه وعيوبه ، وقبل استخدامه يجب أن تفكر دائمًا وتتخذ قرارًا بوعي. تعتبر النماذج من ناحية طريقة لتعميم تقنيات البرمجة لتسهيل قراءة التعليمات البرمجية ومناقشتها. ومن ناحية أخرى - وسيلة لحل مشكلة (أدخلت في بعض الأحيان بشكل مصطنع). وبطبيعة الحال ، على أي حال ، لا تقدم تعريفة الشفرة إلى جميع الأنماط المعروفة لمجرد حقيقة استخدامها.
أعتقد أنني انتهيت! كل رمز جميل وعدد أقل من "البق"!
مقالاتي الأخرى عن أنماط التصميم: