سازنده

سازنده‌ها متدهای خاصی هستند که وجود آنها برای ساخت اشیاء لازم است. آن‌ها به شما اجازه می‌دهند که مقادیری را به هر یک از اعضای داده‌ای یک آرایه اختصاص دهید و کدهایی که را که می‌خواهید هنگام ایجاد یک شیء اجرا شوند را به برنامه اضافه کنید. اگر از هیچ سازنده‌ای در کلاستان استفاده نکنید، کامپایلر از سازنده پیشفرض که یک سازنده بدون پارامتر است استفاده می‌کند. می‌توانید در برنامه‌تان از تعداد زیادی سازنده استفاده کنید که دارای پارامترهای متفاوتی باشند. در کاتلین دو نوع سازنده وجود دارد :

Primary Constructor یا سازنده اولیه

در مثال زیر یک کلاس که شامل سازنده اولیه است، را مشاهده می‌کنید:

  1: class Person(var name: String, var age: Int, var height: Double )
  2: {
  3:     fun showInformation()
  4:     {
  5:         println("Name: $name")
  6:         println("Age: $age years old")
  7:         println("Height: $height cm")
  8:     }
  9: }
 10: 
 11: fun main(args: Array<String>)
 12: {
 13:     val firstPerson = Person("Jack", 21, 160.0)
 14:     val secondPerson = Person("Mike", 23, 158.0)
 15: 
 16:     firstPerson.showInformation()
 17:     println() // separator
 18:     secondPerson.showInformation()
 19: }
Name: Jack
Age: 21 years old
Height: 160cm

Name: Mike
Age: 23 years old
Height: 158cm

در خط 1، یک سازنده اولیه تعریف شده است. برای ایجاد سازنده اولیه بعد از نام کلاس علامت باز و بسته را گذاشته و در داخل آنها فیلدها یا خاصیت های کلاس را می نویسید. وقتی که سازنده اولیه ایجاد می کنید، باید آرگومانهایی را هم برای پارامترهای آن در نظر بگیرید. این کار را در خطوط 13 و 14 انجام داده ایم. ابتدا دو شی ایجاد کرده و سپس در داخل پرانتزهای آنها سه آرگومان به سه پارامتر متد سازنده خط 1 اختصاص داده ایم. بعد از ارسال آرگومان ها از خطوط 13 و 14 به خط 1، مقدار این آرگومانها در داخل بدنه متد showInformation و در خطوط 5، 6 و 7 چاپ می شوند. در نتیجه با فراخوانی این متد در خطوط 16 و 18، مقادیر، چاپ می شوند. ساختار یک سازنده اولیه دارای محدودیت هایی است و نمیتوانیم کدی داخل آن بنویسیم ، در عوض با استفاده از بلوک های مقداردهی اولیه ، می توانیم از کد های بیشتری برای مقداری دهی های خود استفاده کنیم. این بلوک ها با کلمه کلیدی init آغاز می شوند. حال می خواهیم مثالی که در بخش قبل بررسی کردیم را با استفاده بلوک های مقداردهی اولیه بنویسیم :

  1: class Person(_name: String, _age: Int, _height: Double)
  2: {
  3:     init
  4:     {
  5:         var name = _name
  6: 
  7:         var age: Int = 0
  8:         if (_age > 0)
  9:         {
 10:             age = _age
 11:         }
 12: 
 13:         var height = _height
 14: 
 15:         println("Name : $name")
 16:         println("Age : $age")
 17:         println("Height : $height \n")
 18:     }
 19: }
 20: 
 21: fun main(args: Array<String>)
 22: {
 23:     val firstPerson = Person("Jack", -21, 160.0)
 24:     val secondPerson = Person("Mike", 23, 158.0)
 25: }
Name : Jack
Age : 0
Height : 160.0 

Name : Mike
Age : 23
Height : 158.0 

