شی گرایی در کاتلین: آموزش کلاس و آبجکت (شی) به زبان ساده

شی گرایی در کاتلین: آموزش کلاس و آبجکت (شی) به زبان ساده

توضیحات

با استفاده از ویژگی شی گرایی در برنامه نویسی راحت تر میتونید به توسعه ی اپ های بزرگ و رابط گرافیکی کاربری بپردازید.

معمولا آبجکت یک ماهیت از دنیای واقعی رو شبیه سازی میکنه مثلا یک دانش آموز، یک تلویزیون، یک دایره ، یک مستطیل و هر شی تو دنیای واقعی میتونه با یک آبجکت (object) شبیه سازی بشه.

به آبجکت ها، اشیا نیز گفته میشه اما کلمه ی آبجکت شناخته شده تر است.

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

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

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

ساختار کلاس

برای ایجاد آبجکت نیاز داریم کلاس (class) تعریف کنیم.

با استفاده از کلیدواژه ی class و اسم دلخواه برای کلاس میتونیم یک کلاس تعریف کنیم.

class Name{ ... }

به طور کلی یک کلاس در کاتلین از چهار قسمت تشکیل شده.

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

1- کانستراکتور (سازنده)

t0 :T0 پارامتر کانستراکتور (سازنده) است، یک کانستراکتور مانند متد میتونه یک یا چند پارامتر داشته باشه.

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

2- متغیر ها

هرچندتا متغیر بخوایم با val یا var میتونیم داخل یک کلاس تعریف کنیم. متغیر های داخل کلاس در کاتلین به دیتا فیلد (data field) یا پراپرتی (property) معروف هستند.

3- توابع

میتونیم یک یا چند تابع داخل هر کلاس تعریف کنیم.

4- آبجکت همراه (companion object)

داخل هر کلاس میتونیم فقط یک آبجکت همراه (companion object) داشته باشیم؛ داخل آبجکت همراه میتونیم متغیر ها و متد های استاتیک رو تعریف کنیم.

کانستراکتور (سازنده)

برای تعریف سازنده در کلاس کاتلین از کلیدواژه ی constructor استفاده میکنیم.

با استفاده از کانستراکتور (سازنده) در کاتلین میتونیم یک نمونه از کلاس ایجاد کنیم، کانستراکتور (سازنده) نوع خاصی از تابع است که هیچ مقداری رو بر نمیگردونه و برای ایجاد آبجکت از کلاس باید یک بار ابتدای کار صدا زده بشه.

بدون صدا زدن کانستراکتور (سازنده) نمیتونیم از کلاس نمونه ایجاد کنیم.

در هر کلاس میتونیم یک یا چند کانستراکتور (سازنده) تعریف کنیم و برای ایجاد نمونه از کلاس هرکدومو خواستیم صدا کنیم.

در کاتلین اولین کانستراکتور (سازنده) که هنگام تعریف کلاس بعد از اسم کلاس وجود داره به کانستراکتور (سازنده) اولیه و بقیه کانستراکتور (سازنده) ها به کانستراکتور (سازنده) های ثانویه معروف هستند.

کانستراکتور (سازنده) اولیه

با استفاده از کلیدواژه ی constructor بعد از اسم کلاس میتونیم سازنده ی اولیه در کاتلین ایجاد کنیم.

class MyClass constructor(){
  ...
}
                  

کانستراکتور (سازنده) که در بالا میبینیم به عنوان کنستراکتور اولیه (primary constructor) شناخته میشه. یک کانستراکتور (سازنده) اولیه و چند کانستراکتور (سازنده) ثانویه در کاتلین میتونیم داشته باشیم.

میشه از کلیدواژه های سطح دسترسی در هر کانستراکتور (سازنده)ی که خواستیم استفاده کنیم.

class MyClass private constructor(){

}
                  

بدنه ی کنستراکتور اولیه بلوک init است برای کانستراکتور (سازنده) اولیه در کاتلین هرچندتا بلوک init خواستیم میتونیم تعریف کنیم که به ترتیب موقع اجرای برنامه کد های داخلشون اجرا میشن.

class MyClass(){

  init{
    println("First Block")
  }
 
  init{
    println("Second Block")
  }
 
  init{
    println("Third Block")
  }

}
                 

میتونیم داخل بدنه ی کانستراکتور (سازنده) پارامتر های کانستراکتور (سازنده) رو به متغیر های کلاس اختصاص بدیم.

