البرمجة الموجهة للبروتوكول في Swift 5.1

البروتوكولات هي خاصية أساسية لـ Swift. وهي تلعب دورًا مهمًا في مكتبات Swift القياسية وهي طريقة شائعة لتجريد الشفرة. من نواح كثيرة ، فهي تشبه الواجهات في لغات البرمجة الأخرى.

في هذا البرنامج التعليمي ، سوف نقدم لك نهج تطوير التطبيقات يسمى البرمجة الموجهة للبروتوكول ، والذي أصبح تقريبًا جوهر Swift. هذا هو حقا ما تحتاج إلى فهمه عند تعلم Swift!

في هذا الدليل ، أنت:

  • فهم الفرق بين البرمجة الشيئية والبرمجة الموجهة.
  • فهم تطبيقات البروتوكول القياسية ؛
  • تعرف على كيفية توسيع وظائف مكتبة Swift القياسية ؛
  • تعرف على كيفية توسيع البروتوكولات باستخدام الأدوية العامة.

ابدء


تخيل أنك تقوم بتطوير لعبة سباق. يمكن للاعبين قيادة السيارات والدراجات النارية والطائرات. وحتى الطيران على الطيور ، هذه لعبة ، أليس كذلك؟ الشيء الرئيسي هنا هو أن هناك dofiga من جميع أنواع "الأشياء" التي يمكنك القيادة ، والطيران ، وما إلى ذلك.

من الأساليب الشائعة لتطوير هذه التطبيقات البرمجة الشيئية. في هذه الحالة ، نرفق كل المنطق في بعض الفئات الأساسية ، التي نرث منها بعد ذلك. لذلك ، يجب أن تحتوي الفئات الأساسية على منطق "الرصاص" و "الطيار".

نبدأ التطوير من خلال إنشاء فصول لكل مركبة. سنؤجل "الطيور" حتى وقت لاحق ، وسنعود إليها بعد ذلك بقليل.

ونحن نرى أن السيارات و الدراجات الناريةمتشابهة جدًا في الوظائف ، لذلك قمنا بإنشاء MotorVehicle من الفئة الأساسية . سيتم توريث السيارة والدراجة النارية من MotorVehicle. وبنفس الطريقة ، نقوم بإنشاء الفئة الأساسية للطائرة ، والتي سننشئ منها فئة الطائرة .

تعتقد أن كل شيء على ما يرام ، ولكن - بام! - تجري أحداث لعبتك في القرن XXX ، ويمكن لبعض السيارات الطيران بالفعل .

لذا ، كان لدينا هفوة. ليس لدى Swift وراثة متعددة. كيف يمكن أن ترث سيارات الطيران الخاصة بك من كل من MotorVehicle و Aircraft؟ إنشاء فئة أساسية أخرى تجمع بين الوظيفتين؟ على الأرجح لا ، حيث لا توجد طريقة واضحة وبسيطة لتحقيق ذلك.

وماذا سينقذ لعبتنا في هذا الوضع الرهيب؟ البرمجة الموجهة نحو البروتوكول في عجلة من أمرها للمساعدة!

ما هي البرمجة الموجهة للبروتوكول؟


تتيح لك البروتوكولات تجميع الطرق والوظائف والخصائص المتشابهة للفئات والهياكل والتعدادات. ومع ذلك ، تسمح لك الفئات فقط باستخدام الوراثة من الفئة الأساسية.

ميزة البروتوكولات في Swift هي أن الكائن يمكن أن يتوافق مع بروتوكولات متعددة.

عند استخدام هذه الطريقة ، يصبح رمزك أكثر نمطيًا. فكر في البروتوكولات باعتبارها لبنات بناء للوظائف. عندما تضيف وظائف جديدة إلى كائن ما ، مما يجعله يتوافق مع بروتوكول معين ، فأنت لا تصنع كائنًا جديدًا تمامًا من البداية ، فقد يكون طويلًا جدًا. بدلاً من ذلك ، يمكنك إضافة كتل بناء مختلفة حتى يصبح الكائن جاهزًا.

الانتقال من فئة أساسية إلى بروتوكولات سيحل مشكلتنا. باستخدام البروتوكولات ، يمكننا إنشاء فئة FlyingCar تتطابق مع كل من MotorVehicle و Aircraft. جميل ، هاه؟

