الفصل السادس: البرمجة كائنية المنحى ( OOP )
أساسيات
معظم اللغات تنقسم إلى:
1- لغات برمجية إجرائية
ينقسم البرنامج فيها على هيئة Modules و Functions و Structures زى ال C
2- لغات برمجية كائنية المنحى
وهى تنقسم فيها الأكواد على هيئة Classes/Modules
المهم أن الشخص إذا فهم ال OOP جيدا سيجد أن الطريق مفتوح لكي يتعلم لغات كثيرة مثل: Java/C++/C# وغيرها
تخيل أنك تصمم إنسان Human على الورق
الإنسان له صفات مثل الطول والوزن .. هذه الصفات نسميها صفات Attributes أو حقول Fields فى بعض اللغات .. فى Ruby بتسمى صفة Attribute فتعود على الكلمة!
الإنسان هذا له أفعال أيضا يعملها ، مثل أنه يمشى وينام ويأكل ويشرب!
سنطبق كل الذي قلناه هكذا:
هذا التصميم الرئيسي:
class Human
def initialize
end
end
لاحظ ال initialize هى طريقة خاصة - للباني Constructor- بتعبر عن إنشاء الكائن .. فى حالة الإنسان، تستطيع أن تقول ولادته مثلا

ملاحظة: ستلاحظ أننا ننشيء الكائنات من الصفوف باستخدام طريقة new.. هذه الطريقة تستدعي طريقة initialize والتي بدورها تجهز لنا الكائن من الصف.
فى أخرى يجب أن نحددها فى الإنسان مثل الإسم والنوع واللون -وليس مجرد إنسان عنصرى

-
class Human
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
end
end
ال @ هى عبارة عن مرجع reference للكائن الحالي، بمعنى أن الكائن الذي سينشىء من الصف يجب أن يكون له اسم، فتكون له هذه العلامة
@name=the name of the object
@sex = the sex of the object
@color = the color of the object
جميل لحد الآن .. الصفات التي حددناها من إسم ونوع ولون ، تسمى حقول Fields او صفات Attributes ..
ناقص الأفعال Actions او السلوك ، وهي التصرفات التي يعملها الإنسان .. طالما أننا قلنا إنه شئ ينفذ اوفعل فهذا معناه إنه مجموعة من الأوامر أليس كذلك ؟ ويعنى بكل بساطة أن الأفعال هى نفسها دوال ..والمهم أن أي دالة مكتوبة داخل الصف تسمى طريقة Method.
نرجع لصفنا لأنه لم يكتمل بعد ..نحن لم نكتب سوى صفاته، وينقصنا أن نطبق أفعال الإنسان نفسه.
الإنسان يعمل ماذا ؟ ما التصرفات أو الأفعال التى يعملها ؟ ياكل .. يشرب .. ينام .. يشتغل ... إلخ
جيد ، سنعمل تطبيق لهذه الأفعال في صفنا.
سيتحول صفنا للصورة التالية:
class Human
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
@legs=2
@eyes=2
end
def move()
#moving code
end
def sleep()
#sleeping code
end
def drink()
#drinking code
end
end
أول شئ هو الباني Constructor وحددنا فيه الصفات التي نريد أن نخصصها للصف .. لاحظ أنه توجد صفات لم نحددها في الباني ولكن هي موجودة بالفعل عند كل الناس بلا إستثناء، فلن يكون لها معنى أن نحددها فى الباني مثل عدد العيون والأرجل.
فكل الحقول أو الصفات المتعلقة بالكائن نسميها متغيرات النسخةInstance Variables لأن كل نسخة لها حقول مختلفة عن الثانية .. وللتمييز بسهولة هى كل الحقول التى فيها @
ثانى شئ السلوك الخاص بصفنا وهو عبارة عن دوال مثل ماقلنا وهى sleep, work, drink, move
جميل ولكن كيف نقدر أن نتعامل مع الحقول التي في الصف ؟
يوجد طريقتين .. الأولى بأن تسمح لأى أحد أن يتحكم فى الحقول وهذا خطأ! لأنه ممكن أن يضع قيم غير منطقية!
الطريقة الثانية، بكل بساطة تستطيع أن تستخدم تكنيك ال get/set وتختبر القيم التي سيحاول المستخدم أن يعدل بها الحقول، لاحظ التالى مثلا لل name field
#Getter
def getName
return @name
end
#Setter
def setName(new_name)
if new_name.class != string:
puts "Error!"
exit(1)
else
@name=new_name
end
end
محاولة لتغيير الإسم بقيمة غير منطقية
ahmed=Human.new("ahmed", "m", "white")
ahmed.setName(10)
#ERRORRRRR
توجدأاساليب أفضل سنتعرض لها إن شاء الله.. ولاحظ أن تستطيع أن تعمل نفس الشئ بالباني Constructor حتى تضمن أن الكائن أنشيء بطريقة صحيحة.
ال getName هى طريقة تعيد لل @name
ال setName هى طريقة تعدل ال @name إلى new_name
مثلا ال sex بتاع ال Object
#Getter
def getSex
return @sex
end
سنستخدم Getter فقط وذلك لأننا لا نريد ان نعدل النوع :S
على كل إذا أحببت أن تعدل النوع تسطيع أن تستخدم Setter كالتالى :
#Setter
def setSex(new_sex)
@sex=new_sex
end
فى نوع آخر من المتغييرات غير المتغيرات النسخة وهو المتغيرات الصف وهى عبارة عن متغيرات خاصة بالصف وليست للكائن .. وهى تبدأ ب @@
بكل بساطة أنت لست فاهم!
تابع المثال التالي بتركيز
class Human
@@NUMBER_OF_HUMANS=0
#The Constructor
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
@legs=2
@eyes=2
@@NUMBER_OF_HUMANS += 1
end
def move()
#moving code
end
def sleep()
#sleeping code
end
def drink()
#drinking code
end
def Human.numberOfHumans
return @@NUMBER_OF_HUMANS
end
end
لاحظ ال @@NUMBER_OF_HUMANS .. المتغير هذا مشترك في كل الكائنات .. بمعنى أن كل الكائنات تستخدم نفس القيمة الموجودة فيه!
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
@legs=2
@eyes=2
@@NUMBER_OF_HUMANS += 1
end
لاحظ ان مع إنشاء كائن جديد يوجد متغير فى الصف وهو ال NUM_OF_HUMANS قيمته ستزيد بمقدار 1 وهذا الهدف منه ، إننا نقدر نحسب كم كائن تم إنشاؤه ،وبكل تأكيد الحساب لن يتم عن طريق الكائن ولكن عن طريق الصف !
فسنعرف الطريقة التي ستعيد لنا قيمة ال NUM_OF_HUMANS ولكن نريد أن نجعلها خاصة بالصف فقط ،لذا سنعرفها بالصورة التالية:
def Human.numberOfHumans
return @@NUMBER_OF_HUMANS
end
لاحظ إننا سبقناها باإسم الصف حتى تفهم Ruby أن الطريقة هذه خاصة بالصف، وهذا النوع من الدوال يطلق عليه الدوال الساكنة static.
جميل جدا .. هكذا أنت فهمت اللعبة، ولكن ينقص شئ واحد فقط و هو أن إسلوب ال Get/Set ليس جميلا

تخيل الذي سيستخدم صفك، يجب أن يعرف كيف تعملget و set الحقول .. بالنسبة لى عادى ولكن تخليك تحس أن الكود الذي أمامك شبه فوضوى .. توجد لغات قدمت مفهوم جديد وهو الخصائص Properties وهى عبارة عن تغليف لـ get/set
def name
@name
end
def name=(name)
@name=name
end
ahmed=Human.new("ahmed", "m", "white")
puts ahmed.name
#output: ahmed
ahmed.name="youssef"
puts ahmed.name
#output: youssef
بسيطة ها ؟
attr_accessor
على كل ، روبي تقدم طريقة أبسط وهى إستخدام ال attr_accessor كالتالى مثلا
attr_accessor :name
ahmed=Human.new("ahmed", "m", "white")
puts ahmed.name
#output: ahmed
ahmed.name="youssef"
puts ahmed.name
#output: youssef
فإذا أحببت أن تعمل getter/setter لأى Attribute فإستخدم attr_accessor!
على فرض إنك تريد أن تجعل الصفة وليكن sex لها get بس ولا تريد أحد أن يغير نوعه

فسنستخدم attr_reader
ahmed=Human.new("ahmed", "m", "white")
puts ahmed.sex
#output: m
ahmed.sex="f"
#ERROR!
attr_writer
على فرض إنك تريد أن تكون للصفة لها set بس فإستخدم attr_writer
Note: لا تستخدم ال attr_writer بدون داعى .. تستطيع أن تعمل set لحقلها من خلال الباني
الوراثة
الوراثة هى احد اهم المفاهيم بال OOP وهى البدأ من حيث إنتهى الآخرون
على فرض إنك تريد أن تصمم صف للموظف ولكن الموظف ماهو إلا إنسان مضاف له بعض الصفات والأفعال التى تميز الموظفين، أليس كذلك ؟
class Employer < Human
def initialize(name, sex, color, salary)
super(name, sex, color)
@salary=salary
end
لاحظ إستخدام طريقة super وهى طريقة تنادي الباني الخاص بالصف الأعلى super class وتعمل set للحقول الخاصة به والحقل الخاص بالموظف نفسه وليس للإنسان وهو ال salary سيحدد من ال Employer Constructor
is_a(class)
تختبر هل الكائن هذا مشتق من صف آخر.
instance_of?(class)
هى طريقة تختبر إذا كان الكائن هو كائن من الصف أم لا؟
kind_of?(class)
هي طريقة تختبر إذا كان اصل الكائن هو صف معين أم لا؟
h1=Human.new("ahmed", "m", "white")
puts "h1 is an instance of human" if h1.instance_of?(Human) #an Object of the Human class
puts h1.class #Human
emp1=Employer.new("ahmed", "m", "white", 2000)
puts "emp1 is an instance of Employer class" if emp1.instance_of?(Employer)
puts emp1.class #Employer
puts h1.is_a?(Human) #is h1 a human ?
puts h1.is_a?(Employer) # is h1 an employer ?
puts emp1.is_a?(Employer) # is emp1 an employer?
puts emp1.is_a?(Human) # is emp1 a human ?
puts emp1.instance_of?(Human)
puts emp1.kind_of?(Human)
#output:
h1 is an instance of human
Human
emp1 is an instance of Employer class
Employer
true
false
true
true
false
true
المثال بسيط جدا واعتقد وضح لك الفكرة
جميل .. ال Employer له بعض الأفعال الخاصة به مثل أنه يعمل أو يرفض