اگه قبل از کانستراکتور (سازنده) اولیه نخوایم کلیدواژه ی سطح دسترسی تعریف کنیم یا annotation بزاریم میتونیم از نوشتن کلید واژه ی constructor صرف نظر کنیم:

class MyClass constructor(){
  ...
}
                 

در یک کلاس کانستراکتور (سازنده) اولیه به طور پیشفرض وجود داره حتی اگه آشکارا نوشته نشه.

class MyClass{
  
}
                    

کانستراکتور (سازنده) های ثانویه

کانستراکتور (سازنده) های ثانویه (Secondary Constructors) رو داخل کلاس و مانند کانستراکتور (سازنده) اولیه با استفاده از کلیدواژه ی constructor تعریف میکنیم.

در تعریف کانستراکتور (سازنده) های ثانویه استفاده از کلیدواژه ی constructor همیشه لازمه.

بدنه ی کانستراکتور (سازنده) های ثانویه دنباله ی خودشونه و بلوکی مثل init براشون وجود نداره.

class TV{

  constructor(size: Float, panel: String){
    //Execute codes or init properties
  }

}
          

اگه کانستراکتور (سازنده) اولیه آشکارا تعریف شده باشه، هرکدوم از کانستراکتور (سازنده) های ثانویه باید وکالت کانستراکتور (سازنده) اولیه رو مستقیم یا غیر مستقیم با استفاده از کلیدواژه ی this به عهده بگیرن.

به بیان دیگه کانستراکتور (سازنده) اولیه به صورت پیشفرض قبل از سایر کانستراکتور (سازنده) ها صدا زده میشه، اگه کانستراکتور (سازنده) اولیه آشکارا تعریف شده باشه، باید با استفاده از کلیدواژه ی this بعد از پرانتز آشکارا کانستراکتور (سازنده) اولیه رو قبل از کانستراکتور (سازنده) ثانویه صدا بزنیم.

//تعریف آشکار کانستراکتور (سازنده) اولیه بدون پارامتر      
class MyClass(){
  //صدا زدن کانستراکتور (سازنده) اولیه با کلیدواژه ی this
  constructor(...): this(){
    //Execute codes or init properties
  }

}
                    
class TV(size: Float){

  constructor(size: Float, panel: String): this(size){

  }

}
                    

میتونیم پارامتر های یکی از کانستراکتور (سازنده) های ثانویه که وکالت کانستراکتور (سازنده) اولیه رو به عهده داره با کلیدواژه ی this در کانستراکتور (سازنده) جدید مقداردهی کنیم، بدین ترتیب وکالت غیر مستقیم کانستراکتور (سازنده) اولیه رو به کانستراکتور (سازنده) جدید دادیم.

class TV(size: Float){

  constructor(size: Float, panel: String): this(size){

  }

  constructor(size: Float, panel: String, color: String): this(size, panel){

  }
  
}
                    

ایجاد نمونه (آبجکت) از کلاس

برای ایجاد نمونه از کلاس کافیه یکی از کانستراکتور (سازنده) های کلاسو صدا بزنیم.

هنگامی که یک آبجکت (نمونه از کلاس) در حافظه ایجاد میشه متغیر به آدرس آبجکت در حافظه اشاره میکنه و خود متغیر حاوی مقداری نیست، به عبارتی متغیر نقش یک اشاره گر رو بازی میکنه.

مثال

میخوایم یک کلاس به اسم TV تعریف کنیم و ازش سه تا نمونه در تابع main ایجاد کنیم. در این مثال سه تا سازنده (کانستراکتور (سازنده)) در کلاس TV تعریف میکنیم و هر سه کانستراکتور (سازنده) ها رو به ترتیب در تابع main صدا میزنیم.

fun main(){
  val tv0 = TV(21f)
  val tv1 = TV(42.5f, "LCD")
  val tv2 = TV(32f, "LED", "White")
  
}

class TV(size: Float){
  

  constructor(size: Float, panel: String): this(size){

  }

  constructor(size: Float, panel: String, color: String): this(size, panel){

  }
  
}
                    

۲- متغیرهای کلاس (Properties)

میتونیم داخل کلاس متغیر تعریف و مقداردهی کنیم، به متغیر های داخل کلاس دیتا فیلد (field data) یا پراپرتی (property) میگیم. پراپرتی ها گویای وضعیت آبجکت هستند.

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