دعنا نفعل الرمز


قم بتشغيل Xcode ، قم بإنشاء ملعب ، احفظه كملف SwiftProtocols.playground ، أضف هذا الرمز:

protocol Bird {
  var name: String { get }
  var canFly: Bool { get }
}

protocol Flyable {
  var airspeedVelocity: Double { get }
}


ترجمة مع Command-Shift-Return للتأكد من أن كل شيء في محله.

نحن هنا تعريف بسيط الطيور البروتوكول ، مع اسم و خصائص canFly . ثم نحدد بروتوكول Flyable مع خاصية airspeedVelocity .

في "عصر ما قبل البروتوكول" ، يبدأ المطور بفئة Flyable كفئة أساسية ، وبعد ذلك ، باستخدام الميراث ، سيحدد الطيور وكل شيء آخر يمكن أن يطير.

ولكن في البرمجة الموجهة للبروتوكول ، يبدأ كل شيء ببروتوكول. تسمح لنا هذه التقنية بتغليف رسم وظيفي بدون فئة أساسية.

كما سترى الآن ، فإن هذا يجعل عملية تصميم النوع أكثر مرونة.

حدد النوع المقابل للبروتوكول


أضف هذا الرمز في أسفل الملعب:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true

  var airspeedVelocity: Double {
    3 * flappyFrequency * flappyAmplitude
  }
}


يحدد هذا الرمز بنية FlappyBird جديدة تتوافق مع كل من بروتوكول Bird وبروتوكول Flyable. خاصية السرعة الجوية هي عمل بتردد القفاز وسعة القفاز. إرجاع خاصية canFly صحيح.

أضف الآن تعريفات لبنيتين إضافيتين:

struct Penguin: Bird {
  let name: String
  let canFly = false
}

struct SwiftBird: Bird, Flyable {
  var name: String { "Swift \(version)" }
  let canFly = true
  let version: Double
  private var speedFactor = 1000.0
  
  init(version: Double) {
    self.version = version
  }

  // Swift is FASTER with each version!
  var airspeedVelocity: Double {
    version * speedFactor
  }
}


البطريق طائر لكنه لا يستطيع الطيران. من الجيد أننا لا نستخدم الميراث ولم نجعل جميع الطيور قابلة للطي !

عند استخدام البروتوكولات ، يمكنك تحديد مكونات الوظيفة وجعل جميع الكائنات المناسبة تتوافق مع البروتوكول.

ثم نحدد SwiftBird ، ولكن في لعبتنا هناك عدة إصدارات مختلفة منه. كلما كان رقم الإصدار أكبر ، زادت سرعة سرعة الطيران ، والتي يتم تعريفها على أنها خاصية محسوبة.

ومع ذلك ، هناك بعض التكرار. يجب أن يحدد كل نوع من الطيور تعريفًا واضحًا لخاصية canFly ، على الرغم من وجود تعريف للبروتوكول القابل للطي. يبدو أننا بحاجة إلى طريقة لتحديد التنفيذ الافتراضي لأساليب البروتوكول. حسنًا ، هناك ملحقات بروتوكول لهذا.

توسيع البروتوكول بالسلوك الافتراضي


تسمح لك امتدادات البروتوكول بتعيين سلوك البروتوكول الافتراضي. اكتب هذا الكود مباشرة بعد تعريف بروتوكول Bird:

extension Bird {
  // Flyable birds can fly!
  var canFly: Bool { self is Flyable }
}


يحدد هذا الرمز امتداد بروتوكول Bird . يحدد هذا الملحق أن خاصية canFly ستعود صحيحة عندما يتوافق النوع مع بروتوكول Flyable . وبعبارة أخرى ، لم يعد أي طائر قابل للطي بحاجة إلى ضبط canFly بشكل صريح.

الآن قم بإزالة let canFly = ... من تعريفات FlappyBird و Penguin و SwiftBird. اجمع الكود وتأكد من أن كل شيء على ما يرام.

دعونا نقوم بالتحويلات


قد تتوافق التعدادات في Swift مع البروتوكولات. يضاف تعريف التعداد التالي:

enum UnladenSwallow: Bird, Flyable {
  case african
  case european
  case unknown
  