همانطور که در خط 1 کد بالا مشاهده می کنید، سه پارامتر برای متد سازنده، بدون کلمات var و val تعریف کرده ایم. این پارامترها، فیلدها یا خاصیت های کلاس نیستند. بلکه خاصیت های کلاس در خطوط 5، 7 و 13 تعریف شده اند. هنگام استفاده از بلوک init، برای ایجاد تفکیک بین پارمترهای متد سازنده و خاصیت های کلاس، به صورت قراردادی قبل از پارامترها علامت زیر خط (_) قرار داده می شود. در خطوط 11-8 کد بالا با استفاده از یک دستور if، گفته ایم که اگر مقدار آرگومان ارسال شده به متد سازنده و برای پارامتر age_ بالاتر از 0 بود آن را در داخل خاصیت age قرار بده. و چون در خط 23 ما مقدار 21- را به این پارامتر ارسال کرده ایم در نتیجه خروجی 0 چاپ می شود. شما می توانید مقادیر پیش فرضی را برای پارامتر های متد سازنده در نظر بگیرید.برای مثال :

  1: class Person(_name: String = "Jack", _age: Int = 21, _height: Double = 160.0)
  2: {
  3:     init
  4:     {
  5:         var name = _name
  6:         var age = _age
  7:         var height = _height
  8: 
  9:         println("Name : $name")
 10:         println("Age : $age")
 11:         println("Height : $height \n")
 12:     }
 13: }
 14: 
 15: fun main(args: Array<String>)
 16: {
 17:     val firstPerson = Person()
 18:     val secondPerson = Person("Mike", 23, 158.0)
 19: }
Name : Jack
Age : 21
Height : 160.0 

Name : Mike
Age : 23
Height : 158.0 

همانطور که در کد بالا مشاهده می کنید با آنکه ما در خط 17 کد بالا هیچ آرگومانی به اولین شیء ایجاد شده، اختصاص نداده ایم، ولی خروجی برنامه برای شیء اول مقادیر پیشفرضی که در خط 1 تعریف کرده ایم را در نظر گرفته است.

Secondary Constructor یا سازنده ثانویه

در کاتلین ، یک کلاس می تواند شامل یک یا چند سازنده ثانویه باشد.این سازنده ها با استفاده از کلمه کلیدی constructor ساخته می شوند. استفاده از سازنده های ثانویه در کاتلین مرسوم نیست.رایج ترین کاربرد استفاده از سازنده های ثانویه زمانی است که شما نیاز دارید تا یک کلاس را بسط دهید و آن کلاس شامل چند سازنده باشد و روش های مختلفی برای مقداردهی آن کلاس وجود داشته باشد. به مثال زیر توجه کنید:

  1: class Person
  2: {
  3:     var name: String
  4:     var age: Int
  5:     var height: Double
  6: 
  7:     constructor()                  : this("No Name", 0, 0.0)
  8:     constructor(n: String)         : this(n, 0, 0.0)
  9:     constructor(n: String, a: Int) : this(n, a, 0.0)
 10:     
 11:     constructor(n: String, a: Int, h: Double)
 12:     {
 13:         name = n
 14:         age = a
 15:         height = h
 16:     }
 17: 
 18:     fun showInformation()
 19:     {
 20:         println("Name: $name")
 21:         println("Age: $age years old")
 22:         println("Height: $height cm\n")
 23:     }
 24: }
 25: 
 26: fun main(args: Array<String>)
 27: {
 28:     val firstPerson = Person()
 29:     val secondPerson = Person("Jack")
 30:     val thirdPerson = Person("Mike", 23)
 31:     val fourthPerson = Person("Chris", 18, 152.0)
 32: 
 33:     firstPerson.showInformation()
 34:     secondPerson.showInformation()
 35:     thirdPerson.showInformation()
 36:     fourthPerson.showInformation()
 37: }
Name: No Name
Age: 0 years old
Height: 0cm

Name: Jack
Age: 0 years old
Height: 0cm

Name: Mike
Age: 23 years old
Height: 0cm

Name: Chris
Age: 18 years old
Height: 152cm

ما چهار سازنده بری اصلاح کلاسمان تعریف کرده‌ایم (خطوط 7، 8، 9، 11). شما می‌توانید تعداد زیادی سازنده برای مواقع لزوم در کلاس داشته باشید. اولین سازنده که هیچ آرگومانی نمی گیرد و مقادیر پیشفرضی را به فیلدهای خطوط 5-3 اختصاص می دهد (خط 7). دومین سازنده یک پارامتر از نوع رشته دریافت می‌کند (خط 8). سومین سازنده دو پارامتر و چهارمین سازنده سه پارامتر می‌گیرد. به چهارمین سازنده در خطوط 16-11 توجه کنید. سه سازنده دیگر به این سازنده وابسته هستند.