class TV(size: Float){
   val size: Float
   var isOn: Boolean = false

    init {
      this.size = size
    }

}
                  

در کاتلین بدون نیاز به بلوک init میتونیم پارامتر های کانستراکتور (سازنده) اولیه رو مستقیم به پراپرتی ها اختصاص بدیم.

class TV(size: Float){
  
  private val _size = size

}
                  

همینطور میتونیم پراپرتی ها رو مستقیم به‌جای پارامتر های کانستراکتور (سازنده) اولیه تعریف کنیم، کاتلین به صورت خودکار پارامتر های لازم رو تعریف میکنه و به پراپرتی ها اختصاص میده.

class TV(private val size: Float){

}
                  

توابع setter و getter پراپرتی ها

برای هر پراپرتی دو تابع setter و getter در کاتلین به صورت پیشفرض تعریف شده وقتی بخوایم مقدار جدیدی به پراپرتی بدیم تابع set به طور پنهان صدا زده میشه؛ وقتی بخوایم از پراپرتی استفاده کنیم تابع get به طور پنهان صدا زده میشه.

تعریف آشکار setter و معرفی backing field

میتونیم تابع setter رو برای هر پراپرتی به طور آشکار پیاده سازی کنیم.

 class TV{
  
  var channel: Int = 1
    set(value){
      if(value >= 1 && value <= 120 ){
        field = value
      }
    }

}
                  

در بالا value یک اسم دلخواه برای پارامتر تابع set است و field داخل تابع set به طور خودکار توسط کاتلین ایجاد شده و مقدار پراپرتی در حافظه رو نگه داری میکنه. بهش بکینگ فیلد (backing field) میگیم.

میتونیم با استفاده از کلیدواژه ی private از دسترسی به تابع setter در بیرون کلاس جلوگیری کنیم.

class TV(){
  var isOn: Boolean = false
    private set
      
}
                  

تعریف آشکار getter

میتونیم تابع getter رو برای هر متغیری که خواستیم، به طور آشکار تعریف کنیم.

class MyClass{
    
  var prop: T = ...
    get(){
      ...
      return t
    }

}
                    

اگه مقداری که getter بر میگردونه به صورت تک عبارتی و متغیر تغییرناپذیر (val) باشه، میتونیم getter رو به فرم زیر تعریف کنیم:

class MyClass{
    
  var prop: T get() = t

}
                  

معرفی بکینگ پراپرتی (Backing Property)

اگه متغیر تغییرپذیر (var) باشه و تابع setter پنهان باشه و به هر دلیلی نخواید مقدار متغیر در بیرون از کلاس تغییر کنه میتونید متغیر رو به صورت بکینگ پراپرتی (Backing Property) پیاده سازی کنید.

در این روش با استفاده از کلیدواژه ی سطح دسترسی private از دسترسی به متغیر اصلی در خارج از کلاس جلوگیری میکنیم و با تعریف یک متغیر به عنوان نماینده ی متغیر اصلی به متغیر اصلی در خارج از کلاس با getter دسترسی میدیم. به متغیر اصلی بکینگ پراپرتی (backing property) میگیم.

class TV(){
  
  private var _volumeLevel: Int = 0
  val volumeLevel: Int get() = _volumeLevel


}
                  

صدا زدن پراپرتی

برای صدا زدن پراپرتی در خارج از کلاس کافیه اسم متغیری که به آبجکت اشاره داره رو بنویسیم، نقطه بزاریم و اسم پراپرتی مورد نظر در آبجکت رو بنویسیم.

fun main(){
  val tv = TV()

  println("is tv on? ${tv.isOn}")

  tv.isOn = true

  println("is tv on? ${tv.isOn}")

}

class TV{

  var isOn = false

}
                  

در داخل کلاس هنگامی که متغیر عضو کلاس با یک متغیر دیگه که عضو کلاس هم نام باشه؛ با کلیدواژه ی this در کاتلین باید مشخص کنیم کدوم متغیر عضو کلاس است.

class TV(private val size){

  private val panel: String

  constructor(size, panel){
    this.panel = panel
  }

}
                  

توابع کلاس

میتونیم داخل کلاس تابع تعریف کنیم اگه با توابع در کاتلین آشنایی ندارید میتونید به موضوع توابع مراجعه کنید.