  var name: String {
    switch self {
    case .african:
      return "African"
    case .european:
      return "European"
    case .unknown:
      return "What do you mean? African or European?"
    }
  }
  
  var airspeedVelocity: Double {
    switch self {
    case .african:
      return 10.0
    case .european:
      return 9.9
    case .unknown:
      fatalError("You are thrown from the bridge of death!")
    }
  }
}


من خلال تحديد الخصائص المناسبة ، يتوافق UnladenSwallow مع بروتوكولين - Bird و Flyable. بهذه الطريقة ، يتم تطبيق التعريف الافتراضي لـ canFly.

تجاوز السلوك الافتراضي


لدينا نوع UnladenSwallow ، وفقًا لبروتوكول Bird ، تلقّى تلقائيًا تنفيذ canFly . ومع ذلك ، نحن بحاجة إلى UnladenSwallow.unknown لإرجاع false for canFly.

أضف الكود التالي أدناه:

extension UnladenSwallow {
  var canFly: Bool {
    self != .unknown
  }
}

الآن فقط .african و. european سيعودان صحيحين لـ canFly. تحقق من ذلك! أضف الكود التالي أسفل ملعبنا:

UnladenSwallow.unknown.canFly         // false
UnladenSwallow.african.canFly         // true
Penguin(name: "King Penguin").canFly  // false

قم بتجميع الملعب وتحقق من القيم المستلمة مع القيم الموضحة في التعليقات أعلاه.

وبالتالي ، فإننا نعيد تعريف الخصائص والأساليب بنفس طريقة استخدام الأساليب الافتراضية في البرمجة الشيئية.

توسيع البروتوكول


يمكنك أيضًا جعل البروتوكول الخاص بك يتوافق مع بروتوكول آخر من مكتبة Swift القياسية وتحديد السلوك الافتراضي. استبدل تصريح بروتوكول Bird بالكود التالي:

protocol Bird: CustomStringConvertible {
  var name: String { get }
  var canFly: Bool { get }
}

extension CustomStringConvertible where Self: Bird {
  var description: String {
    canFly ? "I can fly" : "Guess I'll just sit here :["
  }
}

يعني الامتثال لبروتوكول CustomStringConvertible أن نوعك يجب أن يحتوي على خاصية وصف. بدلاً من إضافة هذه الخاصية في نوع الطيور وفي جميع مشتقاته ، نحدد ملحق البروتوكول CustomStringConvertible ، والذي سيتم إقرانه بنوع الطيور فقط.

اكتب UnladenSwallow.african في أسفل الملعب. ترجمة وسترى "يمكنني الطيران".

البروتوكولات في مكتبات Swift القياسية


كما ترى ، تعد البروتوكولات طريقة فعالة لتوسيع الأنواع وتخصيصها. في مكتبة Swift القياسية ، يتم استخدام هذه الخاصية أيضًا على نطاق واسع.

أضف هذا الرمز إلى الملعب:

let numbers = [10, 20, 30, 40, 50, 60]
let slice = numbers[1...3]
let reversedSlice = slice.reversed()

let answer = reversedSlice.map { $0 * 10 }
print(answer)

ربما تعرف ما الذي سيخرجه هذا الرمز ، ولكن قد تفاجأ بالأنواع المستخدمة هنا.

على سبيل المثال ، الشريحة ليست Array ، ولكنها ArraySlice. هذا "غلاف" خاص يوفر طريقة فعالة للعمل مع أجزاء من الصفيف. وفقًا لذلك ، تعد الشريحة المعكوسة مجموعة عكسية <ArraySlice>.

لحسن الحظ ، يتم تعريف وظيفة الخريطة على أنها امتداد لبروتوكول Sequence ، الذي يتوافق مع جميع أنواع المجموعات. يتيح لنا هذا تطبيق وظيفة الخريطة على كل من Array و ReversedCollection وعدم ملاحظة الفرق. قريبا سوف تستفيد من هذه الحيلة المفيدة.

على علاماتك


لقد حددنا حتى الآن عدة أنواع تتوافق مع بروتوكول Bird. الآن سنضيف شيئًا مختلفًا تمامًا:

class Motorcycle {
  init(name: String) {
    self.name = name
    speed = 200.0
  }

