Ruby/modules and classes
الوحدات
تخدم الوحدات غايتين اثنتين في لغة روبي وهما نطاقات الأسماء، وميّزة الخلط الضمني mix-in التي سنوضّحها لاحقًا. يستخدم نطاق الأسماء namespace لتنظيم الشيفرة البرمجية ضمن مجموعات مستقلّة تمنع تداخل التوابع والمتغيّرات ذات الأسماء المتشابهة فيما بينها. فعلى سبيل المثال، نطاق الأسماء 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
تُعدّ إعادة فتح الأصناف ميّزة فعّالة جدًا في لغة روبي، لكنّ الاستخدام الأمثل لها يكمن في إعادة فتح أصنافك الخاصّة، لأنّ فتح أصناف أخرى لا تمتلكها أنت قد يؤدي إلى تضاربات أو صعوبة في تتبع الأخطاء.
التضمين Nesting
يمكن تعريف الوحدات داخل بعضها:
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
التوابع
راجع صفحة التوثيق الخاصة بصيغة التوابع لتتعرف على صيغة تعريفها. يمكن استدعاء توابع الأصناف مباشرة، ولعلّك تجد ذلك غريبًا، لكنّ التابع الخاص بالوحدة يطلق عليه تابع الصنف بدلًا من تابع الوحدة، انظر أيضًا صفحة التوثيق الخاصّة بالوحدات في لغة روبي إذ بإمكانك تحويل تابع مثال instance method معرّف في وحدة إلى تابع صنف. عندما يشير تابع الصنف إلى ثابت فستُطبق حينها ذات القواعد عند الإشارة إليه خارج التابع، إذ أنّ النطاق نفسه في الحالتين. تُستدعى توابع الأمثلة المعرّفة في الوحدات عند تضمينها حصرًا. ويكون لها وصول إلى الثوابت المعرّفة عند تضمينها في شجرة الآباء:
module A
Z = 1
def z
Z
end
end
include A
p self.class.ancestors #=> [Object, A, Kernel, BasicObject]
p z #=> 1
المرئية
توفّر لغة روبي ثلاثة مستويات من المرئية. المستوى الافتراضي هو المرئية العامّة 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
المستوى الثالث للمرئية هو المرئية الخاصّة. فالتوابع الخاصة لا يمكن استدعاؤها عن طريق مستقبل ولا حتى self. وإذا استدعيت بهذه الطريقة فسيظهر استثناء NoMethodError المماثلة alias وإلغاء التعريف undef يمكنك مماثلة أو إلغاء تعريف التوابع، إلا أنّ هذه العمليات ليست خاصّة بالوحدات او الأصناف. راجع توثيق الصيغ المنوعة في لغة روبي لمزيد من التفاصيل
الأصناف
الأصناف هي وحدات إلا أنّها لا يمكن استخدامها للخلط الضمنيّ 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 أو الصنف الخاص) هو الصنف الذي يحوي التوابع الخاصّة بكائن معيّن دون غيره. ويمكنك الوصول إلى الصنف المنفرد الخاصّ بكائن ما عن طريق كتابة الصيغة 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.