کلمه کلیدی this در خطوط 9-7 به شما اجازه می‌دهد که یک سازنده دیگر موجود در داخل کلاس را فراخوانی کنید. در خط 7 یک سازنده پیشفرض بدون پارامتر تعریف شده است و مقادیر پیشفرضی به فیلدها از طریق این سازنده اختصاص داده ایم. چون ما سه مقدار پیشفرض برای فیلدها بعد از کلمه کلیدی this سازنده پیشفرض (خط 7) در نظر گرفته‌ایم، در نتیجه سازنده‌ای که دارای سه پارامتر است (چهارمین سازنده) فراخوانی شده و سه آرگومان به پارامترهای آن ارسال می‌شود. کدهای داخل بدنه چهارمین سازنده اجرا می‌شوند و مقادیر پارامترهای آن به هر یک از اعضای داده‌ای یه همان فیلدهای تعریف شده در خطوط 5-3 اختصاص داده می‌شود. اگر کدی در داخل بدنه سازنده پیشفرض بنویسیم قبل از بقیه کدها اجرا می‌شود. دومین سازنده (خط 8) به یک آرگومان نیاز دارد که همان فیلد name کلاس Person است.

وقتی این پارامتر با یک مقدار رشته‌ای پر شد، سپس به پارامترهای سازنده چهارم ارسال شده و در کنار دو مقدار پیشفرض دیگر (0 برای age و 0 برای height) قرار می‌گیرد. در خط 9 سومین سازنده تعریف شده است که بسیار شبیه دومین سازنده است با این تفاوت که دو پارامتر دارد. مقدار دو پارامتر سومین سازنده به اضافه‌ی یک مقدار پیشفرض صفر برای سومین آرگومان، به چهارمین سازنده ارسال می‌شود.

همانطور که مشاهده می‌کنید با ایجاد چندین سازنده برای یک کلاس، چندین راه برای ایجاد یک شیء بر اساس داده‌هایی که نیاز داریم به وجود می‌آید. در مثال بالا 4 نمونه از کلاس Person ایجاد کرده‌ایم و چهار تغییر در سازنده آن به وجود آورده‌ایم. سپس مقادیر مربوط به فیلدهای هر نمونه را نمایش می‌دهیم. یکی دیگر از موارد استفاده از کلمه کلیدی this این است که، فرض کنید نام پارامترهای سازنده، شبیه نام یکی از فیلدها باشد.

constructor(name: String, age: Int, height: Double)
{
    name = name
    age = age
    height = height
}

این نوع کدنویسی ابهام بر انگیز است و کامپایلر نمی‌تواند متغیر را تشخیص داده و مقداری به آن اختصاص دهد. اینجاست که از کلمه کلیدی this استفاده می‌کنیم.

constructor(name: String, age: Int, height: Double)
{
    this.name = name
    this.age = age
    this.height = height
}

قبل از هر فیلدی کلمه کلیدی this را می‌نویسیم و نشان می‌دهیم که این همان چیزی است که می‌خواهیم به آن مقداری اختصاص دهیم. کلمه کلیدی this ارجاع یک شیء به خودش را نشان می‌دهد. نکته آخر اینکه می توان سازنده اولیه، ثانویه و بلوک init را همزمان در کلاس استفاده کرد. به مثال زیر توجه کنید:

  1: class Person(var name: String)
  2: {
  3:     init
  4:     {
  5:         println("Name: $name")
  6:     }
  7: 
  8:     constructor(name: String, age: Int, height: Double) : this(name)
  9:     {
 10:         println("Age: $age")
 11:         println("Height: $height")
 12:     }
 13: }
 14: 
 15: fun main(args: Array<String>)
 16: {
 17:     Person("Jack")
 18: 
 19:     println()
 20: 
 21:     Person("Mike", 23, 158.0)
 22: }
Name: Jack

Name: Mike
Age: 23
Height: 158.0

همانطور که در کد بالا مشاهده می کنید، ما از سازنده اولیه، ثانویه و بلوک init به طور همزمان استفاده کرده ایم. بلوک init که به سازنده اولیه وابسته است. در نتیجه موقعی که ما در خط 17 کلاس Person را فراخوانی می کنیم و به آن یک آرگومان ارسال می کنیم، این آرگومان به وسیله بلوک init چاپ می شود. در مورد سازنده یا سازنده های ثانویه هم به این نکته توجه کنید که ما هر چند سازنده ثانویه ای که در برنامه داشته باشیم باید با استفاده از کلمه this در جلوی آنها سازنده اولیه را فراخوانی کنیم و این کار را ما در خط 8 انجام داده ایم. با آنکه ما در سازنده خواسته ایم که فقط age و height چاپ شوند (خطوط 10 و 11) ولی چون سازنده اولیه را با this فراخوانی کرده ایم در نتیجه name هم از طریق سازنده اولیه چاپ می شود در نتیجه در خط 21 با ارسال سه آرگومان این مقادیر چاپ می شوند.