  var name: String
  var speed: Double
}

هذا النوع لا علاقة له بالطيور والرحلات. نريد ترتيب سباق للدراجات النارية مع طيور البطريق. حان الوقت لبدء هذه الشركة الغريبة.

ضع كل شيء معا


من أجل توحيد مثل هذه المتسابقين بطريقة أو بأخرى ، نحتاج إلى بروتوكول مشترك للسباق. يمكننا القيام بكل هذا دون لمس كل الأنواع التي أنشأناها من قبل ، بمساعدة شيء رائع يسمى النمذجة بأثر رجعي. فقط أضف هذا إلى الملعب:

// 1
protocol Racer {
  var speed: Double { get }  // speed is the only thing racers care about
}

// 2
extension FlappyBird: Racer {
  var speed: Double {
    airspeedVelocity
  }
}

extension SwiftBird: Racer {
  var speed: Double {
    airspeedVelocity
  }
}

extension Penguin: Racer {
  var speed: Double {
    42  // full waddle speed
  }
}

extension UnladenSwallow: Racer {
  var speed: Double {
    canFly ? airspeedVelocity : 0.0
  }
}

extension Motorcycle: Racer {}

// 3
let racers: [Racer] =
  [UnladenSwallow.african,
   UnladenSwallow.european,
   UnladenSwallow.unknown,
   Penguin(name: "King Penguin"),
   SwiftBird(version: 5.1),
   FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0),
   Motorcycle(name: "Giacomo")]

إليك ما نقوم به هنا: أولاً ، حدد بروتوكول Racer. هذا كل ما يمكن أن يشارك في السباقات. ثم نلقي جميع أنواعنا التي تم إنشاؤها سابقًا على بروتوكول Racer. وأخيرًا ، نقوم بإنشاء مصفوفة تحتوي على مثيلات لكل نوع من أنواعنا.

قم بتجميع الملعب بحيث يكون كل شيء في محله.

السرعة القصوى


نكتب وظيفة لتحديد السرعة القصوى للدراجين. أضف هذا الرمز في نهاية الملعب:

func topSpeed(of racers: [Racer]) -> Double {
  racers.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
}

topSpeed(of: racers) // 5100

هنا نستخدم الدالة max للعثور على الفارس بأقصى سرعة وإعادته. إذا كان الصفيف فارغًا ، فسيتم إرجاع 0.0.

جعل الوظيفة أكثر عمومية


افترض أن مصفوفة المتسابقين كبيرة بما يكفي ، ونحن بحاجة إلى العثور على السرعة القصوى ليس في الصفيف بأكمله ، ولكن في جزء منه. الحل هو تغيير topSpeed ​​(من :) بحيث يأخذ كوسيطة ليست صفيفًا على وجه التحديد ، ولكن كل شيء يتوافق مع بروتوكول Sequence.

استبدال تنفيذنا لـ topSpeed ​​(:) :) على النحو التالي:

// 1
func topSpeed<RacersType: Sequence>(of racers: RacersType) -> Double
    /*2*/ where RacersType.Iterator.Element == Racer {
  // 3
  racers.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
}

  1. RacersType هو نوع الوسيطة العامة لوظيفتنا. يمكن أن يكون أي شيء يتوافق مع بروتوكول التسلسل.
  2. حيث يحدد أن محتوى Sequence يجب أن يتوافق مع بروتوكول Racer.
  3. يبقى جسم الوظيفة نفسها دون تغيير.

تحقق من خلال إضافة هذا في نهاية ملعبنا:

topSpeed(of: racers[1...3]) // 42

تعمل وظيفتنا الآن مع أي نوع يتوافق مع بروتوكول Sequence ، بما في ذلك ArraySlice.

جعل الوظيفة أكثر "سرعة"


السر: يمكنك القيام بعمل أفضل. أضف هذا في الأسفل:

extension Sequence where Iterator.Element == Racer {
  func topSpeed() -> Double {
    self.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
  }
}

racers.topSpeed()        // 5100
racers[1...3].topSpeed() // 42

والآن قمنا بتوسيع بروتوكول Sequence نفسه باستخدام topSpeed ​​(). يتم تطبيقه فقط عندما يحتوي Sequence على نوع Racer.