توابع در کلاس نیز از قوانین سطح دسترسی پیروی میکنند.

رفتار (behavior) یک آبجکت با توابع تعریف میشه. وقتی تابع مربوط به آبجکت رو صدا میزنیم تابع از آبجکت میخواد کاری رو انجام بده، مثلا در بخش پراپرتی ها دیدیم تابع getter مقدار پراپرتی رو بر میگردونه یا تابع setter مقدار جدید به پراپرتی اضافه میکنه.

class TV{
  var isOn = false
    private set

  fun turnOn(){
    isOn = true
  }

}
                

صدا زدن تابع کلاس

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

fun main(){
  val tv = TV()

  tv.turnOn()

}
                

آبجکت همراه

آبجکت همراه (companion object) در کاتلین از خانواده ی آبجکت های اعلامی است.

با تعریف آبجکت همراه (companion object) در کلاس میتونیم پراپرتی و توابع مستقل از آبجکت های کلاس تعریف کنیم.

پراپرتی و توابع تعریف شده در companion object مستقل از آبجکت های ایجاد شده از کلاس هستند و مقدار پراپرتی های companion object بین تمام آبجکت های ایجاد شده از کلاس مشترک است.

عموما اعضای companion object رو با دیتا فیلد (data field) و توابع استاتیک در جاوا یکی میدونن با اینکه اعضای companion object چیزی شبیه به اعضای استاتیک در جاوا هستند اما در واقع اعضای companion object استاتیک نیستند.

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

fun main(){

  val first = MyClass()
  first.printCurrentObjectIndex()

  val second = MyClass()
  second.printCurrentObjectIndex()

  val third = MyClass()
  third.printCurrentObjectIndex()


  val fourth = MyClass()
  fourth.printCurrentObjectIndex()

  MyClass.printTotalNumberOfCreatedObjects()

}

class MyClass{

  var index

  init{
    index = count++
  }

  fun printCurrentObjectIndex(){
    println("Index of current object is $index")

  companion object Counter{
    var count = 0
    fun printTotalNumberOfCreatedObjects(){
      println("Total number of objects created by MyClass are $count")
    }
  }
}
                  

اسم Counter اسمی دلخواه برای companion object است.

میتونیم آبجکت های همراه رو نامگذاری نکنیم در این صورت برای صدا زدن اعضای آبجکت همراه باید اسم کلاس نقطه، اسم Companion نقطه و اسم عضو مورد نظر رو بنویسیم:

 fun main(){

  val first = MyClass()
  first.printCurrentObjectIndex()

  val second = MyClass()
  second.printCurrentObjectIndex()

  val third = MyClass()
  third.printCurrentObjectIndex()


  val fourth = MyClass()
  fourth.printCurrentObjectIndex()

  MyClass.Companion.printTotalNumberOfCreatedObjects()

}

class MyClass{

  var index

  init{
    index = count++
  }

  fun printCurrentObjectIndex(){
    println("Index of current object is $index")

  companion object Counter{
    var count = 0
    fun printTotalNumberOfCreatedObjects(){
      println("Total number of objects created by MyClass are $count")
    }
  }
}
                  

مورد مطالعه (مثال ها)

خب الان وقتشه مثال هایی از کلاس و آبجکت ببینیم.

تعریف کلاس تلویزیون و ایجاد نمونه از کلاس.

از قسمت های کلاس TV در بالا برای توضیحات استفاده کردیم الان وقتشه این قسمت ها رو یکجا در قالب یک مثال کامل بیاریم.

fun main(){

    val tv0 = TV(21f)

    tv0.turnOn()
    tv0.printBasicInfo()

    tv0.channel = 25
    tv0.volumeUp()

    println("tv0's channel is ${tv0.channel} and volume level is ${tv0.volumeLevel}")
 
    val tv1 = TV(42.5f, "LCD", "Silver")

    tv1.turnOn()
    tv1.printBasicInfo()

    tv1.channel = 12
    tv1.volumeUp()
    tv1.volumeUp()
    tv1.volumeUp()

    println("tv1's channel is ${tv1.channel} and volume level is ${tv1.volumeLevel}")         
}

class TV(private val size: Float){

  private var panel: String = "Flatiron"
  private var color: String = "Black"

  var isOn: Boolean = false
    private set

  var channel: Int = 1
    set(value){
      if(value > 0 && value <= 120)
        //Backing Field
        field = value
    }
  
