گاهی شما نیاز دارید یک رفتار مشترک یا خصوصیت برای اشتراک گذاری بین چند کلاس مرتبط به یکدیگر تعریف کنید. کاتلین دو راه برای انجام این کار پیشنهاد می کند، رابط ها (interfaces) و کلاس های انتزاعی (abstract classes). در این تکلیف شما یک کلاس انتزاعی AquariumFish برای خصوصیات مشترک همه ماهی ها می سازید. شما یک رابط به نام FishAction برای تعریف رفتار معمول همه ماهی ها تعریف می کنید.
کلاس های انتزاعی همیشه open هستند؛ شما نیازی به علامت زدن آنها با open ندارید. خصوصیات و متدهای یک کلاس انتزاعی غیر انتزاعی هستند مگر اینکه شما به طور صریح آنها را با کلمه کلیدی abstract علامت بزنید. این یعنی زیر کلاس ها می توانند آنطور که داده شده از آنها استفاده کنند. اگر خصوصیات یا متدها انتزاعی باشند زیر کلاس ها باید آن را پیاده سازی کنند.
گام ۱. یک کلاس انتزاعی بسازید
۱. زیر example.myapp یک فایل جدید به نام AquariumFish.kt بسازید.
۲. یک کلاس جدید به نام AquariumFish بسازید و آن را با abstract علامت بزنید.
۳. یک خصوصیت String به نام color اضافه کنید و آن را با abstract علامت بزنید.
package example.myapp abstract class AquariumFish { abstract val color: String }
۴. دو زیر کلاس از AquariumFish به نام های Shark و Plecostomus بسازید.
۵. چون color انتزاعی است زیر کلاس ها باید آن را پیاده سازی کنند. Shark را خاکستری و Plecostomus را طلایی کنید.
class Shark: AquariumFish() { override val color = "gray" } class Plecostomus: AquariumFish() { override val color = "gold" }
۶. در main.kt، یک تابع ()makeFish برای امتحان کردن کلاس های تان بسازید. Shark و Plecostomus را معرفی کنید و بعد رنگ هر کدام را چاپ کنید.
۷. کد امتحانی پیشین در ()main را حذف کنید و ()makeFish را صدا بزنید. کد شما باید مانند کد زیر باشد.
فایل main.kt:
package example.myapp fun makeFish() { val shark = Shark() val pleco = Plecostomus() println("Shark: ${shark.color}") println("Plecostomus: ${pleco.color}") } fun main () { makeFish() }
۸. برنامه را اجرا و خروجی را مشاهده کنید.
⇒ Shark: gray Plecostomus: gold
نمودار زیر کلاس Shark و کلاس Plecostomus را نمایش می دهد، که از کلاس انتزاعی AquariumFish زیر کلاس شدند.
گام ۲. یک رابط بسازید
۱. در AquariumFish.kt یک رابط FishAction با یک متد ()eat بسازید.
interface FishAction { fun eat() }
۲. رابط FishAction را به هر کدام از زیر کلاس ها اضافه کنید، و ()eat را با چاپ کردن کاری که ماهی انجام میدهد پیاده سازی کنید.
class Shark: AquariumFish(), FishAction { override val color = "gray" override fun eat() { println("hunt and eat fish") } } class Plecostomus: AquariumFish(), FishAction { override val color = "gold" override fun eat() { println("eat algae") } }
۳. در تابع ()makeFish کاری کنید که هر ماهی که ساختید با صدا زدن تابع ()eat چیزی بخورد.
fun makeFish() { val shark = Shark() val pleco = Plecostomus() println("Shark: ${shark.color}") shark.eat() println("Plecostomus: ${pleco.color}") pleco.eat() }
۴. برنامه را اجرا و خروجی را مشاهده کنید.
⇒ Shark: gray hunt and eat fish Plecostomus: gold eat algae
نمودار پایین کلاس Shark و کلاس Plecostomus را نمایش می دهد، هر دوی آنها از رابط FishAction استفاده می کنند و آن را پیاده سازی می کند.
چه زمانی از کلاس های انتزاعی و چه زمانی از رابط ها استفاده کنید
مثال های بالا ساده هستند، اما وقتی شما تعداد زیادی کلاسهای به هم وابسته داشته باشید کلاس های انتزاعی و رابط ها می توانند به شما کمک کنند تا طراحی تمیزتر، سازماندهی شده تر و با نگهداری آسان تر داشته باشید.
همانطور که در بالا اشاره شد کلاس های انتزاعی می توانند سازنده داشته باشند، و رابطها نمیتوانند، اما به جز این بسیار شبیه هستند. پس چه زمانی باید از هرکدام از آنها استفاده کنید؟
وقتی از رابط ها برای ساخت یک کلاس استفاده می کنید عملکرد کلاس توسط نمونه کلاسهایی که در آن قرار دارد تعمیم می یابد. ترکیب بندی برای آسان تر ساختن استفاده دوباره از کد است و از ارث بری از یک کلاس انتزاعی بهتر است. همچنین شما می توانید از رابطهای چندگانه داخل یک کلاس استفاده کنید اما فقط میتوانید از کلاس انتزاعی زیر کلاس بسازید.
وقتی در کاتلین برنامه طراحی میکنید به این فکر کنید که چگونه می توانید از ترکیبات برای ساختن یک اپلیکیشن توسط بخش های سازنده کوچک استفاده کنید.
ترکیب بندی معمولاً به کپسول سازی بهتر، کاهش وابستگی متقابل، رابطههای تمیزتر و کدهای کاربردی تر منجر می شود. به این دلایل استفاده از ترکیب بندی با رابط ها طراحی مقدم است. در طرف دیگر ارث بری از یک کلاس انتزاعی یک راه حل طبیعی برای برخی مسائل است. شما باید از ترکیب بندی استفاده کنید اما وقتی ارث بری در کاتلین مفهوم دارد پس بیایید آن را هم انجام دهیم!
اگر تعداد زیادی متد و یک یا دو پیاده سازی پیش فرض دارید از رابط استفاده کنید برای مثال در AquariumAction زیر:
interface AquariumAction { fun eat() fun jump() fun clean() fun catchFish() fun swim() { println("swim") }
هرگاه نمی توانید یک کلاس را کامل کنید از یک کلاس انتزاعی استفاده کنید. برای مثال، وقتی به کلاس AquariumFish برمیگردید میتوانید کاری کنید همه AquariumFish ها FishAction را پیاده سازی کند یک پیاده سازی پیش فرض برای eat ارائه میدهد در حالی که color را انتزاعی می کنید زیرا واقعا رنگ پیش فرضی برای ماهی وجود ندارد.
interface FishAction { fun eat() } abstract class AquariumFish: FishAction { abstract val color: String override fun eat() = println("yum") }