در این مقاله، مبحث ارث بری را مرور می کنیم و به بررسی ارثبری چندگانه (Multiple Inheritance) و چگونگی پیادهسازی آن میپردازیم.
در برنامهنویسی شیءگرا یا OOP مفهومی به نام ارثبری Inheritanceداریم. مفهوم ارثبری به برنامهنویسان این امکان را میدهد که کلاس جدیدی (مثلا فرزند) را بر اساس کلاس موجود (یا اصطلاحا والد) تعریف کنند و ویژگیها و رفتارهای کلاس والد را به کلاس فرزند منتقل کنند. در واقع، کلاس فرزند میتواند از کلاس والد به عنوان یک پایه برای ساختار خود استفاده کند.
ارثبری چندگانه به برقراری ارتباط بین یک کلاس فرزند و چندین کلاس والد می پردازد. جایی که کلاس فرزند ویژگیها و عملکردهای همه کلاسهای والد خود را به ارث میبرد. این ویژگی به برنامهنویس این امکان را میدهد که از ویژگیهای مختلف کلاسهای والد برای ایجاد کلاس یا کلاس های فرزند استفاده کنند.
قبل از تشریح موضوع با ذکر مثال , گفتن این نکته حائز اهمیت است که در زبان دارت ,به طور مستقیم امکان ارث بری چندگانه مقدور نیست. چون در زبان دارت امکان ارثبری چندگانه با ابهام یا (Diamond Inheritance) وجود ندارد ما نمیتوانیم چنین کدی را به زبان دارت بنویسیم :
class A { void show() { print('کلاس A'); } } class B extends A { void show() { print('کلاس B'); } } class C extends A { void show() { print('کلاس C'); } } class D extends B with C {} void main() { var obj = D(); obj.show(); // خطا: ارثبری چندگانه با ابهام }
اما به دو روش امکان پیاده سازی آن وجود دارد :
1- با استفاده از mixin ها
2- با استفاده از کلاس های abstract
در ادامه هر دو روش پیادهسازی را با حل یک مساله بررسی خواهیم کرد.
صورت مساله:
ما نیاز داریم یک برنامه بنویسیم که دو نفر با جنسیت و شغل مشخص شده را به عنوان دو شیء از کلاس Human ایجاد کند و سپس با فراخوانی تابعintroduce()، جملهای را چاپ کند که جنسیت و شغل هر فرد را نمایش دهد.
پیادهسازی ارثبری چندگانه با میکسینها:
میکسینها کلاسهایی هستند که میتوانند ویژگیها و متدهایی را شامل شوند و سپس این میکسینها را به یک یا چند کلاس فرزند ( به کمک عبارت کلیدی with ) اضافه می کنیم و به این ترتیب، کلاس های فرزند قابلیت استفاده از ویژگیها و متدهای mixin را خواهد داشت.
mixin GenderMixin { String _gender = '' ; String getGender() { return _gender; } void setGender(String gender) { _gender = gender; } } mixin JobMixin { String _job = ''; String getJob() { return _job; } void setJob(String job) { _job = job; } } class Human with GenderMixin, JobMixin { Human(String gender, String job) { setGender(gender); setJob(job); } void introduce() { print('من یک ${getGender()} هستم و شغل من ${getJob()} است.'); } } void main() { var person1 = Human('مرد', 'مهندس'); var person2 = Human('زن', 'پزشک'); person1.introduce(); // خروجی: من یک مرد هستم و شغل من مهندس است. person2.introduce(); // خروجی: من یک زن هستم و شغل من پزشک است. }
پیادهسازی با آبسترکت کلاس:
آبسترکت کلاسها (Abstract Classes) در Dart نیز برای پیادهسازی ارثبری چندگانه قابل استفاده هستند و این امکان را فراهم میکنند که یک کلاس از چند کلاس آبسترکت به عنوان والد ارثبری کند. این امر امکان اشتراک ویژگیها و متدها بین چند کلاس را بدون نیاز به ارثبری چندگانه فراهم میکند.
abstract class Gender { String getGender(); void setGender(String gender); } abstract class Job { String getJob(); void setJob(String job); } class Human implements Gender, Job { String _gender = ''; String _job = '';
@override String getGender() { return _gender; } @override void setGender(String gender) { _gender = gender; } @override String getJob() { return _job; } @override void setJob(String job) { _job = job; } void introduce() { print('من یک ${getGender()} هستم و شغل من ${getJob()} است.'); }
وراثت مکانیزمی است که به برنامه نویسان اجازه می دهد تا کلاس هایی را ایجاد کنند که بر اساس کلاس های موجود ساخته شده اند، پیاده سازی جدیدی را با حفظ رفتارهای مشابه مشخص کنند، از کدها استفاده مجدد کنند و به طور مستقل نرم افزار اصلی را از طریق کلاس ها و رابط های عمومی گسترش دهند. وراثت علی رغم سودمندی که در برنامه نویسی شی گرا دارد , می تواند یکی از موارد پیچیده و مستعد خطا نیز باشد.
مورد اول - Method overriding :
این مورد زمانی اتفاق می افتد که یک زیر کلاس پیاده سازی خود را از طریق متد یا فانکشنی ارائه می کند که قبلاً توسط کلاس والد آن ارائه شده است.
مورد دوم- Diamond problem :
این زمانی اتفاق می افتد که دو ابر کلاس از یک کلاس یک کلاس پایه مشترک داشته باشند. این مشکل می تواند منجر به ابهام و سردرگمی در سلسله مراتب وراثت شود.
مورد سوم- Method overloading :
این مورد زمانی اتفاق می افتد که یک کلاس دو یا چند متد با نام یکسان اما پارامترهای متفاوت دارد.
برای جلوگیری از این مسائل، رعایت بهترین شیوه ها هنگام طراحی و اجرای برنامه های شی گرا مهم است. که در ادامه به چند مورد از آنها اشاره می کنیم .
از سلسله مراتب ارثی عمیق اجتناب کنید:
سلسله مراتب وراثت عمیق می تواند درک و نگهداری کد را دشوار کند. در عوض، سعی کنید سلسله مراتب ارثی خود را کم عمق و ساده نگه دارید.
از ترکیب به جای وراثت استفاده کنید:
به جای تلاش برای ارثبری از چند کلاس مختلف و ترتیبهای پیچیده، از ترکیب کلاسها به عنوان یک راه حل ساده و قابل درک استفاده کنید. شما میتوانید از ترکیب به جای وراثت استفاده کنید و اشیاء پیچیده را از کلاس های ساده تری بسازید. این حالت می تواند انعطاف پذیرتر و راحت تر از وراثت قابل درک باشد.
از اصل جایگزینی لیسکوف پیروی کنید:
این اصل بیان میکند که اگر برنامهای از یک کلاس پایه استفاده میکند، باید بتواند از هر یک از کلاسهای مشتق شده خود بدون اطلاع از آن استفاده کند. به عنوان مثال، اگر یک کلاس پایه به نام "حیوان" داشته باشیم و دو کلاس مشتق شده به نامهای "شیر" و "گربه" داشته باشیم، اگر این دو کلاس مشتق شده توانایی جایگزینی کلاس "حیوان" را داشته باشند و تمام ویژگیها و توابع مورد نیاز برای این جایگزینی را داشته باشند، میتوانیم از هر کدام از این دو کلاس بجای کلاس "حیوان" در برنامهی خود استفاده کنیم. این میتواند به ما امکان افزودن انعطافپذیری به برنامه و استفاده آسان از ارثبری چندگانه را بدهد.
در مجموعه مقالات برنامهنویسی شیءگرا، مانند مقالات SOLID، برای هر یک از اصول برنامهنویسی شیءگرا (OOP)، مثالهایی با استفاده از زبان برنامهنویسی Dart خواهیم داشت که نمونههای صحیح و نمونههای نادرست پیادهسازی این اصول را نشان میدهیم.
مقالات مرتبط:
پیاده سازی OOP با کدهای دارت برای فریم ورک Flutter
پیاده سازی ارث بری در OOP با کدهای دارت برای فریم ورک Flutter-بخش اول