  //Backing Property  
  private var _volumeLevel: Int =  1
  val volumeLevel get() = _volumeLevel

  constructor(size: Float, panel: String): this(size){
    this.panel panel
  }


  constructor(size: Float, panel: String, color: String): this(size, panel){

    this.color = color

  }

  fun turnOn(){
    isOn = true
  }

  fun turnOff(){
    isOn = false
  }

  fun channelUp(){
    if(isOn && channel < 120)
      channel++
  }

  fun channelDown(){
    if(isOn && channel > 1)
      channel--
  }

  fun volumeUp(){
    if(isOn && volumeLevel < 100)
      _volumeLevel++
  }

  fun volumeDown(){
    if(isOn && volumeLevel > 0)
      _volumeLevel--
   }

   fun printBasicInfo(){
    println("Television:\nsize: $size\" \npanel: $panel \ncolor: $color")
   }

}
                  

تعریف کلاس مستطیل و ایجاد نمونه از کلاس.

طول و عرض مستطیل ویژگی های مستطیل هستند، مساحت و محیط مستطیل از طول و عرض مستطیل محاسبه میشه بنابراین دوتا پراپرتی برای طول و عرض داریم و دو تابع برای مساحت و محیط مستطیل.

fun main(){
  
  val r0 = Rectangle()
  
  println("Rectangle r0: \n width: ${r0.width} \n height: ${r0.height} \n area: ${r0.area()} \n perimeter: ${r0.perimeter()}")

  val r1 = Rectangle(4, 8)

  println("Rectangle r1: \n width: ${r1.width} \n height: ${r1.height} \n area: ${r1.area()} \n perimeter: ${r1.perimeter()}")

} 
            
class Rectangle(var width: Double = 1.0, var height: Double = 1.0){

  fun area(): Double = width * height
  
  fun perimeter() = 2 * (width + height)

}
                  

تعریف کلاس دایره و ایجاد نمونه از کلاس.

دایره یه شکل هندسیه و محاسبات مساحت و محیط دایره بستگی به مقدار شعاع دایره داره. در این مثال برای محاسبه ی مساحت و محیط بجای تابع از پراپرتی val با getter استفاده میکنیم وقتی پراپرتی تغییر ناپذیر با getter تعریف میشه نقشی شبیه به تابع رو ایفا میکنه.

fun main(){
  val c0 = Circle(2)
  println("c0's radius is ${c0.radius} so the area is ${c0.area}, and the perimeter is ${c0.perimeter})


  c0.radius = 4
  println("Radius is changed to ${c0.radius} so the Area is ${c0.area}, and the Perimeter is ${c0.perimeter})

 
  val c1 = Circle(2)

  println("c1's radius is ${c1.radius} so the Area is ${c1.area}, and the perimeter is ${c1.perimeter})

}


class Circle(){

  var radius: Double = 1.0

  val area: Double get() = radius * radius * PI
  
  var perimeter: Double get() = 2 * radius * PI

    constructor(radius: Double){
      this.radius = radius
    }

}
                  

خلاصه

  • برای ایجاد آبجکت نیاز به کلاس داریم
  • کانستراکتور (سازنده) ها نوع خاصی از تابع هستند که هیچ مقداریو بر نمیگردونن
  • هنگام ایجاد یک آبجکت (نمونه) از کلاس یکی از کانستراکتور (سازنده) های کلاس صدا زده میشه.
  • پراپرتی های کلاس حاوی اطلاعات کلاس هستند و تعیین کننده ی وضعیت کلاس اند.
  • دو تابع getter و setter در کاتلین به صورت پنهان برای هر پراپرتی تعریف میشه.
  • با استفاده از کلیدواژه ی this در داخل کلاس میتونیم رجوع کنیم به پراپرتی های کلاس.
  • توابع در کلاس، رفتار آبجکت رو تعیین میکنن و بهش میگن چکار کنه.
  • با اینکه اعضای آبجکت همراه (companion object) استاتیک نیستند اما شبیه اعضای استاتیک در جاوا عمل میکنن.

بازخورد و دیدگاه‌ ها

اولین نفری باشید که در این صفحه نظر می‌دهید

notifications برای اطلاع از جدیدترین مطالب و پست های مرتبط، عضو کانال تلگرام ما بشید.

arrow_drop_up
کپی شد!