مثال شامل:
class Human
@@NUMBER_OF_HUMANS=0
#The Constructor
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
@legs=2
@eyes=2
@@NUMBER_OF_HUMANS += 1
end
attr_accessor :name, :color
attr_reader :sex
def move()
#moving code
end
def sleep()
#sleeping code
end
def drink()
#drinking code
end
def Human.numberOfHumans
return @@NUMBER_OF_HUMANS
end
end
class Employer < Human
attr_accessor :salary
def initialize(name, sex, color, salary)
super(name, sex, color)
@salary=salary
@state=""
end
def work
#working code
end
def getHired(salary)
@salary=salary
@state="hired"
end
def getFired
@salary=0 #no money :(
@state="fired"
end
def empInfo
s="Name: " << @name
s << ", State: " << @state
s << ", Salary: " << @salary.to_s
return s
end
end
class Firm
def initialize(firm_name, manager)
@firm_name=firm_name
@manager=manager
@emps=[]
end
def raiseEmployer(emp, raise)
emp.salary += raise
puts @manager + ": " + emp.name + " is raised!\n"
end
def hire(emp, salary)
emp.getHired(salary)
@emps.push(emp)
puts @manager + ": " + emp.name + " is hired.\n"
end
def fire(emp)
emp.getFired
@emps.delete(emp)
puts @manager + emp.name + ": " + "is fired.\n"
end
def employersList
return @emps
end
end
#demo
firm=Firm.new("High-Tech", "Ahmed Youssef")
$ahmed=Employer.new("ahmed", "m", "white", 2000)
$tina =Employer.new("christina", "f", "white", 4000)
$wael= Employer.new("wael", "m", "white", 3000)
def empsInfo
puts "----------------"
lst=[$ahmed, $tina, $wael]
for emp in lst
puts emp.empInfo
end
puts "----------------"
end
puts "Hiring..."
firm.hire($ahmed, 2500)
firm.hire($tina, 2500)
firm.hire($wael, 3000)
empsInfo
puts "Firing wael..."
firm.fire($wael)
empsInfo
firm.raiseEmployer($ahmed, 1000)
empsInfo
puts "--------------"
puts "The list of employers: "
for emp in firm.employersList
puts emp.name
end
الخرج:
Hiring...
Ahmed Youssef: ahmed is hired.
Ahmed Youssef: christina is hired.
Ahmed Youssef: wael is hired.
----------------
Name: ahmed, State: hired, Salary: 2500
Name: christina, State: hired, Salary: 2500
Name: wael, State: hired, Salary: 3000
----------------
Firing wael...
Ahmed Youssefwael: is fired.
----------------
Name: ahmed, State: hired, Salary: 2500
Name: christina, State: hired, Salary: 2500
Name: wael, State: fired, Salary: 0
----------------
Ahmed Youssef: ahmed is raised!
----------------
Name: ahmed, State: hired, Salary: 3500
Name: christina, State: hired, Salary: 2500
Name: wael, State: fired, Salary: 0
----------------
--------------
The list of employers:
ahmed
christina
اها بالمناسبة اى متغير يسبقه $ معناه إنه متغير عام تستطيع أن تستخدمه فى أى مكان فى كودك!
هل تذكر لما تكلمنا عن الوحدة Module وقلنا أنها مجموعة صفوف و ثوابت و دوال جاهزة ؟
كل ما عليك هو أنك تأخد كل الذي عملناه من الصفوف لـ empsInfo
وتضعهم في ملف ، وتسمه باسم مرتبط بهم مثلا Business و تضع السطر التالي في بداية الملف
module Business
وهذا في نهايته
end
سيتحول للشكل التالي:
module Business
class Human
@@NUMBER_OF_HUMANS=0
#The Constructor
def initialize(name, sex, color)
@name =name
@sex =sex
@color=color
@legs=2
@eyes=2
@@NUMBER_OF_HUMANS += 1
end
attr_accessor :name, :color
attr_reader :sex
def move()
#moving code
end
def sleep()
#sleeping code
end
def drink()
#drinking code
end
def Human.numberOfHumans
return @@NUMBER_OF_HUMANS
end
end
class Employer < Human
attr_accessor :salary
def initialize(name, sex, color, salary)
super(name, sex, color)
@salary=salary
@state=""
end
def work
#working code
end
def getHired(salary)
@salary=salary
@state="hired"
end
def getFired
@salary=0 #no money :(
@state="fired"
end
def empInfo
s="Name: " << @name
s << ", State: " << @state
s << ", Salary: " << @salary.to_s
return s
end
end
class Firm
def initialize(firm_name, manager)
@firm_name=firm_name
@manager=manager
@emps=[]
end
def raiseEmployer(emp, raise)
emp.salary += raise
puts @manager + ": " + emp.name + " is raised!\n"
end
def hire(emp, salary)
emp.getHired(salary)
@emps.push(emp)
puts @manager + ": " + emp.name + " is hired.\n"
end
def fire(emp)
emp.getFired
@emps.delete(emp)
puts @manager + emp.name + ": " + "is fired.\n"
end
def employersList
return @emps
end
end
def empsInfo
puts "----------------"
lst=[$ahmed, $tina, $wael]
for emp in lst
puts emp.empInfo
end
puts "----------------"
end
def demo
firm=Firm.new("High-Tech", "Ahmed Youssef")
$ahmed=Employer.new("ahmed", "m", "white", 2000)
$tina =Employer.new("christina", "f", "white", 4000)
$wael= Employer.new("wael", "m", "white", 3000)
puts "Hiring..."
firm.hire($ahmed, 2500)
firm.hire($tina, 2500)
firm.hire($wael, 3000)
empsInfo
puts "Firing wael..."
firm.fire($wael)
empsInfo
firm.raiseEmployer($ahmed, 1000)
empsInfo
puts "--------------"
puts "The list of employers: "
for emp in firm.employersList
puts emp.name
end
end
end
وللإستخدام بكل بساطة اعمل صف يشمل على هذه الوحدة.
الاول تستدعى الوحدة بإستخدام require وتعمل include لها بإستخدام include
require "business"
class Dem
include Business
end
p=Dem.new
p.demo
Public/Private/Protected
public: معناها ان الوصول إليها لكل العالم سواء داخل الصف او خارجه
private : معناها أن الوصول إليها لداخل الصف فقط
protected : معناها إن الوصول إليها للصف او للصفوف المشتقة من نفس الصف
class A
def method1
end
def method2
end
def method3
end
end
class B < A
end
نعمل كائنات:
لاحظ أن method1, method2, method3 تستطيع أن تستخدمهم من خلال الكائنات A, B وهذا لأنهم Public بشكل افتراضي.
فى ال C# مثلا نحن نعرف أننا نستخدم private حتى نحدد عملية الوصول لإستخدام الطرق سواء للعالم الخارجى او للصفوف التي ستشتق من الصف الحالي.
لكن فى Ruby الوضع يختلف لأن ال private فيها = protected بمعنى أن الطريقة صلاحيات وصولها هي Private تستطيع أن تستخدمها فى الصفوف المشتقة.
لكن دعنا على القاعدة اللى قلناها بالأول
public: معناها ان الوصول إليها لكل العالم سواء داخل الصف او خارجه
private : معناها أن الوصول إليها لداخل الصف فقط
protected : معناها أن الوصول إليها للصف او للصفوف المشتقة من نفس الصف
نضرب مثالا بسيطا
إذا عدلنا المثال السابق إلى
class A
def method1
puts "method1"
end
def method2
puts "method2 calling method3"
method3
end
def method3
puts "method3"
end
private :method1, :method3
end
class B < A
def method4
puts "method4 calling method3"
method3
end
end
سننشيء الكائنات:
a=A.new()
b=B.new()
b2.method4
#output:
method4 calling method3
method3
ستلاحظ أن ال a, b ليس لهم وصول إلى method1, method3 ! لأنهم private تقدر تستخدمهم فى العمليات الداخلية للصف نفسه.
هكذا:
def method4
puts "method4 calling method3"
method3
end
فهذا معنى private بكل بساطة.
لن نتكلم عن protected لأن Ruby اهملت مفهومها فجعلتها مثل public

تحميل الطرق
وهى إعادة تعريف طريقة موجودة فى الصف الأب أو الصف الأساسي فى الصف الفرعي او الصف الإبن بصورة تتلائم معه
لاحظ المثال:
class A
def method1
puts "A's method1 is called"
end
def method2
puts "A's method2 calling method3.."
method3
end
def method3
puts "A's method3 is called"
end
private :method1, :method3
end
class B < A
def method2 #override the protected method
puts "B's method2 is called"
end
def method4
puts "Calling method1..."
method1
end
end
نستخدمهم:
a=A.new()
#Calling the parent's method2
a.method2
b=B.new()
#Calling the overriden method2 in the child
b.method2
الخرج:
A's method2 calling method3..
A's method3 is called
B's method2 is called
لاحظ إن method2 فى ال B اصبحت مختلفة تماما عنها فى ال A وهذا بسبب أنها أعيد تعريفها فى ال B
تعدد الأشكال
هذه الكلمة تثير حساسية كثير من الناس مع أن مغزاها سهل جدا وبسيط، وهو أنه يوجد أشكال عديدة من خلال إسم واحد، وسأشرحها حالا
لاحظ المثال التالي:
class A
def method1
puts "A's method1 is called.."
end
end
class B
def method1
puts "B's method2 is called.."
end
end
لاحظ وجود طريقتين بنفس الإسم method1
نعمل objects
نستخدم method1 الموجودة بالإثنين:
او بهذه الصورة:
objects=[a, b]
for obj in objects
obj.method1
end
فى كلتا الحالتين الخرج سيكون :
#output:
#A's method1 is called..
#B's method2 is called..
freeze, frozen?
Freeze هي طريقة تمنع الكائن من التعديل عليه
frozen? هي طريقة تستخدم فى إختبار هل الكائن تم تنفيذ freeze عليه أم لا
s="Hello"
s.freeze
s << ", World!" #can't be done as "s" is frozen!
puts "freezed!" if s.frozen?
to_s
هى طريقة تعبر عن الكائن فى حالة إستخدام puts معه "التمثيل النصي“
to_i
هى طريقة تعبر عن الكائن فى حالة محاولة تحويله ل int
to_f
هى طريقة تعبر عن الكائن فى حالة تحويله ل float
to_a
هى طريقة تعبر عن الكائن فى حالة تحويله ل array
to_hash
هى طريقة تعبر عن الكائن فى حالة تحويله ل hash