آموزش اصول شیءگرایی کاتلین در برنامهنویسی شیءگرا،ارثبری (Inheritance) و چندریختی (Polymorphism از مفاهیم کلیدی هستند که به توسعهدهندگان امکان ایجاد کدهای قابل استفاده مجدد و انعطافپذیر را میدهند. در زبان کاتلین، ارثبری و چندریختی با ساختارهای ساده و منظم پیادهسازی شده است. در این مقاله، به بررسی این دو مفهوم در کاتلین همراه با مثالهای کاربردی میپردازیم.
ارثبری و چندریختی (Inheritance & Polymorphism)
1. ارثبری (Inheritance)
ارثبری به مفهوم ایجاد یک کلاس فرزند (Subclass) از یک کلاس والد (Superclass) است. کلاس فرزند تمام ویژگیها و رفتارهای کلاس والد را به ارث میبرد و میتواند ویژگیها و متدهای جدیدی را اضافه کند یا متدهای والد را بازنویسی (Override) کند.
در کاتلین، کلاسها به طور پیشفرض نهایی (Final هستند، به این معنی که قابل ارثبری نیستند. برای قابل ارثبری کردن یک کلاس، باید از کلمه کلیدی `open` استفاده کنیم.
مثال: تعریف کلاس والد و فرزند
فرض کنید کلاسی به نام `Animal` داریم که شامل متدی به نام `sound()` برای تولید صدای حیوان است. سپس از این کلاس برای ساخت کلاسهای فرزند `Dog` و `Cat` استفاده میکنیم.
// تعریف کلاس والد
open class Animal {
open fun sound() {
println("Some generic animal sound")
}
}
// تعریف کلاس فرزند Dog که از Animal ارثبری میکند
class Dog : Animal() {
override fun sound() {
println("Woof")
}
}
// تعریف کلاس فرزند Cat که از Animal ارثبری میکند
class Cat : Animal() {
override fun sound() {
println("Meow")
}
}
fun main() {
val animal = Animal()
animal.sound() // خروجی: Some generic animal sound
val dog = Dog()
dog.sound() // خروجی: Woof
val cat = Cat()
cat.sound() // خروجی: Meow
}
در این مثال:
– `Animal` یک کلاس والد است که دارای متدی به نام `sound()` است.
– کلاسهای `Dog` و `Cat` از `Animal` ارثبری میکنند و با استفاده از `override`، متد `sound()` را بازنویسی کردهاند تا صدای خاص خود را تولید کنند.
2. چندریختی (Polymorphism)
چندریختی به این معناست که **یک شیء میتواند به چند شکل مختلف رفتار کند**. در واقع، کلاسهای فرزند میتوانند به عنوان نوع کلاس والد در نظر گرفته شوند و در نتیجه، میتوانند در جایگاهی که نوع والد استفاده شده است، قرار گیرند. این ویژگی به انعطافپذیری برنامه کمک میکند.
مثال: چندریختی با ارثبری
در ادامه مثال بالا، میتوانیم از چندریختی استفاده کنیم تا یک آرایه از `Animal` داشته باشیم که شامل `Dog` و `Cat` باشد و بدون دانستن نوع خاص هر شیء، آنها را مدیریت کنیم.
fun main() {
val animals: Array<Animal> = arrayOf(Dog(), Cat(), Animal())
for (animal in animals) {
animal.sound()
}
}
خروجی:
Woof Meow Some generic animal sound
در اینجا:
– آرایهای از نوع `Animal` داریم که شامل اشیای `Dog` و `Cat` است.
– بدون دانستن نوع دقیق هر شیء، از متد `sound()` برای همه اشیا استفاده میکنیم.
– کاتلین به طور خودکار، نسخه مناسب `sound()` را برای هر نوع شیء فراخوانی میکند.
استفاده از کلمه کلیدی `super`
گاهی اوقات لازم است که در کلاس فرزند، متدی را که بازنویسی شده است، با استفاده از نسخه اصلی کلاس والد نیز فراخوانی کنیم. برای این کار از کلمه کلیدی `super` استفاده میکنیم.
مثال: استفاده از `super` در بازنویسی متد
open class Animal {
open fun sound() {
println("Some generic animal sound")
}
}
class Dog : Animal() {
override fun sound() {
super.sound() // فراخوانی نسخه کلاس والد
println("Woof")
}
}
fun main() {
val dog = Dog()
dog.sound()
}
خروجی:
Some generic animal sound Woof
در این مثال:
– در کلاس `Dog`، متد `sound()` بازنویسی شده است.
– با استفاده از `super.sound()`, متد `sound()` کلاس والد نیز فراخوانی میشود.
ارثبری چند سطحی
در کاتلین، میتوان ارثبری چند سطحی را نیز پیادهسازی کرد. در ارثبری چند سطحی، یک کلاس از کلاسی دیگر که خود از کلاس دیگری ارثبری کرده است، مشتق میشود.
مثال: ارثبری چند سطحی
در این مثال، کلاس `Mammal` از `Animal` ارثبری میکند و سپس `Dog` از `Mammal` ارثبری میکند.
open class Animal {
open fun sound() {
println("Some generic animal sound")
}
}
open class Mammal : Animal() {
override fun sound() {
println("Some generic mammal sound")
}
}
class Dog : Mammal() {
override fun sound() {
println("Woof")
}
}
fun main() {
val dog = Dog()
dog.sound() // خروجی: Woof
val mammal: Mammal = dog
mammal.sound() // خروجی: Woof
val animal: Animal = dog
animal.sound() // خروجی: Woof
}
نتیجهگیری
در کاتلین، ارثبری و چندریختی به توسعهدهندگان اجازه میدهند که کدهای ساختاریافته و قابل استفاده مجدد ایجاد کنند. ارثبری امکان به اشتراکگذاری ویژگیها و رفتارها را بین کلاسها فراهم میکند و چندریختی امکان انعطافپذیری بیشتری در مدیریت اشیا فراهم میآورد. با استفاده از این دو مفهوم، میتوان برنامههایی با معماری قویتر و قابل توسعهتر طراحی کرد.
اینترفیسها و پیادهسازیها (Interfaces & Implementations)
در زبان کاتلین، اینترفیس بهعنوان ساختاری برای تعریف قراردادها و رفتارهای مشترک بین کلاسها استفاده میشود، بدون اینکه کد پیادهسازی واقعی در آن باشد. با استفاده از اینترفیسها میتوان نوعی انتزاع ایجاد کرد که به کلاسها امکان میدهد از رفتارهای مشترکی پیروی کنند و چندریختی (Polymorphism) را تسهیل میکند.
تعریف اینترفیس در کاتلین
برای تعریف یک ینترفیس در کاتلین از کلمه کلیدی `interface` استفاده میشود. اینترفیسها میتوانند شامل متدهای انتزاعی (که در کلاسهای پیادهسازی باید تعریف شوند) ومتدهای دارای پیادهسازی پیشفرض باشند. همچنین میتوانند شامل ویژگیها (پراپرتیها) نیز باشند که کلاسهای پیادهسازی باید آنها را پیادهسازی کنند.
مثال ساده: تعریف اینترفیس
در این مثال، یک اینترفیس به نام `Movable` تعریف میکنیم که دو متد `move()` و `stop()` دارد.
interface Movable {
fun move()
fun stop()
}
در اینجا:
– `Movable` یک اینترفیس است که دو متد `move()` و `stop()` را تعریف کرده است.
– این متدها انتزاعی هستند و هیچ پیادهسازی در اینترفیس ندارند. هر کلاسی که از این اینترفیس استفاده کند، باید این متدها را پیادهسازی کند.
پیادهسازی یک اینترفیس در کلاس
هنگامی که یک کلاس از یک اینترفیس استفاده میکند، باید تمامی متدهای آن را پیادهسازی کند. برای پیادهسازی یک اینترفیس، از علامت `:` پس از نام کلاس استفاده میشود.
مثال: پیادهسازی اینترفیس
در این مثال، کلاسهای `Car` و `Bicycle` اینترفیس `Movable` را پیادهسازی میکنند.
// تعریف اینترفیس Movable
interface Movable {
fun move()
fun stop()
}
// پیادهسازی اینترفیس در کلاس Car
class Car : Movable {
override fun move() {
println("The car is moving")
}
override fun stop() {
println("The car has stopped")
}
}
// پیادهسازی اینترفیس در کلاس Bicycle
class Bicycle : Movable {
override fun move() {
println("The bicycle is moving")
}
override fun stop() {
println("The bicycle has stopped")
}
}
fun main() {
val car: Movable = Car()
car.move() // خروجی: The car is moving
car.stop() // خروجی: The car has stopped
val bicycle: Movable = Bicycle()
bicycle.move() // خروجی: The bicycle is moving
bicycle.stop() // خروجی: The bicycle has stopped
}
ویژگیهای پیشفرض در اینترفیسها
کاتلین به اینترفیسها امکان میدهد که **متدهای دارای پیادهسازی پیشفرض** نیز داشته باشند. این کار باعث میشود که کلاسهای پیادهسازی فقط متدهایی را که لازم دارند بازنویسی کنند و نیازی به پیادهسازی همه متدها نباشد.
مثال: متدهای دارای پیادهسازی پیشفرض
در این مثال، اینترفیس `Playable` شامل متد `play()` و `pause()` با پیادهسازی پیشفرض است.
interface Playable {
fun play() {
println("Playing...")
}
fun pause() {
println("Paused")
}
fun stop() // متد انتزاعی بدون پیادهسازی
}
// پیادهسازی اینترفیس Playable در کلاس VideoPlayer
class VideoPlayer : Playable {
override fun stop() {
println("Stopped")
}
}
fun main() {
val player: Playable = VideoPlayer()
player.play() // خروجی: Playing...
player.pause() // خروجی: Paused
player.stop() // خروجی: Stopped
}
در این مثال:
– متد `play()` و `pause()` در `Playable` دارای پیادهسازی پیشفرض هستند.
– کلاس `VideoPlayer` فقط نیاز به پیادهسازی `stop()` دارد، زیرا `play()` و `pause()` پیادهسازی پیشفرض دارند و نیازی به بازنویسی نیست.
وراثت چندگانه در کاتلین با اینترفیسها
کاتلین از وراثت چندگانه برای اینترفیسها پشتیبانی میکند؛ به این معنی که یک کلاس میتواند چند اینترفیس را پیادهسازی کند. اما اگر چند اینترفیس دارای متدهایی با نامهای مشابه باشند، کلاس پیادهساز باید مشخص کند که از کدام نسخه استفاده میکند.
مثال: پیادهسازی چندین اینترفیس
در این مثال، دو اینترفیس `Printable` و `Scannable` داریم که هر دو شامل متد `execute()` هستند.
interface Printable {
fun execute() {
println("Printing...")
}
}
interface Scannable {
fun execute() {
println("Scanning...")
}
}
// کلاس MultiFunctionDevice که هر دو اینترفیس را پیادهسازی میکند
class MultiFunctionDevice : Printable, Scannable {
override fun execute() {
super<Printable>.execute() // پیادهسازی متد Printable
super<Scannable>.execute() // پیادهسازی متد Scannable
}
}
fun main() {
val device = MultiFunctionDevice()
device.execute()
}
خروجی:
Printing... Scanning...
در این مثال:
– `MultiFunctionDevice` از هر دو اینترفیس `Printable` و `Scannable` استفاده میکند.
– از `super<Printable>.execute()` و `super<Scannable>.execute()` برای فراخوانی متدهای هر اینترفیس استفاده میکنیم.
نتیجهگیری
اینترفیسها در کاتلین ابزاری قدرتمند برای تعریف رفتارهای مشترک بین کلاسها و تسهیل چندریختی هستند. با امکان تعریف متدهای دارای پیادهسازی پیشفرض و پشتیبانی از وراثت چندگانه، اینترفیسها به توسعهدهندگان کمک میکنند که کدهایی ساختارمند، منظم و قابل استفاده مجدد ایجاد کنند.
کلاسهای انتزاعی (Abstract Classes)
در کاتلین، کلاسهای انتزاعی (Abstract Classes) کلاسهایی هستند که نمیتوان از آنها مستقیماً شیءسازی کرد و فقط برای ایجاد ساختار پایهای برای کلاسهای فرزند مورد استفاده قرار میگیرند. این کلاسها ممکن است شامل متدهای انتزاعی (بدون پیادهسازی) و متدهای معمولی (با پیادهسازی) باشند. کلاسهای فرزند باید متدهای انتزاعی را پیادهسازی کنند.
کلاسهای انتزاعی برای ایجاد یک چارچوب کلی برای کلاسهای خاصتر و فراهم کردن امکان پیادهسازی متدها و ویژگیهای مشترک مفید هستند.
تعریف کلاس انتزاعی
برای تعریف یک کلاس انتزاعی در کاتلین، از کلمه کلیدی `abstract` استفاده میکنیم. کلاسهای انتزاعی میتوانند شامل:
– متدهای انتزاعی که توسط کلاسهای فرزند پیادهسازی میشوند.
– متدهای غیرانتزاعی (با پیادهسازی) که تمام کلاسهای فرزند میتوانند از آنها استفاده کنند.
تفاوت کلاس انتزاعی و اینترفیس
– کلاس انتزاعی میتواند شامل ویژگیها و متدهای پیادهسازی شده و انتزاعی باشد و همچنین میتواند وضعیت (state) ذخیره کند.
– اینترفیس فقط شامل تعریف رفتارهاست و حالت ذخیره نمیکند (اگرچه از ویژگیهای پیادهسازی شده پشتیبانی میکند).
مثال: تعریف کلاس انتزاعی
فرض کنید میخواهیم یک ساختار پایه برای موجودات مختلفی مثل **پرندگان** و **ماهیها** ایجاد کنیم. یک کلاس انتزاعی به نام `Animal` تعریف میکنیم که متدی انتزاعی به نام `move()` و متدی با پیادهسازی به نام `breathe()` دارد.
// تعریف کلاس انتزاعی Animal
abstract class Animal(val name: String) {
abstract fun move() // متد انتزاعی که باید توسط کلاسهای فرزند پیادهسازی شود
fun breathe() {
println("$name is breathing")
}
}
// کلاس Bird که از Animal ارثبری میکند و متد move را پیادهسازی میکند
class Bird(name: String) : Animal(name) {
override fun move() {
println("$name is flying")
}
}
// کلاس Fish که از Animal ارثبری میکند و متد move را پیادهسازی میکند
class Fish(name: String) : Animal(name) {
override fun move() {
println("$name is swimming")
}
}
fun main() {
val bird = Bird("Sparrow")
bird.breathe() // خروجی: Sparrow is breathing
bird.move() // خروجی: Sparrow is flying
val fish = Fish("Goldfish")
fish.breathe() // خروجی: Goldfish is breathing
fish.move() // خروجی: Goldfish is swimming
}
در این مثال:
– `Animal` یک **کلاس انتزاعی** است که دارای یک **متد انتزاعی** `move()` و یک متد عادی `breathe()` است.
– `Bird` و `Fish` کلاسهای فرزندی هستند که از `Animal` ارثبری میکنند و **متد انتزاعی `move()`** را پیادهسازی کردهاند.
– میتوانیم از متد `breathe()` که پیادهسازی شده است در هر دو کلاس فرزند بدون بازنویسی آن استفاده کنیم.
استفاده از ویژگیها (Properties) در کلاسهای انتزاعی
در کلاسهای انتزاعی، میتوانیم ویژگیهای انتزاعی را نیز تعریف کنیم که کلاسهای فرزند باید آنها را پیادهسازی کنند.
مثال: ویژگی انتزاعی در کلاس انتزاعی
abstract class Shape {
abstract val area: Double // ویژگی انتزاعی که باید توسط کلاسهای فرزند پیادهسازی شود
abstract fun draw() // متد انتزاعی برای رسم شکل
}
class Circle(val radius: Double) : Shape() {
override val area: Double
get() = Math.PI * radius * radius
override fun draw() {
println("Drawing a circle with radius $radius")
}
}
class Rectangle(val width: Double, val height: Double) : Shape() {
override val area: Double
get() = width * height
override fun draw() {
println("Drawing a rectangle with width $width and height $height")
}
}
fun main() {
val circle = Circle(5.0)
println("Area of circle: ${circle.area}")
circle.draw()
val rectangle = Rectangle(4.0, 6.0)
println("Area of rectangle: ${rectangle.area}")
rectangle.draw()
}
خروجی:
Area of circle: 78.53981633974483 Drawing a circle with radius 5.0 Area of rectangle: 24.0 Drawing a rectangle with width 4.0 and height 6.0
در این مثال:
– `Shape` یک کلاس انتزاعی است که شامل یک ویژگی انتزاعی به نام `area` و یک متد انتزاعی `draw()` است.
– کلاسهای `Circle` و `Rectangle` ویژگی `area` را پیادهسازی کرده و متد `draw()` را نیز بر اساس نیاز خود پیادهسازی کردهاند.
نتیجهگیری
کلاسهای انتزاعی در کاتلین امکان ایجاد ساختارهای قابل توسعه و سازماندهی شده را فراهم میکنند. با استفاده از کلاسهای انتزاعی، میتوانیم کدهایی با پایهای مشترک و انعطافپذیر برای پیادهسازی در کلاسهای فرزند داشته باشیم، و از انتزاع برای ایجاد معماریهایی با سلسله مراتب پیچیده استفاده کنیم.
برابری و تغییر ناپذیری اشیاء (Object Equality & Immutability)
در کاتلین، متغیرها به دو دسته کلی تقسیم میشوند:
1. متغیرهای تغییرپذیر (Mutable Variables):
این متغیرها با کلمه کلیدی `var` تعریف میشوند. مقدار این متغیرها پس از مقداردهی اولیه میتواند تغییر کند. به عبارتی، میتوان در طول اجرای برنامه مقادیر مختلفی را به آنها اختصاص داد.
2. متغیرهای تغییرناپذیر (Immutable Variables):
این متغیرها با کلمه کلیدی `val` یا `const` تعریف می
شوند و پس از مقداردهی اولیه، نمیتوان مقدار آنها را تغییر داد. این متغیرها به محض اینکه مقداردهی شدند، مقدارشان ثابت میماند.
تعریف متغیرهای تغییرپذیر (`var`)
این متغیرها میتوانند در طول اجرای برنامه تغییر کنند. اگر نیاز دارید متغیری داشته باشید که در طول برنامه مقدار آن تغییر کند، از `var` استفاده کنید.
var age = 25 println(age) // خروجی: 25 age = 30 println(age) // خروجی: 30
تعریف متغیرهای تغییرناپذیر (`val`)
متغیرهای `val` پس از مقداردهی اولیه دیگر نمیتوانند مقدارشان تغییر کند. این نوع متغیر برای مواردی مناسب است که میخواهیم مقدار ثابتی داشته باشیم.
val name = "Ali" println(name) // خروجی: Ali // این خط کد خطا میدهد چون name تغییرناپذیر است // name = "Reza"
تعریف متغیرهای ثابت با `const val`
`const val` نوع خاصی از `val` است که در زمان **کامپایل** مقداردهی میشود و فقط در سطح **تاپلول** (Top-Level) یا **اشیاء (Objects)** قابل استفاده است. این متغیرها فقط میتوانند مقادیر **ثابت و بدون محاسبات** را ذخیره کنند.
const val PI = 3.14159 println(PI) // خروجی: 3.14159
تفاوت `val` و `const val`
– `val` در زمان اجرا مقداردهی میشود، بنابراین میتواند مقدارهایی را بپذیرد که در زمان اجرا محاسبه میشوند.
– `const val`در زمان کامپایل مقداردهی میشود و فقط برای مقادیر ثابت استفاده میشود.
جمعبندی
در کاتلین:
– از `var` برای متغیرهای تغییرپذیر استفاده کنید.
– از `val` برای متغیرهای تغییرناپذیر که در زمان اجرا مقداردهی میشوند استفاده کنید.
– از `const val` برای مقادیر ثابت و بدون تغییر در زمان کامپایل استفاده کنید.
