Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط المحول

جزء من قسم البرمجة كائنية التوجه في رحلة 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، أو عندما تحتاج إلى استخدام فئات موجودة لا تتوافق مع متطلبات الواجهة لديك. فهو يعمل كجسر من دون تعديل التعليمات البرمجية الأصلية.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لمشغّل وسائط باستخدام نمط 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، يجب أن يستدعي adapter DropNeedle الخاص بمشغّل الأسطوانات، مع استخدام 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
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت