يوفر عليك استدعاء وتعريف الدوال من عدة أماكن نسخ ولصق الشيفرة المصدرية، إذ أن عدم تكرار الشيفرة هو ممارسة جيدة لأنه إذا أردت تغيير هذه الشيفرة المكرّرة (إما لحل بعض الأخطاء أو لإضافة ميزات جديدة)، فستحتاج فقط لتغييرها في مكان واحد، ويصبح البرنامج أقصر دون شيفرة مكررة وأسهل للقراءة.
الأمر مماثل بالنسبة للدوال، الوراثة inheritance هي تقنية لإعادة استخدام الشيفرة، ويمكن تطبيقها في الأصناف، وهي وسيلة لوضع الأصناف في علاقة أب-ابن، بحيث يرث الصنف الابن نسخةً من توابع الصنف الأب، ويخفف عليك عبء تكرار التوابع في عدة أصناف.
يعتقد العديد من المبرمجين أن الوراثة أمرٌ مبالغٌ فيه أو خطر بسبب زيادة تعقيد شبكات وراثة الأصناف المضافة إلى البرنامج. ليست المنشورات المدونة بعنوان "الوراثة خطيرة" خاطئة كليًا، فمن السهل استغلال البرنامج، ولكن الاستخدام المحدود لهذه التقنية يمكن أن يوفر الكثير من الوقت بما يتعلق بتنظيم الشيفرة.
كيف تعمل الوراثة
نضع اسم الصنف الأب الموجود أساسًا بين قوسين في تعليمة class
لإنشاء صنف ابن. لتتدرب على إنشاء صنف ابن، نفتح نافذة محرر الملفات ونكتب الشيفرة التالية ونحفظها في ملف inheritanceExample.py:
1 class ParentClass: 2 def printHello(self): print('Hello, world!') 3 class ChildClass(ParentClass): def someNewMethod(self): print('ParentClass objects don't have this method.') 4 class GrandchildClass(ChildClass): def anotherNewMethod(self): print('Only GrandchildClass objects have this method.') print('Create a ParentClass object and call its methods:') parent = ParentClass() parent.printHello() print('Create a ChildClass object and call its methods:') child = ChildClass() child.printHello() child.someNewMethod() print('Create a GrandchildClass object and call its methods:') grandchild = GrandchildClass() grandchild.printHello() grandchild.someNewMethod() grandchild.anotherNewMethod() print('An error:') parent.someNewMethod()
عندما ننفذ البرنامج، يكون الخرج على النحو التالي:
Create a ParentClass object and call its methods: Hello, world! Create a ChildClass object and call its methods: Hello, world! ParentClass objects don't have this method. Create a GrandchildClass object and call its methods: Hello, world! ParentClass objects don't have this method. Only GrandchildClass objects have this method. An error: Traceback (most recent call last): File "inheritanceExample.py", line 35, in <module> parent.someNewMethod() # ParentClass objects don't have this method. AttributeError: 'ParentClass' object has no attribute 'someNewMethod'
أنشأنا ثلاثة أصناف ParentClass
في السطر (1) و ChildClass
في السطر (3) و GrandchildClass
في السطر (4). الصنف ChildClass
هو صنف فرعي للصنف ParentClass
، يعني أن ChildClass
لديها توابع ParentClass
نفسها، ونقول أن ChildClass
يرث التوابع من ParentClass
، وأيضًا GrandchildClass
هو صنف فرعي من ChildClass
وبالتالي لديه كل توابع ChildClass
وأبيها ParentClass
.
نسخنا ولصقنا الشيفرة من التابع printHello()
باستخدام هذه الطريقة إلى الصنفين ChildClass
و GrandChild
. أي تغيير للشيفرة في PrintHello()
لا يحدث فقط في ParentClass
بل في ChildClass
و GrandchildClass
. هذا نفس تغيير الشيفرة في دالة تُحدّث كل استدعاءات الدالة الخاصة بها. يمكنك رؤية هذه العلاقة في الشكل 1. لاحظ في مخططات الأصناف أن السهم ينطلق من الصنف الفرعي ويشير إلى الصنف الأساس. هذا يعكس أن كل صنف يعرف دائمًا الصنف الأساس الخاص به ولكنه لا يعرف أصنافه الفرعية.
[الشكل 1: مخطط هرمي (يسار) ومخطط فين Venn (يمين) يبينان العلاقات بين الأصناف الثلاثة والتوابع التي يستخدموها]
تمثّل الأصناف أب- ابن عادةً علاقات "is a"، إذ أن كائن ChildClass
هو كائن ParentClass
لأن لديه نفس التوابع التي لدى كائن ParentClass
، إضافةً إلى بعض التوابع الإضافية التي يعرّفها. هذه هي علاقة باتجاه واحد: ليس كائن ParentClass
هو كائن ChildClass
. إذا حاول كائن استدعاء someNewMethod()
الموجود فقط لكائنات ChildClass
(وأصناف ChildClass
الفرعية) يعطي بايثون Python خطأ AttributeError
.
يمكنك الاطلاع على مقال مخططات الفئات (Class Diagram) في لغة النمذجة الموحدة UML على أكاديمية حسوب لمزيدٍ من المعلومات على علاقات "is a" وغيرها في مخططات الأصناف.
يعتقد المبرمجون أن الأصناف المتعلقة ببعضها تندرج تحت هرمية علاقات "is a" واقعية، فغالبًا ما ترى في تدريبات البرمجة كائنية التوجه OOP أصناف أب وابن وحفيد:
Vehicle▶FourWheelVehicle▶Car
أو
Animal▶Bird▶Sparrow
أو
Shape▶Rectangle▶Square
لكن تذكر أن السبب الأساسي للوراثة هو إعادة استعمال الشيفرة. تسمح لك الوراثة بتفادي نسخ ولصق الشيفرة إذا كان البرنامج الخاص بك يحتاج إلى صنف بمجموعة من التوابع التي هي مجموعة كبرى من توابع صنف أُخر.
نسمي أحيانًا الصنف الابن بالصنف الفرعي subclass أو الصنف المُشتق derived class ونسمي الصنف الأب الصنف الأعلى super class أو الصنف الأساس base class، ويمكنك الاطلاع على مقال الوراثة والتعددية الشكلية Polymorphism والأصناف المجردة Abstract Classes في جافا على أكاديمية حسوب لمزيدٍ من المعلومات.
إعادة تعريف التوابع
ترث الأصناف الفرعية كل توابع الأصناف الأب، ويمكن لصنف ابن إعادة تعريف تابع موروث عن طريق تقديم التابع والشيفرة الخاصين بها. يكون لدى التابع الذي يعيد التعريف اسم تابع الصنف الأب ذاته.
لتوضيح هذا المفهوم لنعد إلى لعبة إكس أو Tic-Tac-Toe التي أنشأناها سابقًا، ولكن هذه المرة سننشئ صنفًا جديدًا MiniBoard
وهو صنف فرعي من TTTBoard
يعيد تعريف getBoardStr()
لرسم لوحة إكس أو أصغر. سيسأل البرنامج أي نوع لوح سيستخدم ولا نحتاج إلى نسخ ولصق باقي توابع TTTBoard
لأن MiniBoard
سيرثهم.
ضِف التالي في نهاية ملف ticktactoe_oop.py لإنشاء صنف ابن لصنف TTTBoard
الأصلي، ثم أعد كتابة تابع getBoardStr()
:
class MiniBoard(TTTBoard): def getBoardStr(self): """Return a tiny text-representation of the board.""" # Change blank spaces to a '.' for space in ALL_SPACES: if self._spaces[space] == BLANK: self._spaces[space] = '.' boardStr = f''' {self._spaces['1']}{self._spaces['2']}{self._spaces['3']} 123 {self._spaces['4']}{self._spaces['5']}{self._spaces['6']} 456 {self._spaces['7']}{self._spaces['8']}{self._spaces['9']} 789''' # Change '.' back to blank spaces. for space in ALL_SPACES: if self._spaces[space] == '.': self._spaces[space] = BLANK return boardStr
كما في التابع ()getBoardStr
الخاص بصنف TTTBoard
سينشئ التابع getBoardStr()
الخاص بـ MiniBoard
لإظهار سلسلة نصية متعددة الأسطر من لوحة إكس أو عندما تمرر إلى دالة print()
ولكن هذه السلسلة النصية هي أقصر وتتجاهل الأسطر بين X و O وتستخدم الفواصل للدلالة على الأماكن الفارغة.
غيّر السطر في main()
ليستنسخ كائن MiniBoard
بدلًا من كائن TTTBoard
:
if input('Use mini board? Y/N: ').lower().startswith('y'): gameBoard = MiniBoard() # Create a MiniBoard object. else: gameBoard = TTTBoard() # Create a TTTBoard object.
يعمل البرنامج كما في السابق ما عدا تغيير هذا السطر الواحد في main()
وعندما تنفذ البرنامج الآن سيصبح الخرج على النحو التالي:
Welcome to Tic-Tac-Toe! Use mini board? Y/N: y ... 123 ... 456 ... 789 What is X's move? (1-9) 1 X.. 123 ... 456 ... 789 What is O's move? (1-9) --snip-- XXX 123 .OO 456 O.X 789 X has won the game! Thanks for playing!
يستطيع البرنامج الآن بسهولة الحصول على تنفيذي صنفي لوح إكس أو، وإذا أردت فقط النسخة المصغرة من اللوحة يمكنك ببساطة استبدال الشيفرة في تابع getBoardStr()
في TTTBoard
. ولكن إذا أردت الاثنين فالوراثة تسمح لك بسهولة إنشاء صنفين عن طريق إعادة استخدام الشيفرة المشتركة بينهما.
يمكننا إضافة سمة attribute جديدة إلى TTTBoard
اسمها useMiniBoard
إذا لم نستخدم الوراثة، ووضع تعليمة if-else
داخل getBoardStr()
لتقرر متى تُظهِر اللوحة العادية أو اللوحة المصغرة، سيعمل هذا جيدًا لأن التغيير بسيط، ولكن ماذا لو كان الصنف الفرعي MiniBoard
يحتاج لإعادة تعريف تابعين أو ثلاثة توابع أو حتى 100 تابع؟ ماذا لو أردنا إنشاء عدة أصناف فرعية من TTTBoard
؟ سيتسبّب عدم استخدام الوراثة بسيل من تعليمات if-else
داخل التابع الخاص بنا وزيادة كبيرة في تعقيد الشيفرة. يُمكّننا استخدام الأصناف الفرعية وإعادة تعريف التوابع من ترتيب الشيفرة الخاصة بنا ضمن أصناف منفصلة للتعامل مع حالات استخدام مماثلة.
دالة super()
يشابه تابع الصنف المعاد تعريفه overridden تابع الصنف الأب؛ فحتى لو كانت الوراثة هي تقنية لإعادة استخدام الشيفرة، قد يتطلب إعادة تعريف التابع إعادة كتابة نفس الشيفرة من تابع الصنف الأب بمثابة جزء من تابع شيفرة الابن. لمنع تكرار الشيفرة: تسمح دالة super()
للتابع المعاد تعريفه استدعاء التابع الأصلي في الصنف الأب.
مثلًا، لنُنشئ صنفًا جديدًا اسمه HintBoard
ليكون صنفًا فرعيًا من TTTBoard
، بحيث يعيد هذا الصنف تعريف getBoardStr()
، ويضيف بعد رسم لوحة إكس أو تلميحًا hint فيما إذا كان X أو O قد يربح في الخطوة التالية. هذا يعني أن تابع getBoardStr()
الخاص بصنف HintBoard
سينجز نفس مهام تابع getBoardStr()
الخاص بصنف TTTBoard
لرسم لوحة إكس أو. بدلًا من تكرار الشيفرة لإنجاز ذلك، يمكننا استخدام super()
لاستدعاء تابع getBoardStr
الخاص بصنف TTTBoard
من تابع getBoardStr()
الخاص بصنف HintBoard
. ضِف التالي لنهاية ملف tictactoe_oop.ps:
class HintBoard(TTTBoard): def getBoardStr(self): """Return a text-representation of the board with hints.""" 1 boardStr = super().getBoardStr() # Call getBoardStr() in TTTBoard. xCanWin = False oCanWin = False 2 originalSpaces = self._spaces # Backup _spaces. for space in ALL_SPACES: # Check each space: # Simulate X moving on this space: self._spaces = copy.copy(originalSpaces) if self._spaces[space] == BLANK: self._spaces[space] = X if self.isWinner(X): xCanWin = True # Simulate O moving on this space: 3 self._spaces = copy.copy(originalSpaces) if self._spaces[space] == BLANK: self._spaces[space] = O if self.isWinner(O): oCanWin = True if xCanWin: boardStr += '\nX can win in one more move.' if oCanWin: boardStr += '\nO can win in one more move.' self._spaces = originalSpaces return boardStr
أولًا، تنفذ التعليمة super().getBoardStr()
في السطر ذو الرقم 1 الشيفرة داخل الصنف getBoardStr()
الخاص بصنف TTTBoard
، والتي تعيد سلسلةً نصيةً على شكل لوحة إكس أو. نحفظ حاليًا هذه السلسلة في متغير اسمه boardStr
. تعالج الشيفرة الباقية إنشاء التلميح بعد إنشاء لوحة السلسلة النصية عن طريق إعادة استخدام getBoardStr()
الخاص بصنف TTTBoard
. يعيّن تابع getBoardStr()
قيمة المتغيرين xCanWin
و oCanWin
إلى False
، وينسخ احتياطيًا القاموس self._spaces
إلى المتغير originalSpaces
(السطر ذو الرقم 2)، ثم تُنفَّذ حلقة for
على كل أماكن اللوحة من 1
إلى 9
. تُضبط سمة self._spaces
لنسخ المكتبة originalSpaces
، وإذا كانت الخلية فارغة تُوضع X مكانها، إذ يحفز هذا تحريك X إلى الفراغ التالي.
سيحدد استدعاء self.isWinner()
إذا كانت هذه هي الحركة الرابحة؛ فإذا كانت كذلك تصبح xCanWin
هي True
. تُكرر هذه الخطوات من أجل O لمعرفة ما إذا كان O يربح بالتحرك إلى هذا المكان (السطر ذو الرقم 3). يستخدم هذا التابع وحدة copy
لنسخ القاموس في self._spaces
لذا نضيف السطر التالي لأول ملف tictactoe.py.
import copy
نغير بعدها السطر في main()
لنستنسخ كائن HintBoard
بدلًا من TTTBoard
:
gameBoard = HintBoard() # Create a TTT board object.
يعمل البرنامج كما كان عدا تغيير السطر الوحيد في main()
وعندما ينفذ البرنامج سيكون الخرج على النحو التالي:
Welcome to Tic-Tac-Toe! --snip-- X| | 1 2 3 -+-+- | |O 4 5 6 -+-+- | |X 7 8 9 X can win in one more move. What is O's move? (1-9) 5 X| | 1 2 3 -+-+- |O|O 4 5 6 -+-+- | |X 7 8 9 O can win in one more move. --snip-- The game is a tie! Thanks for playing!
في نهاية التابع: إذا كانت قيمة xCanWin
و oCanWin
هي True
، تُضاف رسالةٌ إضافية تشير إلى ذلك إلى السلسلة النصية boardStr
، وأخيرًا تُعاد القيمة boardStr
.
لا يحتاج كل تابع معاد تعريفه لاستخدام super()
؛ فإذا كان يعمل التابع -الذي يعيد التعريف- شيئًا مختلفًا تمامًا عن التابع المُعاد تعريفه في الصنف الأب، لا توجد حاجة لاستدعاء التابع المعاد تعريفه باستخدام super()
. تفيد الدالة super()
على نحوٍ خاص عندما يكون للصنف أكثر من تابع أب كما موضح في الفقرة "الوراثة المتعددة" لاحقًا.
فضل التكون Composition على الوراثة
الوراثةهي تقنية جيدة لإعادة استخدام الشيفرة، وقد تفكر باستخدامها فورًا في الأصناف الخاصة بك، ولكن ربما لا تريد دومًا أن يكون الأساس والأصناف الفرعية مرتبطة جدًا، فإنشاء مستويات متعددة من الوراثة لا يرتب الشيفرة الخاصة لك أكثر ما يضيف بيروقراطية.
على الرغم من أنه بإمكانك استخدام الوراثة للأصناف ذات العلاقات " is a" (بمعنى آخر، عندما يكون الصنف الابن هو نوع من أنواع الصنف الأب)، من المفضل استخدام تقنية تدعى التكوّن composition للأصناف ذات العلاقات "لديه has a". التكوّن هو تقنية تصميم لضم الكائنات في الأصناف الخاصة بك بدلًا من توارث أصناف تلك الكائنات. هذا ما نفعله عندما نضيف خاصيّات إلى الأصناف الخاصة بنا.
عند تصميم الأصناف الخاصة بك باستخدام الوراثة فضّل التكوّن على الوراثة، هذا ما كنا نفعله في كل الأمثلة الحالية والسابقة كما يلي:
-
كائن
WizCoin
"لديه has a" كمية من النقود من أنواع galleon و sickle و knut. -
كائن
TTTBoard
"لديه has a" مصفوفة بتسع فراغات. -
كائن
MiniBoard
"هو is a" كائنTTTBoard
لذا "لديه has a" مصفوفة من تسعة فراغات. -
كائن
HintBoard
"هو is a" كائنTTTBoard
لذا "لديه has a" مصفوفة من تسعة فراغات.
لنعد إلى صنف WizCoin
الذي أنشأناه سابقًا. إذا أنشأنا صنف WizardCustomer
لتمثل الزبائن في العالم السحري، يجب على هؤلاء الزبائن حمل كمية من المال، الذي نعبّر عنه بصنف WizCoin
ولكن لا توجد علاقة "is a" بين الصنفين؛ فكائن WizardCustomer
ليس من نوع كائن WizCoin
. إذا استخدمنا الوراثة، سنحصل على شيفرة برمجية غير مُعتادة:
import wizcoin 1 class WizardCustomer(wizcoin.WizCoin): def __init__(self, name): self.name = name super().__init__(0, 0, 0) wizard = WizardCustomer('Alice') print(f'{wizard.name} has {wizard.value()} knuts worth of money.') print(f'{wizard.name}\'s coins weigh {wizard.weightInGrams()} grams.')
في هذا المثال، يرث WizardCustomer
توابع الكائن WizCoin
مثل value()
و weightInGrams()
. تقنيًا يمكن للصنف WizardCustomer
الذي ورث من WizCoin
أن ينجز بجميع المهام التي ينجزها WizardCustomer
، والتي تضم كائن WizCoin
، كما تفعل السمة، ولكن اسمَي التابعين wizard.value()
و wizard.weightInGrams()
مضللة؛ إذ يبدو أنها تُعيد قيمة ووزن الساحر بدلًا من قيمة ووزن نقود الساحر. إضافةً إلى ذلك، إذا أردنا لاحقًا إضافة تابع weightInGrams()
لوزن الساحر، سيكون هذا الاسم مأخوذًا مسبقًا.
من الأسهل أن يكون الكائن WizCoin
سمةً لأن الزبون الساحر "لديه" كميةً من نقود الساحر.
import wizcoin class WizardCustomer: def __init__(self, name): self.name = name 1 self.purse = wizcoin.WizCoin(0, 0, 0) wizard = WizardCustomer('Alice') print(f'{wizard.name} has {wizard.purse.value()} knuts worth of money.') print(f'{wizard.name}\'s coins weigh {wizard.purse.weightInGrams()} grams.')
بدلًا من جعل الصنف WizardCutomer
يرث التوابع من WizCoin
، نعطي للصنف WizardCutomer
سمة purse
التي تحتوي كائن WizCoin
. أي تغييرات لتوابع الصنف WizCoin
عند استخدام التكوّن لن تغير توابع الصنف WizardCustomer
. تمنحك هذه الطريقة مرونةً أكبر في تغيير التصميمات المستقبلية لكلا الصنفين وتؤدي إلى شيفرة سهلة الصيانة.
مساوئ الوراثة
السيئة الأساسية في الوراثة هي أنه أي تغيير مستقبلي يحصل على الأصناف الأب سترثه كل الأصناف الابن. في بعض الحالات هذا الربط الشديد هو ما تحتاجه ولكن في بعض الحالات لا يفي نموذج الوراثة بمتطلبات الشيفرة.
مثلًا، لنقل أنه لدينا الأصناف Car
و Motorcycle
و LunarRover
في برنامج محاكاة عربات، ستحتاج هذه الأصناف إلى توابع متماثلة، مثل startIgnition()
و changeTire()
. بدلًا من نسخ ولصق الشيفرة إلى كل صنف، يمكننا إنشاء صنف أب Vehicle
ونجعل Car
و Motorcycle
و LunarRover
يرثونها. الآن نريد إصلاح خطأ في تابع changeTire()
، وسنجري التغيير في مكان واحد. هذا مفيد جدًا، إذ لدينا العديد من أصناف العربات التي ترث من Vehicle
.
ستكون شيفرة هذه الأصناف على النحو التالي:
class Vehicle: def __init__(self): print('Vehicle created.') def startIgnition(self): pass # Ignition starting code goes here. def changeTire(self): pass # Tire changing code goes here. class Car(Vehicle): def __init__(self): print('Car created.') class Motorcycle(Vehicle): def __init__(self): print('Motorcycle created.') class LunarRover(Vehicle): def __init__(self): print('LunarRover created.')
لكن كل التغييرات المستقبلية على Vehicle
ستؤثر على هذه الأصناف الفرعية أيضًا. ماذا سيحدث لو أردنا تابع changeSparkPlug()
؟ لدى السيارات والدراجات النارية محركات احتراق بشمعات احتراق ولكن العربات القمرية lunar rovers ليس لديها ذلك. يمكننا -بتفضيل التكوّن على الوراثة- إنشاء صنفي CombustionEngine
و ElectricEngine
، ثم تصميم صنف Vehicle
ليكون "لديه has a" سمة محرك إما CombustionEngine
أو ElectricEngine
مع التوابع الموافقة.
class CombustionEngine: def __init__(self): print('Combustion engine created.') def changeSparkPlug(self): pass # هنا الشيفرة البرمجية التي تعدّل على شمعة الاحتراق class ElectricEngine: def __init__(self): print('Electric engine created.') class Vehicle: def __init__(self): print('Vehicle created.') self.engine = CombustionEngine() # استخدم هذا المحرك افتراضيًا --snip-- class LunarRover(Vehicle): def __init__(self): print('LunarRover created.') self.engine = ElectricEngine()
يتطلب هذا إعادة كتابة كمية كبيرة من الشيفرة خصوصًا إذا كان لدينا عدة أصناف ترث من الصنف Vehicle
الموجود مسبقًا. كل استدعاءات vehicleObj.changeSparkPlug()
ستكون بحاجة لتصبح vehicleObj.engine.changeSparkPlug()
لكل كائن في الصنف Vehicle
أو أصنافها الفرعية لأن كل تغيير كبير سيحدث أخطاءً ربما تجعل من التابع changeSparkPlug()
الخاص بالصنف LunarVehicle
لا يفعل شيئًا. تتمثل الطريقة الخاصة ببايثون في هذه الحالة بضبط قيمة changeSparkPlug
إلى None
في صنف LunarVehicle
:
class LunarRover(Vehicle): changeSparkPlug = None def __init__(self): print('LunarRover created.')
يتبع السطر:
changeSparkPlug = None
الصياغة المعرفة في "سمات الصنف" التي سنناقشها لاحقًا، وهذا يعيد تعريف التابع changeSparkPlug()
الموروث من Vehicle
، لذا يسبب استدعاؤه باستخدام كائن LunarRover
خطأ:
>>> myVehicle = LunarRover() LunarRover created. >>> myVehicle.changeSparkPlug() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'NoneType' object is not callable
يؤدي هذا الخطأ إلى فشل البرنامج وتوقفه سريعًا، ويمكننا مباشرةً ملاحظة المشكلة عند استدعاء التابع غير المناسب باستخدام كائن LunarRover
. يرث أيضًا كل صنف ابن للصنف LunarRover
القيمة None
للتابع changeSparkPlug()
.
تخبرنا رسالة الخطأ التالية بأن مبرمج الصنف LunarRover
تعمّد ضبط قيمة التابع changSprakPlug()
إلى None
:
TypeError: 'NoneType' object is not callable
إذا لم يكن هناك بالأساس تابع، سنحصل على رسالة الخطأ التالية:
NameError: name 'changeSparkPlug' is not defined
تخلق الوراثة أصنافًا فيها تعقيدات وتناقضات لذا يُفضل استخدام التكوّن بدلًا عنها.
الخلاصة
الوراثة هي تقنية لإعادة استخدام الشيفرة، تسمح لك إنشاء أصناف ابن التي ترث توابع أصناف الأب، يمكنك إعادة تعريف التوابع لتقدم شيفرةً جديدةً لهم واستخدام super()
لاستدعاء التابع الأصلي من الصنف الأب. لدى الأصناف الابن علاقة "is a" مع الصنف الأب الخاصة بها، لأن كائن من الصنف الابن هو كائن للصنف الأب.
استخدام الأصناف والوراثة في بايثون اختياري، إذ يرى بعض المبرمجين أن التعقيد المرافق للاستخدام الكثير للوراثة لا يبرر فائدته. من المرونة أكثر استخدام التكوّن بدلًا من الوراثة لأنها تنفذ علاقة "has a" مع كائن من أحد الأصناف وكان من أصناف أخرى بدلًا من وراثة التوابع مباشرةً من هذه الأصناف، فمثلًا قد يحتوي كائن Customer
على سمة birthday
المسندة إلى كائن Date
بدلًا من أن يكون هناك أصناف فرعية من صنف Customer
للكائن Date
.
ترجمة -وبتصرف- لقسم من الفصل Object-Oriented Programming And Inheritance من كتاب Beyond the Basic Stuff with Python.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.