وراثت
Kotlin Inheritance
مفهوم وراثت را در دنیایی که در آن دنیای واقعی میدانید. از نظر بیولوژیکی، نوع خون و رنگ چشم، محصول ژنهایی هستند که از والدین به ارث برده میشوند . بسیاری از رفتارهای شما نیز از والدینتان به ارث میرسد.
چرا ارثبری؟
فرض کنید داخل برنامه سه کاراکتر math teacher، footballer و businessman وجود دارد. از آنجایی که همه کاراکترها انسان هستند، همگی میتوانند راه بروند و صحبت کنند. با این حال هر یک از آنها مهارتهای مخصوص خود را نیز دارا هستند. math teacher به خوبی ریاضی آموزش میدهد، footballer به صورت حرفهای فوتبال بازی میکند و businessman میتواند تجارت کند.
همانطور که میبینید سه کلاس فوق دارای توابع مشترکی همچون ()walk هستند. این یعنی کد ()walk یا ()talk را باید سه بار (برای هر یک از کلاسها) کپی پیست کنیم. اگر قرار باشد قابلیت جدیدی که بین سه کلاس مشترک است مثلا eat یا خوردن را اضافه کنید باز باید به همین صورت در هر سه کلاس این کار را تکرار کنید. کپی کردن کدها کاری مستعد خطاست و بدتر از آن کدهای تکراری، حجم برنامه را بیهوده افزایش داده و باعث شلوغ شدن منطق آن میشوند.
مفهوم ارثبری اینجا به دادمان میرسد. شما میتوانید یک کلاس پایه تحت عنوان Person ایجاد کنید. سپس همهی توابع مشترک سه کلاس را در همین کلاس تعریف کنید و سپس در کلاسهای مشتق شده این توابع را به ارث ببرید. در چنین حالتی به راحتی میتوانید کلاس جدیدی مثلاً Artist, Developer و … ایجاد کنید و ویژگیهای مشترک را از کلاس Person به ارث ببرید. مزیت دیگر اثبری این است که تمامی تغییرات روی توابع مشترک در یک نقطهی مرکزی یعنی کلاس پایه صورت میگیرد. تغییرات حاصل را تمامی کلاسهای ارثبرنده دریافت میکنند بدون اینکه لازم باشد کار اضافهای انجام دهید.
ارثبری در کاتلین
خب اجازه دهید بحثهای بالا را در قالب کد پیادهسازی کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 | open class Person(age: Int) { // code for eating, talking, walking } class MathTeacher(age: Int): Person(age) { // other features of math teacher } class Footballer(age: Int): Person(age) { // other features of footballer } class Businessman(age: Int): Person(age) { // other features of businessman } |
اینجا Person یک کلاس پایه بوده و کلاسهای MathTeacher, Footballer و Buisinessman کلاسهای مشتق شده از Person هستند. به کلمۀ open قبل از کلاس پایه Person توجه کنید. مهم است.
در کاتلین به صورت پیشفرض کلاسها در کاتلین final هستند. اگر با جاوا آشنا باشید میدانید که از کلاسهای final نمیتوان زیرکلاس ایجاد کرد. با استفاده از کلمهی open، کامپایلر به شما اجازه میدهد تا از این کلاس برای ساخت زیرکلاس استفاده کنید با اصطلاحاً از آن ارثبری نمایید.
مثال:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | println("My name is $name.") println("My age is $age") } } class MathTeacher(age: Int, name: String): Person(age, name) { fun teachMaths() { println("I teach in primary school.") } } class Footballer(age: Int, name: String): Person(age, name) { fun playFootball() { println("I play for LA Galaxy.") } } fun main(args: Array<String>) { val t1 = MathTeacher(25, "Jack") t1.teachMaths() println() val f1 = Footballer(29, "Christiano") f1.playFootball() } |
خروجی:
1 2 3 4 5 6 | My name is Jack. My age is 25 I teach in primary school. My name is Cristiano. My age is 29 I play for LA Galaxy. |
اینجا دو کلاس MathTeacher و Footballer از کلاس Person مشتق شدهاند. سازندۀ اولیۀ Person دو پراپرتی age و name را تعریف کرده است و یک initializer block هم دارد. این بلاک (و توابع عضوِ) کلاس پایۀ Person میتواند توسط کلاسهای مشتق شده مورد استفاده قرار گیرد. کلاسهای مشتق شده MathTeacher و Footballer توابع عضو مختص خود را نیز دارند: ()teachMaths و ()playFootball. این توابع تنها از داخل شیای که تعریف شدهاند قابل دسترسی هستند.
وقتی شی t1 از کلاس MathTeacher ساخته میشود:
1 | val t1 = MathTeacher(25, "Jack") |
پارامترهایش به سازندۀ اولیه پاس داده میشوند. در کاتلین، بلاک init زمانی فرخوانی میشود که آبجکت ما ساخته شده باشد. بنابراین MathTeacher از کلاس Person مشتق میشود و داخل کلاس پایه (Person) دنبال initializer Block میگردد، سپس اچرایش میکند. اگر MathTeacher این بلاک را داشت، کامپایلر آن را هم اجرا میکرد. سپس تابع ()teachMaths برای شی t1 فرخوانی میشود. این کار با دستور ()t1.teachMaths انجام گرفته است.
برنامه برای شی f1 از کلاس Footballer هم اینگونه رفتار میکند. بعد از ساخت این شی بلاک initializer کلاس پایه اجرا شده سپس متد ()playFootbal از کلاس Footballer با دستور ()f1.playFootball فرخوانی شده است.
نکات مهم ارثبری در زبان کاتلین
- اگر کلاسی حاوی سازندۀ اولیه بود، کلاس پایه بایستی با پارامترهای سازندۀ اولیه مقداردهی شود. در برنامۀ بالا، هر دو کلاس مشتق شده دارای دو پارامتر age و name هستند و هر دوی این پارامترها با سازندۀ اولیۀ کلاس پایه مقدار دهی شدهاند.
مثالی دیگر:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | open class Person(age: Int, name: String) { // some code } class Footballer(age: Int, name: String, club: String): Person(age, name) { init { println("Football player $name of age $age and plays for $club.") } fun playFootball() { println("I am playing football.") } } fun main(args: Array<String>) { val f1 = Footballer(29, "Cristiano", "LA Galaxy") } |
اینجا سازندۀ اولیۀ کلاس مشتق شده دارای ۳ پارامتر و کلاس پایه دارای دو پارامتر است. توجه کنید که هر دو پارامتر کلاس پایه مقداردهی اولیه شدهاند.
- وقتی هیچ سازندۀ اولیهای وجود نداشته باشد، بایستی با کلمهی super سازندۀ کلاس پایه فرخوانی شود یا این کار را به سازندۀ دیگری واگذار کند. برای مثال:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | fun main(args: Array<String>) { val p1 = AuthLog("Bad Password") } open class Log { var data: String = "" var numberOfData = 0 constructor(_data: String) { } constructor(_data: String, _numberOfData: Int) { data = _data numberOfData = _numberOfData println("$data: $numberOfData times") } } class AuthLog: Log { constructor(_data: String): this("From AuthLog -> + $_data", 10) { } constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) { } } |
Override کردن توابع عضو و پراپرتیها در کاتلین
اگر کلاس پایه و کلاس مشتق حاوی تابع عضو یا پراپرتی همنامی باشند باید تابع عضو کلاس مشتق را با کلمهی override کنید و با کلمهی open تابعی که متعلق به کلاس پایه هست را نشانهگذاری کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Empty primary constructor open class Person() { open fun displayAge(age: Int) { println("My age is $age.") } } class Girl: Person() { override fun displayAge(age: Int) { println("My fake age is ${age - 5}.") } } fun main(args: Array<String>) { val girl = Girl() girl.displayAge(31) } |
خروجی:
1 | My fake age is 26. |
دستور (girl.displayAge(31 در اینجا متد ()displayAge از کلاس مشتق یعنی Girl را فراخونی میکند چون override شده است.
به همین صورت پراپرتیها را هم میتوان override کرد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | open class Person() { open var age: Int = 0 get() = field set(value) { field = value } } class Girl: Person() { override var age: Int = 0 get() = field set(value) { field = value - 5 } } fun main(args: Array<String>) { val girl = Girl() girl.age = 31 println("My fake age is ${girl.age}.") } |
خروجی:
1 | My fake age is 26. |
همانطور که میبینید از کلیدواژههای override و open برای پراپرتی age در کلاس مشتق و کلاس پایه استفاده کردهایم.
فراخوانی اعضای کلاس پایه از داخل کلاس مشتق شده در کاتلین
برای فرخوانی توابع و پراپرتیهایِ کلاس پایه از داخل کلاس مشتق شده، کلیدواژۀ super را به کار میبریم. به این صورت:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | open class Person() { open fun displayAge(age: Int) { println("My actual age is $age.") } } class Girl: Person() { override fun displayAge(age: Int) { // calling function of base class super.displayAge(age) println("My fake age is ${age - 5}.") } } fun main(args: Array<String>) { val girl = Girl() girl.displayAge(31) } |
خروجی:
1 2 | My age is 31. My fake age is 26. |
Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance Kotlin Inheritance