Programming Freaks  | دورات ومقالات برمجيه

Please login or register.

Login with username, password and session length
Advanced search  

News:

Programming-Fr34ks.net
Up and running

Author Topic: البرمجة كائنية المنحى  (Read 1049 times)

Striky

  • Helping Freak
  • Administrator
  • Posting Freak
  • *****
  • Posts: 283
    • View Profile
    • Weblog
    • Email
البرمجة كائنية المنحى
« on: October 26, 2008, 06:46:48 AM »

الفصل السادس: البرمجة كائنية المنحى ( OOP )

Code: ($2) [Select]
أساسيات
   معظم اللغات تنقسم إلى:

   1- لغات برمجية إجرائية
ينقسم البرنامج فيها على هيئة Modules و Functions و Structures زى ال C

   2- لغات برمجية كائنية المنحى

وهى تنقسم فيها الأكواد على هيئة Classes/Modules

المهم أن الشخص إذا فهم ال OOP جيدا سيجد أن الطريق مفتوح لكي يتعلم لغات كثيرة مثل: Java/C++/C# وغيرها
تخيل أنك تصمم إنسان Human على الورق
الإنسان له صفات مثل الطول والوزن .. هذه الصفات نسميها صفات Attributes أو حقول Fields فى بعض اللغات .. فى  Ruby بتسمى صفة Attribute فتعود على الكلمة!
الإنسان هذا له أفعال أيضا يعملها ، مثل أنه يمشى وينام ويأكل ويشرب!
سنطبق كل الذي قلناه هكذا:

هذا التصميم الرئيسي:
Code: ($2) [Select]
class Human
  def initialize
   
  end
end

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

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

فى أخرى يجب أن نحددها فى الإنسان مثل الإسم والنوع واللون -وليس مجرد إنسان عنصرى :D-
Code: ($2) [Select]
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.

نرجع لصفنا لأنه لم يكتمل بعد ..نحن لم نكتب سوى صفاته، وينقصنا أن نطبق أفعال الإنسان نفسه.
الإنسان يعمل ماذا ؟ ما التصرفات أو الأفعال التى يعملها ؟ ياكل .. يشرب .. ينام .. يشتغل ... إلخ
جيد ، سنعمل تطبيق لهذه الأفعال في صفنا.
سيتحول صفنا للصورة التالية:
Code: ($2) [Select]
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
Code: ($2) [Select]
  #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


محاولة لتغيير الإسم بقيمة غير منطقية
Code: ($2) [Select]
ahmed=Human.new("ahmed", "m", "white")
ahmed.setName(10)
#ERRORRRRR

توجدأاساليب أفضل سنتعرض لها إن شاء الله.. ولاحظ أن تستطيع أن تعمل نفس الشئ بالباني Constructor حتى تضمن أن الكائن أنشيء بطريقة  صحيحة.

ال getName هى طريقة تعيد لل @name
ال setName هى طريقة تعدل ال @name إلى new_name

مثلا ال sex بتاع ال Object
Code: ($2) [Select]
  #Getter
  def getSex
    return @sex
  end

سنستخدم Getter فقط وذلك لأننا لا نريد ان نعدل النوع :S
على كل إذا أحببت أن تعدل النوع تسطيع أن تستخدم Setter كالتالى :
Code: ($2) [Select]
  #Setter
  def setSex(new_sex)
    @sex=new_sex
  end 


فى نوع آخر من المتغييرات غير المتغيرات النسخة وهو المتغيرات الصف وهى عبارة عن متغيرات خاصة بالصف وليست للكائن .. وهى تبدأ ب @@
بكل بساطة أنت لست فاهم!

تابع المثال التالي بتركيز
Code: ($2) [Select]
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  ولكن نريد أن نجعلها خاصة بالصف فقط ،لذا سنعرفها بالصورة التالية:
Code: ($2) [Select]
  def Human.numberOfHumans
    return @@NUMBER_OF_HUMANS
  end

لاحظ إننا سبقناها باإسم الصف حتى تفهم Ruby أن الطريقة هذه خاصة بالصف، وهذا النوع من الدوال يطلق عليه الدوال الساكنة static.

جميل جدا .. هكذا أنت فهمت اللعبة، ولكن ينقص شئ واحد فقط و هو أن إسلوب ال Get/Set ليس جميلا :D
تخيل الذي سيستخدم صفك، يجب أن يعرف كيف تعملget و set الحقول .. بالنسبة لى عادى ولكن تخليك تحس أن الكود الذي أمامك شبه فوضوى .. توجد لغات قدمت مفهوم جديد وهو الخصائص Properties وهى عبارة عن تغليف لـ get/set
 
Code: ($2) [Select]
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 كالتالى مثلا

Code: ($2) [Select]
 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!
Code: ($2) [Select]
attr_reader

على فرض إنك تريد أن تجعل الصفة وليكن sex لها get بس ولا تريد أحد أن يغير نوعه :D
فسنستخدم attr_reader
Code: ($2) [Select]
 attr_reader :sex 

Code: ($2) [Select]
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 وهى البدأ من حيث إنتهى الآخرون
على فرض إنك تريد أن تصمم صف للموظف ولكن الموظف ماهو إلا إنسان مضاف له بعض الصفات والأفعال التى تميز الموظفين، أليس كذلك ؟
Code: ($2) [Select]
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)
هي طريقة تختبر إذا كان اصل الكائن هو صف معين أم لا؟

Code: ($2) [Select]
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 له بعض الأفعال الخاصة به مثل أنه يعمل أو يرفض :D

مثال شامل:
Code: ($2) [Select]
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

الخرج:
Code: ($2) [Select]
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

سيتحول للشكل التالي:
Code: ($2) [Select]
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 : معناها إن الوصول إليها للصف او للصفوف المشتقة من نفس الصف

Code: ($2) [Select]
class A
  def method1
   
  end
 
  def method2
   
  end
 
  def method3
   
  end
 
end

class B < A
 
end

نعمل كائنات:
Code: ($2) [Select]
a=A.new()
b=B.new()


لاحظ أن method1, method2, method3 تستطيع أن تستخدمهم من خلال الكائنات A, B  وهذا لأنهم Public  بشكل افتراضي.

فى ال C# مثلا نحن نعرف أننا نستخدم private حتى نحدد عملية الوصول لإستخدام الطرق سواء للعالم الخارجى او للصفوف التي ستشتق من الصف الحالي.

لكن فى Ruby الوضع يختلف لأن ال private فيها = protected بمعنى أن الطريقة صلاحيات وصولها هي Private تستطيع أن تستخدمها فى الصفوف المشتقة.

لكن دعنا على القاعدة اللى قلناها بالأول

public: معناها ان الوصول إليها لكل العالم سواء داخل الصف  او خارجه
private : معناها أن الوصول إليها لداخل الصف فقط
protected : معناها أن الوصول إليها للصف او للصفوف المشتقة من نفس الصف

نضرب مثالا بسيطا
إذا عدلنا المثال السابق إلى
Code: ($2) [Select]
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

سننشيء الكائنات:
Code: ($2) [Select]
a=A.new()
b=B.new()

b2.method4

#output:
method4 calling method3
method3

ستلاحظ أن ال a, b ليس لهم وصول إلى method1, method3 ! لأنهم private تقدر تستخدمهم فى العمليات الداخلية للصف نفسه.
هكذا:
Code: ($2) [Select]
  def method4
    puts "method4 calling method3"
    method3
  end



فهذا معنى private بكل بساطة.

لن نتكلم عن protected لأن Ruby اهملت مفهومها فجعلتها مثل public :)
تحميل الطرق
وهى إعادة تعريف طريقة موجودة فى الصف الأب أو الصف الأساسي فى الصف الفرعي  او الصف الإبن  بصورة تتلائم معه
لاحظ المثال:
Code: ($2) [Select]
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

نستخدمهم:
Code: ($2) [Select]
a=A.new()
#Calling the parent's method2
a.method2

b=B.new()
#Calling the overriden method2 in the child
b.method2

 
الخرج:
Code: ($2) [Select]
A's method2 calling method3..
A's method3 is called
B's method2 is called

لاحظ إن method2 فى ال B اصبحت مختلفة تماما عنها فى ال A  وهذا بسبب أنها أعيد تعريفها فى ال B
تعدد الأشكال
هذه الكلمة  تثير حساسية كثير من الناس مع أن مغزاها سهل جدا وبسيط،  وهو أنه يوجد أشكال عديدة من خلال إسم واحد، وسأشرحها حالا
لاحظ المثال التالي:
Code: ($2) [Select]
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
Code: ($2) [Select]
a=A.new()
b=B.new()

نستخدم method1 الموجودة بالإثنين:
Code: ($2) [Select]
a.method1
b.method1

او بهذه الصورة:
Code: ($2) [Select]
objects=[a, b]
for obj in objects
  obj.method1
end


فى كلتا الحالتين الخرج سيكون :
Code: ($2) [Select]
#output:
#A's method1 is called..
#B's method2 is called..




freeze, frozen?
Freeze هي طريقة تمنع الكائن من التعديل عليه
frozen? هي طريقة تستخدم فى إختبار هل الكائن تم تنفيذ freeze عليه أم لا
Code: ($2) [Select]
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


Logged

Life is just a chance to grow a soul. - A. Powell
Weblog: http://ahmedyoussef.wordpress.com/