إخفاء المعلومات في Go
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 50 من 107.
يتجاوز إخفاء المعلومات مجرد جعل fields غير مُصدَّرة. إنه يتعلق بتصميم أنواعك بحيث لا تعرف الشيفرة الخارجية سوى ما تحتاج إلى معرفته. لا شيء أكثر من ذلك.
الهدف هو إتاحة السلوك من خلال الأساليب مع إبقاء البنية الداخلية مخفية تمامًا. Consider هذا النهج:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
لا تعرف الشيفرة الخارجية أن Counter تستخدم int داخليًا. قد تكون float64، أو شريحة، أو شيئًا آخر تمامًا. يمكن أن تتغير implementation دون التأثير في أي شيفرة تستخدم Counter، ما دامت الأساليب تتصرف بالطريقة نفسها.
ينطبق هذا المبدأ أيضًا على helper functions. أبقِ internal logic غير مُصدَّر:
// مُصدَّر - جزء من واجهة برمجة التطبيقات العامة
func (c *Counter) Reset() {
c.value = 0
}
// غير مُصدَّر - مساعد داخلي
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
من خلال إخفاء isAtLimit()، تصبح حرًّا في تغييره أو إزالته لاحقًا. ينشئ إخفاء المعلومات حدًّا واضحًا بين ما يعد به الحِزْمَة للمستخدمين الخارجيين وكيفية عملها داخليًا.
التحدي
سهللنبنِ نظام محفظة آمنة يوضّح إخفاء المعلومات. ستنشئ محفظة digital حيث يتم إخفاء تتبّع balance الداخلي وسجل المعاملات بالكامل عن الكود الخارجي: لا يتم كشف سوى السلوك من خلال الأساليب.
ستنظّم الكود الخاص بك عبر ملفين:
wallet.go: أنشئ بنيةWalletتخفي كل حالتها الداخلية. يجب أن تتتبّع المحفظة قيمة balance الحالية وحدّ الإنفاق داخليًا، لكن يجب ألا يعرف الكود الخارجي أبدًا كيفية تخزين هذه القيم أو تمثيلها. اكشف السلوك من خلال هذه الأساليب:NewWallet(limit float64) *Wallet- constructor ينشئ محفظة باستخدام حدّ الإنفاق given وbalance يساوي صفرًاDeposit(amount float64) bool- يضيف المال إلى المحفظة، ويُرجعtrueإذا نجحت العملية (يجب أن تكون amount موجبة)Spend(amount float64) bool- يزيل المال إذا كان balance كافيًا AND لم تتجاوز amount حدّ الإنفاق، ويُرجعtrueإذا نجحت العمليةBalance() float64- يُرجع قيمة balance الحاليةStatus() string- يُرجع رسالة حالة بالتنسيق:Balance: $[balance] (Limit: $[limit])
main.go: اقرأ إعدادات المحفظة والمعاملات من inputs، وأنشئ محفظة، ونفّذ العمليات، واعرض النتائج بعد كل إجراء.
سيتم توفير inputs التالية:
- السطر 1: Limit الإنفاق
- السطر 2: Deposit amount
- السطر 3: First spend amount
- السطر 4: Second spend amount
بعد إنشاء المحفظة، اطبع حالتها initial. ثم نفّذ كل عملية واطبع إما الحالة المحدّثة (إذا نجحت) أو Transaction failed (إذا تم رفض العملية). نسّق جميع المبالغ بالدولار باستخدام منزلتين عشريتين.
على سبيل المثال، given 50 و100 و30 و80، يجب أن يكون الناتج:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedتفشل المعاملة الأخيرة لأن $80 تتجاوز حدّ الإنفاق البالغ $50، رغم وجود balance كافٍ. تفرض المحفظة قواعدها داخليًا دون كشف كيفية اتخاذها لهذه القرارات. يستدعي الكود الخارجي الأساليب ببساطة ويتلقى النتائج، دون أن يعرف أبدًا تفاصيل implementation الداخلية.
جرّب بنفسك
package main
import (
"fmt"
)
func main() {
// اقرأ المدخلات
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: أنشئ محفظة جديدة بحد الإنفاق المعطى
// TODO: اطبع الحالة الأولية
// TODO: Perform deposit and print result (status or "Transaction failed")
// TODO: نفّذ الإنفاق الأول واطبع النتيجة
// TODO: نفّذ الإنفاق الثاني واطبع النتيجة
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت