چند ریختی و کست کردن در کاتلین

چند ریختی و کست کردن در کاتلین

توضیحات

سه رکن شی گرایی عبارتند از کپسوله کردن (encapsulation)، وراثت (inheritance) و چند ریختی (polymorphism).

بعضی از برنامه نویس ها abstraction هم به عنوان رکن چهارم شی گرایی در نظر میگیرن که اینجا موضوع بحث ما نیست.

در دو بخش قبل با کپسوله کردن (Encapsulation) و وراثت (inheritance) آشنا شدیم در این بخش میخوایم با رکن سوم شی گرایی یعنی چند ریختی (Polymorphism) آشنا بشیم. و سپس به پیوند پویا (Dynamic Binding) و کست کردن (Type Casting) که با چند ریختی مرتبط هستند می پردازیم.

برای پرداختن به پیوند پویا (dynamic binding) و کست کردن (type casting) ابتدا لازمه با چند ریختی (polymorphism) در کاتلین آشنا بشیم.

چند ریختی (Polymorphism)

چند ریختی یعنی متغیر از جنس یکی از superclass ها اشاره کنه به آبجکت ایجاد شده از subclass

همینطور که در قسمت قبل (وراثت) گفته شد subclass ویژگی های superclass رو به ارث میبره به علاوه ویژگی های جدید خودشو میتونه داشته باشه پس میشه گفت subclass یک کلاس اختصاصی از superclass است.

هر دایره شکل هندسیه اما هر شکل هندسی دایره نیست (میتونه مستطیل باشه).

بنابراین هر دایره علاوه بر ویژگی های یک شکل هندسی ویژگی های اختصاصی خودشم داره که دایره رو از بقیه شکل های هندسی متمایز میکنه.

با توجه به این استدلال میشه گفت هر متغیر از GeometricShape میتونه به هر آبجکت از Circle یا Rectangle اشاره کنه اما برعکسش نمیشه.

در بخش قبل کلاس های Circle، Rectangle و GeometricShape تعریف شدن. همینطور کلاس GeometricShape از Shape نیز ارث بری میکنه.

در مثال زیر برای خلاصه شدن از دوباره نویسیشون خودداری کردیم میتونید به بخش قبل مراجعه و در مورد مطالعه کد هاشونو کپی کنید.

fun displayObject(object: GeometricShape){ println("Created on $dateCreated. Color is $color and Stroke is $stroke" } fun main(){ val object0: GeometricShape = Circle(4, "red", "yellow", false) println("Created on ${object0.dateCreated}. Color is ${object0.color} and Stroke is ${object0.stroke}") val object1: GeometricShape = Rectangle(3, 4) println("Created on ${object1.dateCreated}. Color is ${object1.color} and Stroke is ${object1.stroke}") }

در مثال زیر کلاس Person سوپر کلاس Student و Citizen است.

open class Person(val name: String, val age: Int) { val dateCreated = java.util.Date() } class Student(name: String, age: Int, val studyField: String): Person(name, age) class Citizen(name: String, age: Int, val job: String): Person(name, age) fun printMessage(person: Person){ println("Created on ${person.dateCreated}, name is ${person.name} and age is ${person.age}") } fun main(){ printMessage(Student("Jim", "Computer Science", 26)) printMessage(Citizen("Rose", "Business Manager", 35)) }

پیوند پویا (Dynamic Binding)

یک تابع در طی ارث بری میتونه به چند روش پیاده‌سازی بشه زمان اجرای برنامه jvm تصمیم میگیره کدوم پیاده‌سازی به اجرا در بیاد.

یک تابع میتونه در superclass تعریف بشه و در subclass باز نویسی بشه به عنوان مثال تابع toString در کلاس Any تعریف شده و چون کلاس Any سوپر کلاس تمام کلاس هاست تابع toString میتونه در هر کلاسی بازنویسی و به طور اختصاصی پیاده‌سازی بشه.

به قطعه کد زیر توجه کنید:

val shape: Any = Shape("blue", "yellow", false) println(shape.toString())

سؤال: هنگام اجرای کد کدوم toString اجرا میشه؟ اونی که در Shape بازنویسی شده یا اونی که در Any تعریف شده؟

برای پاسخ به این سؤال ابتدا با دو مفهوم نوع تعریف شده (declared type) و نوع واقعی (actual type) باید اشنا بشیم.

نوع تعریف شده (declared type)

هنگام تعریف متغیر اگه نوع متغیر رو مشخص کنیم بهش declared type میگیم در بالا declared type کلاس Any است.

نوع واقعی (actual type)

به آبجکتی که ایجاد میکنیم نوع واقعی (actual type) میگیم. actual type میتونه نوعش با declared type یکی باشه یا یکی از subclass های declared type باشه. در بالا actual type کلاس Shape است.

بهتره برگردیم به سوالمون اینکه کدوم toString اجرا میشه؟

جواب: بستگی به actual type داره.

زمان اجرا JVM از actual type شروع میکنه به بررسی تا به declared type برسه و به اولین پیاده‌سازی تابع که رسید بررسی رو متوقف و تابع رو اجرا میکنه. در بالا چون اولین پیاده‌سازی toString داخل Shape اتفاق افتاده همونو اجرا میکنه.

مثال

open class Person: Any{ override fun toString(): String { return "Person" } } open class Student: Person{ override fun toString(): String { return "Student" } } class GraduateStudent: Student{ } fun m(x: Object){ println(x.toString()) } fun main(){ m(GraduateStudent()) m(Student()) m(Person()) m(Any()) }

کست کردن (Type Casting)

تبدیل آبجکت به یک آبجکت از نوع دیگه رو کست کردن (type casting) میگیم.

فرض کنید declared type سوپر کلاس actual type است.

به عبارتی یک متغیر تعریف میکنیم که نوع متغیر superclass آبجکتی است که بهش داره اشاره میکنه.

در این حالت هنگام صدا زدن پراپرتی و توابع با استفاده از متغیر فقط توابع و پراپرتی هایی که در superclass تعریف شدن در دسترس هستند.

گاهی در کد نیاز داریم از توابع و پراپرتی های actual type در کد استفاده کنیم برای همین رو میاریم به کست کردن declared type به actual type.

برای کست کردن (type casting) از کلیدواژه ی as در کاتلین استفاده میکنیم.

مثال

open class Shape(var color: String ,var stroke: String, var isFilled: Boolean = false){ val dateCreated = java.util.Date() override fun toString(): String{ return "Shape created at $dateCreated \ncolor is $color and stroke is $stroke \nisFilled? $isFilled" } } fun main(){ val o: Any = Shape("blue", "indigo", true) println(o.toString()) //Casting Any to Shape (o as Shape).isFilled = false (o as Shape).color = "amber" println(o.toString()) }

در این حالت کامپایلر اخطار میده که ممکنه o به Shape در حافظه اشاره نکنه و این کست کردن safe نیست. به هر حال کست کردن انجام میشه و برنامه بدون مشکل اجرا میشه.

میتونیم با استفاده از is بررسی کنیم declared type سوپر کلاس actual type هست یا خیر.

open class Person(val name: String, val age: Int) { val dateCreated = java.util.Date() } class Student(name: String, age: Int, val studyField: String): Person(name, age) class Citizen(val name: String, age: Int, val job: String): Person(name, age) fun displayData(object: Any){ if(object is Student){ //Casting Any to Student val student = (object as Student) println("Student: \nName: " + student.name + " Age: " + student.age + " Field of study " + student.studyField) }else if(object is Citizen){ //Casting Any to Citizen val citizen = (object as Citizen) println("Citizen: \n Name: " + citizen.name + " Age: " + citizen.age + " Job: " + citizen.job) } } fun main(){ displayData(Student("Jim", "Computer Science", 26) displayData(Citizen("Rose", "Business Manager", 35) }

کاتلین از ویژگی کست هوشمند (smart casting) بهره می بره یعنی اگه با is معلوم بشه declared type به کدوم actual type داره اشاره میکنه دیگه نیاز به کست کردن آشکار نیست.

fun displayData(object: Any){ if(object is Student){ println("Student: \nName: " + object.name + " Age: " + object.age + " Field of study " + object.studyField) }else if(object is Citizen){ println("Citizen: \n Name: " + object.name + " Age: " + object.age + " Job: " + object.job) } }

خلاصه

- سه رکن شی گرایی عبارتند از encapsulation، inheritance، polymorphism

- چند ریختی یعنی یک متغیر از جنس superclass اشاره کنه به آبجکتی از جنس subclass

- هنگامی که یک تابع صدا زده میشه JVM از actual type شروع میکنه به بررسی و اولین پیاده‌سازی تابع رو اجرا میکنه.

- کست کردن یعنی تبدیل آبجکت از یک نوع به آبجکتی از نوع دیگه.

- کاتلین از ویژگی کست هوشمند یا (smart casting) پشتیبانی میکنه.

arrow_drop_up
کپی شد!