يُعد بناء تجريدات abstractions لتفاصيل البرنامج الخاص بك أعظم أداة يمكن أن تقدمها لغة البرمجة للمطور، إذ تسمح البُنى structs لمطوري لغة جو بوصف العالم الذي يعمل فيه البرنامج؛ فبدلًا من استخدام السلاسل النصية strings لوصف أشياء، مثل الشارع Street والمدينة City والرمز البريدي PostalCode، يمكن استخدام بنية تجمع كل هذه الأشياء تحت مفهوم "العنوان Address".
يمكن تعريف البنية على أنها هيكل بيانات يُستخدم لتعريف نوع بيانات جديد يحتوي مجموعةً محددةً من القيم مختلفة النوع، ويمكن الوصول لهذه العناصر أو القيم عن طريق اسمها. تساعد البنى المطورين المستقبليين (بما في ذلك نحن) بتحديد البيانات المهمة للبرامج الخاصة بنا وكيف يجب أن تستخدم الشيفرات المستقبلية هذه البيانات بالطريقة الصحيحة.
يمكن تعريف البنى واستخدامها بعدة طرق مختلفة. سنلقي نظرةً في هذا المقال على كل من هذه التقنيات.
تعريف البنى
يمكنك أن تتخيل البنى مثل نماذج جوجل التي يُطلب منك ملؤها أحيانًا. تتضمّن هذه النماذج حقولًا يُطلب منك تعبئتها، مثل الاسم، أو العنوان، أو البريد الإلكتروني، أو مربعات فارغة يمكن تحديد إحداها لوصف حالتك الزوجية (أعزب، متزوج، أرمل) …إلخ. يمكن أن تتضمن البُنى أيضًا حقولًا يمكنك تعبئتها. تهيئة متغير ببنية جديدة، أشبه باستخراج نسخة من نموذج جاهز للتعبئة.
لإنشاء بنية جديدة يجب أولاً إعطاء لغة جو مُخططًا يصف الحقول التي تحتوي عليها البنية. يبدأ تعريف البنية بالكلمة المفتاحية type
متبوعًا باسم البنية الذي تختاره Creature
ثم الكلمة struct
ثم قوسين {}
نضع بداخلهما الحقول التي نريدها في البنية. يمكنك استخدام البنية بعد الانتهاء من التصريح عنها مع المتغيرات كما لو أنها نوع بيانات بحد ذاته.
package main import "fmt" type Creature struct { Name string } func main() { c := Creature{ Name: "Sammy the Shark", } fmt.Println(c.Name) }
ستحصل عند تشغيل البرنامج على الخرج التالي:
Sammy the Shark
صرّحنا في هذا المثال أولًا عن بنية اسمها Creature
تتضمّن حقلًا اسمه Name
من نوع سلسلة نصية string
. نُعرّف داخل الدالة main
مُتغيرًا c
من النوع Creature
ونهيئ الحقل Name
فيه بالقيمة "Sammy the Shark"، إذ نفتح قوسين {}
ونضع هذه المعلومات بينهما كما في الشيفرة أعلاه. أخيرًا استدعينا الدالة fmt.Println
وطبعنا من خلالها الحقل Name
من خلال المتغير c
وذلك عن طريق وضع اسم المتغير متبوعًا بنقطة .
ومتبوعًا باسم الحقل، مثل c.Name
، الذي يعيد في هذه الحالة حقل Name
.
عندما نأخذ نسخةً من بنية، نذكر غالبًا اسم كل حقل ونسند له قيمة (كما فعلنا في المثال السابق). عمومًا، إذا كنت ستؤمن قيمة كل حقل أثناء تعريف نسخة من بنية فيمكنك عندها تجاهل كتابة أسماء الحقول، كما يلي:
package main import "fmt" type Creature struct { Name string Type string } func main() { c := Creature{"Sammy", "Shark"} fmt.Println(c.Name, "the", c.Type) }
وسيكون الخرج على النحو التالي:
Sammy the Shark
أضفنا حقلًا جديدًا للبنية Creature
باسم Type
وحددنا نوعه string
. أنشأنا داخل الدالة main
نسخةً من هذه البنية وهيّأنا قيم الحقلين Name
و Type
بالقيمتين "Sammy" و "shark" على التوالي من خلال التعليمة:
Creature{"Sammy", "Shark"}
واستغنينا عن كتابة أسماء الحقول صراحةً.
نُسمي هذه الطريقة بالطريقة المُختصرة للتصريح عن نسخة من بنية، وهذه الطريقة غير شائعة كثيرًا لأنها تتضمن عيوبًا مثل ضرورة تحديد قيم لجميع الحقول وبالترتيب وعدم نسيان أي حقل. نستنتج سريعًا أن استخدام هذه الطريقة لا يكون جيدًا عندما يكون لدينا عددٌ كبيرٌ من الحقول لأننا سنكون عُرضةً للخطأ والنسيان والتشتت عندما نقرأ الشيفرة مرةً أخرى. إذًا، يُفضّل استخدام هذه الطريقة فقط عندما يكون عدد الحقول قليل.
ربما لاحظت أننا نبدأ أسماء جميع الحقول بحرف كبير، وهذا مهم جدًا لأنه يلعب دورًا في تحديد إمكانية الوصول إلى هذه الحقول؛ فعندما نبدأ اسم الحقل بحرف كبير، فهذا يعني إمكانية الوصول إليه من خارج الحزمة، أما الحرف الصغير فلا يمكن الوصول إليها من خارج الحزمة.
تصدير حقول البنية
يعتمد تصدير حقول البنية على نفس قواعد تصدير المكونات الأخرى في لغة جو؛ فإذا بدأ اسم الحقل بحرف كبير، فسيكون قابلاً للقراءة والتعديل بواسطة شيفرة من خارج الحزمة التي صُرّح عنه فيها؛ أما إذا بدأ الحقل بحرف صغير، فلن تتمكن من قراءة وتعديل هذا الحقل إلا من شيفرة من داخل الحزمة التي صُرّح عنه فيها. يوضّح المثال التالي الأمر:
package main import "fmt" type Creature struct { Name string Type string password string } func main() { c := Creature{ Name: "Sammy", Type: "Shark", password: "secret", } fmt.Println(c.Name, "the", c.Type) fmt.Println("Password is", c.password) }
وسيكون الخرج على النحو التالي:
Sammy the Shark Password is secret
أضفنا إلى البنية السابقة حقلًا جديدًا secret
، وهو حقل من النوع string
ويبدأ بحرف صغير؛ أي أنه غير مُصدّر، وأي حزمة أخرى تحاول إنشاء نسخة من هذه البنية Creature
لن تتمكن من الوصول إلى حقل secret
. عمومًا، يمكننا الوصول إلى هذا الحقل ضمن نطاق الحزمة، لذا إذا حاولنا الوصول إلى هذا الحقل من داخل الدالة main
والتي بدورها موجودة ضمن نفس الحزمة بالتأكيد، فيمكننا الرجوع لهذا الحقل c.password
والحصول على القيمة المُخزنة فيه. وجود حقول غير مُصدّرة أمر شائع في البنى مع إمكانية وصول بواسطة توابع مُصدّرة exported.
البنى المضمنة Inline
تُسمى أيضًا البنى السريعة. تُمكّنك لغة جو من تعريف بُنى في أي وقت تريده وفي أي مكان ودون الحاجة للتصريح عنها على أنها نوع بيانات جديد بحد ذاته، وهذا مفيد في الحالات التي تكون فيها بحاجة إلى استخدام بنية مرةً واحدةً فقط (أي لن تحتاج إلى إنشاء أكثر من نسخة)، فمثلًا، تستخدم الاختبارات غالبًا بنيةً لتعريف جميع المعاملات التي تُشكل حالة اختبار معينة. سيكون ابتكار أسماء جديدة مثل CreatureNamePrintingTestCase
مرهقًا لدى استخدام هذه البنية في مكان واحد فقط.
يكون كتابة البنى المُضمّنة بنفس طريقة كتابة البنى العادية تقريبًا، إذ نكتب الكلمة المفتاحية struct
متبوعةً بقوسين {}
نضع بينهما الحقول وعلى يمين اسم المتغير. يجب أيضًا وضع قيم لهذه الحقول مباشرةً من خلال استخدام قوسين آخرين {}
كما هو موضح في الشيفرة التالية:
package main import "fmt" func main() { c := struct { Name string Type string }{ Name: "Sammy", Type: "Shark", } fmt.Println(c.Name, "the", c.Type) }
ويكون الخرج كما يلي:
Sammy the Shark
لاحظ هنا أننا لم نحتاج إلى تعريف نوع بيانات جديد لتمثيل البنية، وبالتالي لم نكتب الكلمة المفتاحية type
، فكل ما نحتاجه هنا هو استخدام الكلمة المفتاحية struct
إشارةً إلى البنية، وإلى معامل الإسناد القصير =:
. نحتاج أيضًا إلى تعريف قيم الحقول مباشرةً كما فعلنا في الشيفرة أعلاه. الآن أصبح لدينا متغيرًا اسمه c
يُمثّل بنيةً يمكن الوصول لحقولها من خلال النقطة .
كما هو معتاد. سترى البنى المُضمّنة غالبًا في الاختبارات، إذ تُعرّف البنى الفردية بصورةٍ متكررة لاحتواء البيانات والتوقعات لحالة اختبار معينة.
خاتمة
البُنى هي كتل بيانات غير متجانسة (أي أنها تضم حقولًا أو عناصر من أنواع بيانات مختلفة) يعرّفها المبرمجون لتنظيم المعلومات. تتعامل معظم البرامج مع أحجام هائلة من البيانات، وبدون البنى سيكون من الصعب تذكر أي من المتغيرات ترتبط معًا وأيّها غير مرتبطة أو أيّها من نوع string
وأيها من نوع int
. لذلك إذا كنت تتعامل مع مجموعة من المتغيرات، اسأل نفسك عما إذا كان تجميعها ضمن بنية سيكون أفضل، إذ من الممكن أن تصف هذه المتغيرات مفهومًا عالي المستوى، فيمكن مثلًا أن يشير أحد المتغيرات إلى عنوان شركة حسوب وهناك متغيّر آخر يخص عنوان شركة أُخرى.
ترجمة -وبتصرف- للمقال Defining Structs in Go لصاحبه Gopher Guides.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.