مقارنات البروتوكول


ميزة أخرى لبروتوكولات Swift هي كيفية تحديد عوامل المساواة للكائنات أو مقارنتها. نكتب ما يلي:

protocol Score {
  var value: Int { get }
}

struct RacingScore: Score {
  let value: Int
}

باستخدام بروتوكول النقاط ، يمكنك كتابة التعليمات البرمجية التي تعامل جميع العناصر من هذا النوع بطريقة واحدة. ولكن إذا حصلت على نوع محدد للغاية ، مثل RacingScore ، فلن تخلط بينه وبين مشتقات أخرى من بروتوكول النقاط.

نريد مقارنة النتائج لمعرفة من لديه أعلى الدرجات. قبل Swift 3 ، كان على المطورين كتابة وظائف عالمية لتحديد عامل تشغيل للبروتوكول. الآن يمكننا تحديد هذه الأساليب الثابتة في النموذج نفسه. نقوم بذلك عن طريق استبدال تعريفات نقاط و RacingScore على النحو التالي:

protocol Score: Comparable {
  var value: Int { get }
}

struct RacingScore: Score {
  let value: Int
  
  static func <(lhs: RacingScore, rhs: RacingScore) -> Bool {
    lhs.value < rhs.value
  }
}

لدينا كل منطق RacingScore في مكان واحد. يتطلب البروتوكول القابل للمقارنة تحديد تنفيذ فقط للوظيفة الأقل. سيتم تنفيذ جميع وظائف المقارنة الأخرى تلقائيًا ، بناءً على تنفيذ وظيفة "أقل من" التي أنشأناها.

اختبارات:

RacingScore(value: 150) >= RacingScore(value: 130) // true

إجراء تغييرات على الكائن


حتى الآن ، أظهر كل مثال كيفية إضافة وظائف. ولكن ماذا لو أردنا عمل بروتوكول يغير شيئًا في كائن ما؟ يمكن القيام بذلك باستخدام طرق متحولة في بروتوكولنا.

إضافة بروتوكول جديد:

protocol Cheat {
  mutating func boost(_ power: Double)
}

هنا نحدد البروتوكول الذي يمكننا من الغش. كيف؟ تغيير محتويات التعزيز بشكل تعسفي.

الآن قم بإنشاء ملحق SwiftBird الذي يتوافق مع بروتوكول Cheat:

extension SwiftBird: Cheat {
  mutating func boost(_ power: Double) {
    speedFactor += power
  }
}

هنا نقوم بتطبيق وظيفة التعزيز (_ :) ، مما يزيد من السرعة من خلال القيمة المرسلة. و الكلمة تحور يجعل هيكل نفهم أن واحدة من قيمها سيتم تغيير من قبل هذه الوظيفة.

تحقق من ذلك!
var swiftBird = SwiftBird(version: 5.0)
swiftBird.boost(3.0)
swiftBird.airspeedVelocity // 5015
swiftBird.boost(3.0)
swiftBird.airspeedVelocity // 5030

استنتاج


هنا يمكنك تنزيل كود المصدر الكامل للملعب.

لقد تعلمت عن إمكانيات البرمجة الموجهة للبروتوكول من خلال إنشاء بروتوكولات بسيطة وزيادة قدراتها بمساعدة الإضافات. باستخدام التطبيق الافتراضي ، يمكنك إعطاء البروتوكولات "السلوك" المناسب. تقريبًا كما هو الحال مع الفئات الأساسية ، ولكن أفضل فقط ، لأن كل هذا ينطبق أيضًا على الهياكل والتعداد.

لقد رأيت أيضًا أن ملحق البروتوكول ينطبق على بروتوكولات Swift الأساسية.

هنا سوف تجد دليل البروتوكول الرسمي .

يمكنك أيضًا مشاهدة محاضرة WWDC ممتازة حول البرمجة الموجهة للبروتوكول.

كما هو الحال مع أي نموذج برمجة ، هناك خطر من الابتعاد والبدء في استخدام البروتوكولات اليسار واليمين. فيما يلي ملاحظة مثيرة للاهتمام حول مخاطر قرارات نمط الرصاصة الفضية.

Source: https://habr.com/ru/post/undefined/


All Articles