آموزش زبان کاتلین – درس ۲۰ (ارث‌بری)

نویسنده : سید ایوب کوکبی ۱۷ آذر ۱۳۹۷

آموزش زبان کاتلین – درس 20 (ارث‌بری)

ارث‌بری (inheritance) یکی از مهم‌ترین ویژگی‌های برنامه‌نویسی شی‌گرا است. این ویژگی به کاربران اجازه می‌دهد تا کلاس جدیدی (کلاس مشتق یا derived class) از کلاس موجود (کلاس پایه یا base class) بسازند. کلاس‌ مشتق تمام ویژگی‌های کلاس پایه را داراست و علاوه بر آن می‌تواند ویژگی‌های جدیدی مختص خود نیز داشته باشد. قبل از بررسی جزئیات، پیشنهاد می‌کنم اگر مقالات پایین را نخوانده‌اید حتماً بخوانید: کلاس‌‌ها و اشیاء و سازنده‌های اولیه در کاتلین.

چرا ارث‌بری؟

فرض کنید داخل برنامه سه کاراکتر math teacher، footballer و businessman وجود دارد. از آنجایی که همه کاراکترها انسان هستند، همگی می‌توانند راه بروند و صحبت کنند. با این حال هر یک از آن‌ها مهارت‌های مخصوص خود را نیز دارا هستند. math teacher به خوبی ریاضی آموزش می‌دهد، footballer به صورت حرفه‌ای فوتبال بازی می‌کند و businessman می‌تواند تجارت کند.

شما می‌توانید سه کلاس مجزا به صورت زیر ایجاد کنید:

همانطور که می‌بینید سه کلاس فوق دارای توابع مشترکی همچون ()walk هستند. این یعنی کد ()walk یا ()talk را باید سه بار (برای هر یک از کلاس‌ها) کپی پیست کنیم. اگر قرار باشد قابلیت جدیدی که بین سه کلاس مشترک است مثلا eat یا خوردن را اضافه کنید باز باید به همین صورت در هر سه کلاس این کار را تکرار کنید. کپی کردن کدها کاری مستعد خطاست و بدتر از آن کدهای تکراری، حجم برنامه را بیهوده کاهش داده و باعث شلوغ شدن منطق آن می‌شوند.

مفهوم ارث‌بری اینجا به دادمان می‌رسد. شما می‌توانید یک کلاس پایه تحت عنوان Person ایجاد کنید. سپس همه‌ی توابع مشترک سه کلاس را در همین کلاس تعریف کنید و سپس در کلاس‌های مشتق شده این توابع را به ارث ببرید. در چنین حالتی به راحتی می‌توانید کلاس جدیدی مثلاً Artist, Developer و … ایجاد کنید و ویژگی‌های مشترک را از کلاس Person به ارث ببرید. مزیت دیگر اث‌بری این است که تمامی تغییرات روی توابع مشترک در یک نقطه‌ی مرکزی یعنی کلاس پایه صورت می‌گیرد. تغییرات حاصل را تمامی کلاس‌های ارث‌برنده دریافت می‌کنند بدون اینکه لازم باشد کار اضافه‌ای انجام دهید.

بخوانید  آموزش زبان کاتلین – درس 16 (آرگومان‌های نام‌گذاری شده و پیش‌فرض)

ارث بری در کاتلین

همانطور که در تصویر بالا می‌بینید. کلاس Person تمامی توابع مشترک سه کلاس را گرد آورده است. هر یک از کلاس‌های مشتق شده توابع مختلف خود را تعریف کرده‌اند. مثلاً MatchTeacher علاوه بر صحبت کردن، قدم زدن و خوردن ()teachMath هم می‌کند. یا فوتبالیست ما ()playFootbal هم می‌کند و … .

ارث‌بری کدها را تمییزتر، جمع‌وجورتر و توسعه‌پذیر می‌کند. به خاطر داشته باشید، در هنگام کار با ارث‌بری هر یک از کلاس‌های مشتق شده باید شرط is a را برای کلاس پایه پاس کنند. مثلاً MatchTeacher is a Person یعنی MatchTeacher باید یک Person باشد. نمی‌شود که Businessman یک Business باشد و از Person ارث‌بری کند. باید نوعش با کلاس پایه بخواند.

ارث‌بری در کاتلین

خب اجازه دهید بحث‌های بالا را در قالب کد پیاده‌سازی کنیم:

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، کامپایلر به شما اجازه می‌دهد تا از این کلاس برای ساخت زیرکلاس استفاده کنید با اصطلاحاً از آن ارث‌بری نمایید.

مثال:

open class Person(age: Int, name: String) {
    init {
        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()
}

خروجی:

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. این توابع تنها از داخل شی‌ای که تعریف شده‌اند قابل دسترسی هستند.

بخوانید  آموزش زبان کاتلین – درس 27 (Object Declarations و Object Expression)

وقتی شی t1 از کلاس MathTeacher ساخته می‌شود:

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 هستند و هر دوی این پارامترها با سازندۀ اولیۀ کلاس پایه مقدار دهی شده‌اند.

مثالی دیگر:

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 سازندۀ کلاس پایه فرخوانی شود یا این کار را به سازندۀ دیگری واگذار کند. برای مثال:
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 تابعی که متعلق به کلاس پایه هست را نشانه‌گذاری کنید.

// 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)
}

خروجی:

My fake age is 26.

دستور (girl.displayAge(31 در اینجا متد ()displayAge از کلاس مشتق یعنی Girl را فراخونی می‌کند چون override شده است.

بخوانید  برنامه‌نویسی پیشرفته اندروید با زبان کاتلین - بخش اول

به همین صورت پراپرتی‌ها را هم می‌توان override کرد. (قبلش بهتر است به درس getter و setter مراجعه کنید):

// Empty primary constructor
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}.")
}

خروجی:

My fake age is 26.

همانطور که می‌بینید از کلیدواژه‌های override و open برای پراپرتی age در کلاس مشتق و کلاس پایه استفاده کرده‌ایم.

فراخوانی اعضای کلاس پایه از داخل کلاس مشتق شده در کاتلین

برای فرخوانی توابع و پراپرتی‌هایِ کلاس پایه از داخل کلاس مشتق شده، کلیدواژۀ super را به کار می‌بریم. به این صورت:

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)
}

خروجی:

My age is 31.
My fake age is 26.

 

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *