نمط المحول
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 92 من 107.
يسمح نمط Adapter للواجهات غير المتوافقة بالعمل معًا من خلال تغليف نوع موجود بواجهة جديدة. بينما يقوم Command بتغليف الإجراءات، يترجم Adapter واجهة إلى واجهة أخرى يتوقعها العملاء.
تخيّل أن لديك طابعة legacy تستخدم توقيع method مختلفًا عمّا تتوقعه application الخاصة بك:
// الواجهة المستهدفة التي يستخدمها تطبيقك
type Printer interface {
Print(text string) string
}
// طابعة قديمة بواجهة غير متوافقة
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}يغلّف adapter النوع legacy وينفّذ الواجهة المتوقعة:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}الآن يعمل legacy printer بسلاسة مع code يتوقع واجهة interface Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"يُعد نمط Adapter ذا قيمة كبيرة عند دمج مكتبات من جهات خارجية، أو العمل مع تعليمات برمجية legacy، أو عندما تحتاج إلى استخدام فئات موجودة لا تتوافق مع متطلبات الواجهة لديك. فهو يعمل كجسر من دون تعديل التعليمات البرمجية الأصلية.
التحدي
سهللنَبْنِ نظامًا لمشغّل وسائط باستخدام نمط Adapter! لديك مشغّل audio حديث يتوقّع واجهة موحّدة، لكنك تحتاج إلى دمج معدّات audio قديمة تستخدم توقيعات method مختلفة تمامًا. سيعمل adapter الخاص بك على سدّ هذه الفجوة، مما يسمح للمعدّات القديمة بالعمل بسلاسة مع النظام الجديد.
ستنظّم التعليمات البرمجية الخاصة بك عبر ثلاثة ملفات:
player.go: عرّف واجهة الهدف وجهازaudioالقديم.أنشئ واجهة
AudioPlayerالتي يتوقّعها نظامك الحديث، معmethodباسمPlay(track string) stringيأخذ اسم مقطوعة ويعيد رسالة تشغيل.عرّف أيضًا جهازًا قديمًا لا يتطابق مع هذه الواجهة:
- بنية
VinylPlayerالتي تحتوي على حقلSpeedRPM(من النوعint). ولديهاmethodباسمDropNeedle(record string, side string) stringيعيدPlaying [record] ([side] side) at [speed]rpm
واجهة
VinylPlayerغير متوافقة معAudioPlayer. فهي تتطلّب معاملين وتستخدم اسمmethodمختلفًا تمامًا.- بنية
adapter.go: أنشئadapterالخاص بك لجعل الجهاز القديم متوافقًا.أنشئ بنية
VinylAdapterتغلّفVinylPlayerوتطبّق واجهةAudioPlayer. عند استدعاءPlay، يجب أن يستدعيadapterDropNeedleالخاص بمشغّل الأسطوانات، مع استخدامAتلقائيًا لمعامل الجانب.أنشئ أيضًا مشغّلًا حديثًا للمقارنة:
- بنية
DigitalPlayer: يعيدmethodالخاص بهاPlayالقيمةStreaming: [track]
- بنية
main.go: اعرض عمل كلا المشغّلين من خلال الواجهة نفسها.أنشئ دالة
PlayMusic(player AudioPlayer, track string) stringتستخدم الواجهة لتشغيلmusic. يجب ألا تعرف هذه الدالة أو تهتم بما إذا كانت تستخدم مشغّلًا رقميًا أم مشغّل أسطوانات مُكيَّفًا.اقرأ نوع المشغّل (
digitalأوvinyl)، وإذا كانvinyl، فاقرأ أيضًا سرعة RPM. ثم اقرأ اسم المقطوعة. أنشئ المشغّل المناسب (باستخدامadapterمعvinyl)، وشغّل المقطوعة من خلال دالةPlayMusic، واطبع النتيجة.
ستُ provided المدخلات التالية:
- السطر 1: نوع المشغّل (
digitalأوvinyl) - السطر 2: سرعة RPM (فقط إذا كان
vinyl) - السطر الأخير: اسم المقطوعة المراد تشغيلها
على سبيل المثال، عند إعطاء:
digital
Bohemian Rhapsodyيجب أن يكون الناتج:
Streaming: Bohemian Rhapsodyوعند إعطاء:
vinyl
33
Abbey Roadيجب أن يكون الناتج:
Playing Abbey Road (A side) at 33rpmوعند إعطاء:
vinyl
45
Blue Mondayيجب أن يكون الناتج:
Playing Blue Monday (A side) at 45rpmلاحظ كيف تعمل دالة PlayMusic بالطريقة نفسها مع كلا المشغّلين. فهي لا تعرف أن مشغّل الأسطوانات مُكيَّف فعليًا من واجهة مختلفة تمامًا. يترجم adapter استدعاءات الواجهة الحديثة إلى التنسيق القديم في الخلفية!
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic يستخدم واجهة AudioPlayer لتشغيل الموسيقى
// تعمل هذه الدالة مع أي تنفيذ لـ AudioPlayer
// TODO: نفّذ هذه الدالة لاستدعاء player.Play(track) وإرجاع النتيجة
func PlayMusic(player AudioPlayer, track string) string {
// TODO: نفّذ هذه الدالة
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// اقرأ نوع المشغّل
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// اقرأ سرعة RPM للفينيل
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// اقرأ اسم المقطع
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: أنشئ VinylPlayer بالـ RPM المعطى
// TODO: غلّفه في VinylAdapter
// TODO: عيّنه لمتغير player
_ = rpm // احذف هذا السطر عندما تستخدم rpm
} else {
// اقرأ اسم المقطع
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: أنشئ DigitalPlayer
// TODO: عيّنه لمتغير player
}
// TODO: استدعِ PlayMusic واطبع النتيجة
_ = player // احذف هذا السطر عندما تستخدم player
_ = track // احذف هذا السطر عندما تستخدم track
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Poolتدرّب بنفسك: مترجم Go عبر الإنترنت