الوحدات والأصناف في لغة روبي
الوحدات
تخدم الوحدات (Modules) غايتين اثنتين في لغة روبي وهما: ميّزة نطاقات الأسماء (namespace)، والخلط الضمني (mix-in) التي سنوضّحها لاحقًا.
يستخدم نطاق الأسماء لتنظيم الشيفرة البرمجية ضمن مجموعات مستقلّة تمنع تداخل التوابع والمتغيّرات ذات الأسماء المتشابهة فيما بينها. فعلى سبيل المثال، نطاق الأسماء IRB يوفّر عمليات irb والتي تمنع التصادم مع الاسم الشائع "Context".
وظيفة الخلط الضمني (Mix-in) تسمح بمشاركة توابع مشتركة عبر عدد من الأصناف أو الوحدات، إذ يأتي مع لغة روبي على سبيل المثال الوحدة Enumerable
التي توفّر توابع حساب (enumeration) مبنية على التابع each
. كما تسمح الوحدة Comparable
بموازنة الكائنات بناء على تابع الموازنة <=>
.
لاحظ وجود شبه كبير بين الوحدات والأصناف، بالإضافة إلى إمكانية الخلط الضمني داخل الوحدة، فوصف الوحدات أدناه يمكن تطبيقه كذلك على الأصناف.
تعريف الوحدة
تُنشأ الوحدة باستخدام الكلمة المحجوزة module
:
module MyModule
# ...
end
يمكن إعادة فتح الوحدة عددًا غير محدود من المرات لإضافة أو تعديل أو حذف وظائف منها.
module MyModule
def my_method
end
end
module MyModule
alias my_alias my_method
end
module MyModule
remove_method :my_method
end
تُعدّ إعادة فتح الأصناف ميّزة فعّالة جدًا في لغة روبي، لكنّ الاستخدام الأمثل لها يكمن في إعادة فتح أصنافك الخاصّة، لأنّ فتح أصناف أخرى لا تمتلكها أنت قد يؤدي إلى تضاربات أو صعوبة في تتبع الأخطاء.
التشعب
يمكن تعريف الوحدات داخل بعضها بشكل متشعب (nested):
module Outer
module Inner
end
end
كثير من الحزم في لغة روبي تعرّف وحدةً أو صنفًا خارجيًّا ليؤمّن نطاق أسماء خاصّ بالعمليات الخاصة بهذه الحزمة.
كما بإمكانك تعريف الوحدات الضمنية باستخدام ::
على أن يكون الصنف أو الوحدة الكبرى معرّفة مسبقًا:
module Outer::Inner::GrandChild
end
لاحظ أنّ هذا المثال سيسبب الخطأ NameError
إذا لم تكن الوحدة Outer
والوحدة الضمنية Outer::Inner
معرفتان.
هذا الأسلوب يفيد المؤلّف في تخفيض عدد المسافات البادئة للشيفرة البرمجية من ثلاثة مستويات إلى مستوى واحد. إلا أنّ نطاق البحث عن المتغيّرات يختلف عند إنشاء نطاق أسماء باستخدام الصيغة الأساسية بدلًا من الصيغة المختصرة.
النطاق
الكائن الذاتي self
يشير self
إلى الكائن الذي يعرّف النطاق الحاليّ، ويتغيّر هذا الكائن عند دخول تابع مختلف أو عند تعريف وحدة جديدة.
الثوابت
الثوابت التي يمكن الوصول إليها تختلف تبعًا لصيغة التعريف الضمنيّ للوحدات. ففي المثال التالي، يعدّ الثابت A::Z
قابلًا للوصول من B
على فرض أنّ A
هو جزء من عملية التضمين:
module A
Z = 1
module B
p Module.nesting #=> [A::B, A]
p Z #=> 1
end
end
إلا أنّك إذا استخدمت الرمز ::
لتعريف الوحدة الضمنية A::B
بدون كتابتها داخل الوحدة A
، فسيظهر خطأ من النوع NameError
بسبب كون التضمين لا يحوي الوحدة A
.
module A
Z = 1
end
module A::B
p Module.nesting #=> [A::B]
p Z #=> يسبب NameError
end
وإذا عُرّف الثابت في نطاق خارج جميع الوحدات، فبإمكانك حينها كتابته مسبوقًا بالنقطتين ::
للإشارة إليه:
Z = 0
module A
Z = 1
module B
p ::Z #=> 0
end
end
التوابع
راجع صفحة التوثيق الخاصة بصيغة التوابع لتتعرف على صيغة تعريفها.
يمكن استدعاء توابع الأصناف مباشرة، ولعلّك تجد ذلك غريبًا، لكنّ التابع الخاص بالوحدة يطلق عليه تابع الصنف (class method) بدلًا من تابع الوحدة (module method)، انظر أيضًا صفحة التوثيق الخاصّة بالوحدات في لغة روبي إذ بإمكانك تحويل تابع نسخة (instance method) معرّف في وحدة إلى تابع صنف.
عندما يشير تابع الصنف إلى ثابت، ستُطبق حينها ذات القواعد عند الإشارة إليه خارج التابع، إذ أنّ النطاق نفسه في الحالتين.
تُستدعى توابع النسخة المعرّفة في الوحدات عند تضمينها حصرًا. ويكون لها وصول إلى الثوابت المعرّفة عند تضمينها في شجرة الآباء:
module A
Z = 1
def z
Z
end
end
include A
p self.class.ancestors #=> [Object, A, Kernel, BasicObject]
p z #=> 1
المرئية
توفّر لغة روبي ثلاثة مستويات من المرئية (Visibility). المستوى الافتراضي هو المرئية العامّة (Public). فالتابع العام يمكن استدعاؤه من أيّ كائن آخر.
المستوى الثاني للمرئية هو المرئية المحمية (protected)، ففي حالة استدعاء التابع المحمي، يجب أن يكون المرسل صنفًا فرعيًا من المستقبل أو أن يكون المستقبل صنفًا فرعيًّا من المرسل؛ عدا ذلك، سيظهر استثناء من النوع NoMethodError
.
كثيرًا ما تستخدم المرئيّة المحميّة لتعريف معامل ==
وتوابع المقارنة الأخرى حيث لا يرغب المؤلّف بإتاحة حالة الكائنات لكلّ من يرغب باستدعائها، بل يحصر ذلك في الأصناف الفرعية. إليك مثالًا عن ذلك:
class A
def n(other)
other.m
end
end
class B < A
def m
1
end
protected :m
end
class C < B
end
a = A.new
b = B.new
c = C.new
c.n b #=> 1 -- C صنف فرعيّ من B
b.n b #=> 1 -- mاستدعي في الصنف المعرَّف
a.n b # NoMethodError A is not a subclass of B يظهر الخطأ
المستوى الثالث للمرئية هو المرئية الخاصّة (private). فالتوابع الخاصة لا يمكن استدعاؤها عن طريق مستقبل ولا حتى self
. وإذا استدعيت بهذه الطريقة فسيرمى الاستثناء NoMethodError
.
الأسماء البديلة وإلغاء التعريف
يمكنك إنشاء أسماء بديلة للتوابع أو إلغاء تعريفها، إلا أنّ هذه العمليات ليست خاصّة بالوحدات أو الأصناف. راجع توثيق الصيغ المنوَّعة في لغة روبي لمزيد من التفاصيل.
الأصناف
الأصناف هي وحدات إلا أنّها لا يمكن استخدامها للخلط الضمنيّ (mix-in) ضمن وحدات أو أصناف أخرى. ويمكن استخدام الأصناف كالوحدات في حجز نطاق الأسماء، كما يمكن للصنف أن يرث التوابع والثوابت من الأصناف الأكبر.
تعريف الصنف
استخدم الكلمة class
لتعريف الصنف:
class MyClass
# ...
end
إذا لم تكتب الصنف الأب فسيرث هذا الصنف من Object
. ويمكنك أن تحدد الصنف الأب باستخدام >
متبوعًا باسم الصنف الأب:
class MySubclass < MyClass
# ...
end
يوجد في لغة روبي صنفًا خاصًّا اسمه BasicObject
صُمّم لأن يكون صنفًا فارغًا ويتضمن حدًّا أدنى من التوابع المعرّفة مسبقًا، فبإمكانك استخدام هذا الصنف لتنشئ هيكلية وراثة مستقلة. انظر صفحة التوثيق الخاصّة بالصنف BasicObject
لمزيد من التفاصيل.
الوراثة
كل تابع معرّف في صنف أب معيّن يمكن استدعاؤه من الأصناف الفرعية:
class A
Z = 1
def z
Z
end
end
class B < A
end
p B.new.z #=> 1
والأمر نفسه يطبّق على الثوابت:
class A
Z = 1
end
class B < A
def z
Z
end
end
p B.new.z #=> 1
كما بإمكانك التعديل على وظيفة موجودة في الصنف الأب عن طريق إعادة تعريف التابع في الصنف الفرعي:
class A
def m
1
end
end
class B < A
def m
2
end
end
p B.new.m #=> 2
إذا أردت طلب وظيفة يقوم بها التابع المعرّف في الصنف الأب، فاستخدم كلمة super:
class A
def m
1
end
end
class B < A
def m
2 + super
end
end
p B.new.m #=> 3
إذا كتبت super
بدون أيّ معاملات فستأخذ المعاملات التي مُرّرت إلى التابع في الصنف الفرعي. أمّا لم ترد إرسال أيّ وسائط، فاكتب ()super
. وإذا أردت إرسال وسائط محدّدة، فاكتبها يدويًّا مثل (super(2
. وليس هناك قيد على عدد المرات التي يمكنك استدعاء super
بها، بل استخدمها كلما أردت.
الصنف المنفرد
الصنف المنفرد (Singleton، ويعرف أيضًا بالصنف المجرّد [metaclass] أو الصنف الخاص [eigenclass]) هو الصنف الذي يحوي التوابع الخاصّة بكائن معيّن دون غيره. ويمكنك الوصول إلى الصنف المنفرد الخاصّ بكائن ما عن طريق كتابة الصيغة class>>object
كما في المثال:
class C
end
class << C
# هنا هي الصنف المنفرد self
end
كثيرًا ما ستجد أن الصيغة التالية مستخدمة للوصول إلى الصنف المنفرد:
class C
class << self
# ...
end
end
وهذه الصيغة تسمح بتعريف توابع وواصفات للصنف (أو الوحدة) بدون الحاجة إلى كتابة def self.my_method
.
وبما أنّك تستطيع أن تفتح أيّ صنف منفرد لأيّ كائن على الإطلاق، فهذا يعني أنّ الكتلة البرمجية التالية:
o = Object.new
def o.my_method
1 + 1
end
تكافئ هذه الكتلة:
o = Object.new
class << o
def my_method
1 + 1
end
end
وسيكون لكلا الكائنين تابعًا اسمه my_method
يعيد القيمة 2.