Kotlin Bootcamp for Programmers 4: Object-oriented programming

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Kotlin Bootcamp สำหรับโปรแกรมเมอร์ คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ คุณอาจข้ามบางส่วนได้ ทั้งนี้ขึ้นอยู่กับความรู้ของคุณ หลักสูตรนี้เหมาะสำหรับโปรแกรมเมอร์ที่รู้จักภาษาเชิงวัตถุและต้องการเรียนรู้ Kotlin

บทนำ

ใน Codelab นี้ คุณจะได้สร้างโปรแกรม Kotlin และเรียนรู้เกี่ยวกับคลาสและออบเจ็กต์ใน Kotlin เนื้อหาส่วนใหญ่จะคุ้นเคยกันดีหากคุณรู้จักภาษาเชิงวัตถุอื่น แต่ Kotlin มีความแตกต่างที่สำคัญบางอย่างเพื่อลดปริมาณโค้ดที่คุณต้องเขียน นอกจากนี้ คุณยังได้เรียนรู้เกี่ยวกับคลาสแบบนามธรรมและการมอบสิทธิ์อินเทอร์เฟซด้วย

บทเรียนในหลักสูตรนี้ได้รับการออกแบบมาเพื่อสร้างความรู้ของคุณ แต่จะมีความเป็นอิสระจากกันในระดับหนึ่งเพื่อให้คุณข้ามส่วนที่คุณคุ้นเคยได้ แทนที่จะสร้างแอปตัวอย่างเพียงแอปเดียว ตัวอย่างหลายรายการใช้ธีมตู้ปลาเพื่อเชื่อมโยงตัวอย่างต่างๆ เข้าด้วยกัน และหากต้องการดูเรื่องราวทั้งหมดของตู้ปลา ให้ดูหลักสูตร Kotlin Bootcamp for Programmers ของ Udacity

สิ่งที่คุณควรทราบอยู่แล้ว

  • พื้นฐานของ Kotlin รวมถึงประเภท ตัวดำเนินการ และการวนซ้ำ
  • ไวยากรณ์ฟังก์ชันของ Kotlin
  • ข้อมูลพื้นฐานเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ
  • พื้นฐานของ IDE เช่น IntelliJ IDEA หรือ Android Studio

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างคลาสและเข้าถึงพร็อพเพอร์ตี้ใน Kotlin
  • วิธีสร้างและใช้ตัวสร้างคลาสใน Kotlin
  • วิธีสร้างคลาสย่อยและวิธีการทำงานของการรับค่า
  • เกี่ยวกับคลาสนามธรรม อินเทอร์เฟซ และการมอบสิทธิ์อินเทอร์เฟซ
  • วิธีสร้างและใช้คลาสข้อมูล
  • วิธีใช้ Singleton, Enum และ Sealed Class

สิ่งที่คุณต้องดำเนินการ

  • สร้างคลาสที่มีพร็อพเพอร์ตี้
  • สร้างตัวสร้างสำหรับคลาส
  • สร้างคลาสย่อย
  • ดูตัวอย่างของคลาสและอินเทอร์เฟซแบบนามธรรม
  • สร้างคลาสข้อมูลอย่างง่าย
  • ดูข้อมูลเกี่ยวกับ Singleton, Enum และ Sealed Class

คุณควรคุ้นเคยกับคำศัพท์ด้านการเขียนโปรแกรมต่อไปนี้อยู่แล้ว

  • คลาสคือพิมพ์เขียวสำหรับออบเจ็กต์ ตัวอย่างเช่น Aquarium คลาสคือพิมพ์เขียวสำหรับการสร้างออบเจ็กต์ตู้ปลา
  • ออบเจ็กต์คืออินสแตนซ์ของคลาส โดยออบเจ็กต์ตู้ปลาคือAquariumจริง 1 ตู้
  • พร็อพเพอร์ตี้คือลักษณะของคลาส เช่น ความยาว ความกว้าง และความสูงของ Aquarium
  • เมธอด หรือที่เรียกว่าฟังก์ชันสมาชิก คือฟังก์ชันการทำงานของคลาส เมธอดคือสิ่งที่คุณ "ทำ" กับออบเจ็กต์ได้ เช่น คุณสามารถfillWithWater()Aquariumออบเจ็กต์ได้
  • อินเทอร์เฟซคือข้อกำหนดที่คลาสสามารถนำไปใช้ได้ ตัวอย่างเช่น การทำความสะอาดเป็นเรื่องปกติสำหรับออบเจ็กต์อื่นๆ นอกเหนือจากตู้ปลา และโดยทั่วไปแล้วการทำความสะอาดออบเจ็กต์ต่างๆ จะมีลักษณะคล้ายกัน ดังนั้น คุณอาจมีอินเทอร์เฟซที่ชื่อ Clean ซึ่งกำหนดเมธอด clean() Aquarium คลาสสามารถใช้Cleanอินเทอร์เฟซเพื่อทำความสะอาดตู้ปลาด้วยฟองน้ำนุ่มๆ
  • แพ็กเกจเป็นวิธีจัดกลุ่มโค้ดที่เกี่ยวข้องเพื่อให้เป็นระเบียบ หรือเพื่อสร้างไลบรารีโค้ด เมื่อสร้างแพ็กเกจแล้ว คุณจะนำเข้าเนื้อหาของแพ็กเกจไปยังไฟล์อื่น และนำโค้ดและคลาสในแพ็กเกจกลับมาใช้ซ้ำได้

ในงานนี้ คุณจะได้สร้างแพ็กเกจและคลาสใหม่ที่มีพร็อพเพอร์ตี้และเมธอดบางอย่าง

ขั้นตอนที่ 1: สร้างแพ็กเกจ

แพ็กเกจช่วยให้คุณจัดระเบียบโค้ดได้

  1. ในแผงโปรเจ็กต์ ภายใต้โปรเจ็กต์ Hello Kotlin ให้คลิกขวาที่โฟลเดอร์ src
  2. เลือกใหม่ > แพ็กเกจ แล้วตั้งชื่อว่า example.myapp

ขั้นตอนที่ 2: สร้างคลาสที่มีพร็อพเพอร์ตี้

คลาสจะกำหนดด้วยคีย์เวิร์ด class และชื่อคลาสโดยทั่วไปจะขึ้นต้นด้วยตัวพิมพ์ใหญ่

  1. คลิกขวาที่แพ็กเกจ example.myapp
  2. เลือก New > Kotlin File / Class
  3. ในส่วนประเภท ให้เลือกชั้นเรียน แล้วตั้งชื่อชั้นเรียนเป็น Aquarium IntelliJ IDEA จะรวมชื่อแพ็กเกจไว้ในไฟล์และสร้างคลาส Aquarium ที่ว่างเปล่าให้คุณ
  4. ภายในคลาส Aquarium ให้กำหนดและเริ่มต้นพร็อพเพอร์ตี้ var สำหรับความกว้าง ความสูง และความยาว (เป็นเซนติเมตร) เริ่มต้นพร็อพเพอร์ตี้ด้วยค่าเริ่มต้น
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

เบื้องหลัง Kotlin จะสร้าง Getter และ Setter สำหรับพร็อพเพอร์ตี้ที่คุณกำหนดไว้ในคลาส Aquarium โดยอัตโนมัติ คุณจึงเข้าถึงพร็อพเพอร์ตี้ได้โดยตรง เช่น myAquarium.length

ขั้นตอนที่ 3: สร้างฟังก์ชัน main()

สร้างไฟล์ใหม่ชื่อ main.kt เพื่อเก็บฟังก์ชัน main()

  1. ในบานหน้าต่างโปรเจ็กต์ทางด้านซ้าย ให้คลิกขวาที่แพ็กเกจ example.myapp
  2. เลือก New > Kotlin File / Class
  3. ในเมนูแบบเลื่อนลงประเภท ให้เลือกไฟล์ แล้วตั้งชื่อไฟล์เป็น main.kt IntelliJ IDEA จะรวมชื่อแพ็กเกจ แต่ไม่รวมคำจำกัดความคลาสสำหรับไฟล์
  4. กำหนดฟังก์ชัน buildAquarium() และสร้างอินสแตนซ์ของ Aquarium ภายใน หากต้องการสร้างอินสแตนซ์ ให้อ้างอิงคลาสราวกับว่าเป็นฟังก์ชัน Aquarium() ซึ่งจะเรียกตัวสร้างของคลาสและสร้างอินสแตนซ์ของคลาส Aquarium คล้ายกับการใช้ new ในภาษาอื่นๆ
  5. กำหนดmain()ฟังก์ชันและเรียกใช้buildAquarium()
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

ขั้นตอนที่ 4: เพิ่มวิธีการ

  1. ในคลาส Aquarium ให้เพิ่มเมธอดเพื่อพิมพ์พร็อพเพอร์ตี้มิติข้อมูลของตู้ปลา
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. ใน main.kt ใน buildAquarium() ให้เรียกใช้เมธอด printSize() ใน myAquarium
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. เรียกใช้โปรแกรมโดยคลิกสามเหลี่ยมสีเขียวข้างฟังก์ชัน main() สังเกตผลลัพธ์
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. ใน buildAquarium() ให้เพิ่มโค้ดเพื่อตั้งค่าความสูงเป็น 60 และพิมพ์คุณสมบัติของมิติข้อมูลที่เปลี่ยนแปลง
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

ในงานนี้ คุณจะได้สร้างตัวสร้างสำหรับคลาสและทำงานกับพร็อพเพอร์ตี้ต่อไป

ขั้นตอนที่ 1: สร้างตัวสร้าง

ในขั้นตอนนี้ คุณจะเพิ่มตัวสร้างให้กับคลาส Aquarium ที่สร้างไว้ในงานแรก ในตัวอย่างก่อนหน้า อินสแตนซ์ทั้งหมดของ Aquarium จะสร้างขึ้นโดยใช้มิติข้อมูลเดียวกัน คุณเปลี่ยนขนาดได้เมื่อสร้างแล้วโดยการตั้งค่าพร็อพเพอร์ตี้ แต่การสร้างขนาดที่ถูกต้องตั้งแต่แรกจะง่ายกว่า

ในภาษาโปรแกรมบางภาษา ตัวสร้างจะกำหนดโดยการสร้างเมธอดภายในคลาสที่มีชื่อเดียวกับคลาส ใน Kotlin คุณจะกำหนดตัวสร้างได้โดยตรงในการประกาศคลาสเอง โดยระบุพารามิเตอร์ภายในวงเล็บราวกับว่าคลาสเป็นเมธอด พารามิเตอร์เหล่านั้นจะมีค่าเริ่มต้นได้เช่นเดียวกับฟังก์ชันใน Kotlin

  1. ในคลาส Aquarium ที่คุณสร้างไว้ก่อนหน้านี้ ให้เปลี่ยนคำจำกัดความของคลาสให้มีพารามิเตอร์ของตัวสร้าง 3 รายการที่มีค่าเริ่มต้นสำหรับ length, width และ height แล้วกำหนดพารามิเตอร์เหล่านั้นให้กับพร็อพเพอร์ตี้ที่เกี่ยวข้อง
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. วิธี Kotlin ที่กระชับกว่าคือการกำหนดพร็อพเพอร์ตี้โดยตรงด้วยตัวสร้างโดยใช้ var หรือ val และ Kotlin ยังสร้างตัวรับและตัวตั้งค่าโดยอัตโนมัติด้วย จากนั้นคุณจะนำคำจำกัดความของพร็อพเพอร์ตี้ในเนื้อหาของคลาสออกได้
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. เมื่อสร้างออบเจ็กต์ Aquarium ด้วยตัวสร้างนั้น คุณจะระบุอาร์กิวเมนต์หรือไม่ก็ได้เพื่อรับค่าเริ่มต้น หรือจะระบุเพียงบางอาร์กิวเมนต์ หรือจะระบุทั้งหมดเพื่อสร้าง Aquarium ที่มีขนาดกำหนดเองทั้งหมดก็ได้ ในฟังก์ชัน buildAquarium() ให้ลองสร้างออบเจ็กต์ Aquarium ด้วยวิธีต่างๆ โดยใช้พารามิเตอร์ที่มีชื่อ
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

โปรดสังเกตว่าคุณไม่จำเป็นต้องโอเวอร์โหลดตัวสร้างและเขียนเวอร์ชันที่แตกต่างกันสำหรับแต่ละกรณีเหล่านี้ (รวมถึงอีก 2-3 กรณีสำหรับชุดค่าผสมอื่นๆ) Kotlin จะสร้างสิ่งที่จำเป็นจากค่าเริ่มต้นและพารามิเตอร์ที่มีชื่อ

ขั้นตอนที่ 2: เพิ่มบล็อก init

ตัวอย่างคอนสตรัคเตอร์ด้านบนเพียงแค่ประกาศพร็อพเพอร์ตี้และกำหนดค่าของนิพจน์ให้กับพร็อพเพอร์ตี้นั้น หากตัวสร้างต้องการโค้ดการเริ่มต้นเพิ่มเติม คุณสามารถวางโค้ดไว้ในบล็อก init อย่างน้อย 1 บล็อกได้ ในขั้นตอนนี้ คุณจะเพิ่มบล็อก init บางส่วนลงในคลาส Aquarium

  1. ในAquariumคลาส ให้เพิ่มบล็อก init เพื่อพิมพ์ว่าออบเจ็กต์กำลังเริ่มต้น และเพิ่มบล็อกที่ 2 เพื่อพิมพ์ปริมาตรเป็นลิตร
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

โปรดสังเกตว่าระบบจะเรียกใช้บล็อก init ตามลำดับที่ปรากฏในคำจำกัดความของคลาส และจะเรียกใช้ทั้งหมดเมื่อมีการเรียกใช้ตัวสร้าง

ขั้นตอนที่ 3: ดูข้อมูลเกี่ยวกับตัวสร้างรอง

ในขั้นตอนนี้ คุณจะได้เรียนรู้เกี่ยวกับตัวสร้างรองและเพิ่มตัวสร้างรองลงในคลาส นอกจากตัวสร้างหลักซึ่งมีบล็อก init อย่างน้อย 1 บล็อกแล้ว คลาส Kotlin ยังมีตัวสร้างรองอย่างน้อย 1 ตัวเพื่ออนุญาตการโอเวอร์โหลดตัวสร้าง ซึ่งก็คือตัวสร้างที่มีอาร์กิวเมนต์ต่างกัน

  1. ในคลาส Aquarium ให้เพิ่มตัวสร้างรองที่รับจำนวนปลาเป็นอาร์กิวเมนต์โดยใช้คีย์เวิร์ด constructor สร้างvalพร็อพเพอร์ตี้แท็งก์สำหรับปริมาตรที่คำนวณแล้วของตู้ปลาเป็นลิตรตามจำนวนปลา สมมติว่าใช้ปริมาณน้ำ 2 ลิตร (2,000 ลูกบาศก์เซนติเมตร) ต่อปลา 1 ตัว และมีพื้นที่เหลืออีกเล็กน้อยเพื่อไม่ให้น้ำหก
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. ในตัวสร้างรอง ให้คงความยาวและความกว้าง (ซึ่งตั้งค่าไว้ในตัวสร้างหลัก) ไว้เหมือนเดิม และคำนวณความสูงที่จำเป็นเพื่อให้แท็งก์มีปริมาตรตามที่กำหนด
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. ในฟังก์ชัน buildAquarium() ให้เพิ่มการเรียกเพื่อสร้าง Aquarium โดยใช้ตัวสร้างรองใหม่ พิมพ์ขนาดและปริมาณ
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

โปรดสังเกตว่าระบบจะพิมพ์ค่าของตัวแปร volume 2 ครั้ง ครั้งแรกโดยinitในตัวสร้างหลักก่อนที่จะเรียกใช้ตัวสร้างรอง และครั้งที่ 2 โดยโค้ดใน buildAquarium()

คุณอาจใส่คีย์เวิร์ด constructor ไว้ในตัวสร้างหลักด้วยก็ได้ แต่ในกรณีส่วนใหญ่แล้วไม่จำเป็น

ขั้นตอนที่ 4: เพิ่มตัวรับพร็อพเพอร์ตี้ใหม่

ในขั้นตอนนี้ คุณจะเพิ่มตัวรับพร็อพเพอร์ตี้ที่ชัดเจน Kotlin จะกำหนดตัวรับและตัวตั้งค่าโดยอัตโนมัติเมื่อคุณกำหนดพร็อพเพอร์ตี้ แต่บางครั้งคุณอาจต้องปรับหรือคำนวณค่าสำหรับพร็อพเพอร์ตี้ เช่น ด้านบน คุณพิมพ์ปริมาณของ Aquarium คุณทำให้ระดับเสียงพร้อมใช้งานเป็นพร็อพเพอร์ตี้ได้โดยการกำหนดตัวแปรและตัวรับสำหรับพร็อพเพอร์ตี้นั้น เนื่องจากต้องคำนวณ volume ตัวรับจึงต้องแสดงค่าที่คำนวณแล้ว ซึ่งคุณทำได้ด้วยฟังก์ชันแบบบรรทัดเดียว

  1. ในคลาส Aquarium ให้กำหนดพร็อพเพอร์ตี้ Int ที่ชื่อ volume และกำหนดเมธอด get() ที่คำนวณปริมาตรในบรรทัดถัดไป
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. นำinitบล็อกที่พิมพ์ระดับเสียงออก
  2. นำโค้ดใน buildAquarium() ที่พิมพ์ระดับเสียงออก
  3. ในprintSize() ให้เพิ่มบรรทัดเพื่อพิมพ์ระดับเสียง
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

ขนาดและปริมาณจะเหมือนเดิม แต่ระบบจะพิมพ์ปริมาณเพียงครั้งเดียวหลังจากที่ทั้งตัวสร้างหลักและตัวสร้างรองเริ่มต้นออบเจ็กต์อย่างสมบูรณ์แล้ว

ขั้นตอนที่ 5: เพิ่มตัวตั้งค่าพร็อพเพอร์ตี้

ในขั้นตอนนี้ คุณจะสร้างตัวตั้งค่าพร็อพเพอร์ตี้ใหม่สําหรับระดับเสียง

  1. ในAquariumคลาส ให้เปลี่ยน volume เป็น var เพื่อให้ตั้งค่าได้มากกว่า 1 ครั้ง
  2. เพิ่มตัวตั้งค่าสำหรับพร็อพเพอร์ตี้ volume โดยเพิ่มเมธอด set() ไว้ใต้ตัวรับค่า ซึ่งจะคำนวณความสูงใหม่ตามปริมาณน้ำที่ระบุ ตามธรรมเนียมแล้ว ชื่อของพารามิเตอร์ตัวตั้งค่าคือ value แต่คุณเปลี่ยนชื่อได้หากต้องการ
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. ใน buildAquarium() ให้เพิ่มโค้ดเพื่อตั้งค่าปริมาตรของตู้ปลาเป็น 70 ลิตร พิมพ์ขนาดใหม่
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. เรียกใช้โปรแกรมอีกครั้งและสังเกตความสูงและปริมาณที่เปลี่ยนแปลง
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

ขณะนี้ยังไม่มีตัวปรับการมองเห็น เช่น public หรือ private ในโค้ด เนื่องจากโดยค่าเริ่มต้น ทุกอย่างใน Kotlin จะเป็นแบบสาธารณะ ซึ่งหมายความว่าทุกอย่างจะเข้าถึงได้ทุกที่ รวมถึงคลาส เมธอด พร็อพเพอร์ตี้ และตัวแปรสมาชิก

ใน Kotlin คลาส ออบเจ็กต์ อินเทอร์เฟซ คอนสตรัคเตอร์ ฟังก์ชัน พร็อพเพอร์ตี้ และตัวตั้งค่าของพร็อพเพอร์ตี้จะมีตัวแก้ไขระดับการเข้าถึงได้

  • public หมายถึงมองเห็นได้นอกชั้นเรียน ทุกอย่างจะเป็นแบบสาธารณะโดยค่าเริ่มต้น รวมถึงตัวแปรและเมธอดของคลาส
  • internal หมายความว่าจะมองเห็นได้ภายในโมดูลนั้นเท่านั้น โมดูลคือชุดไฟล์ Kotlin ที่คอมไพล์ร่วมกัน เช่น ไลบรารีหรือแอปพลิเคชัน
  • private หมายความว่าจะมองเห็นได้ในคลาสนั้นเท่านั้น (หรือไฟล์ต้นฉบับหากคุณทำงานกับฟังก์ชัน)
  • protected เหมือนกับ private แต่จะมองเห็นได้ในคลาสย่อยด้วย

ดูข้อมูลเพิ่มเติมได้ที่ตัวแก้ไขระดับการเข้าถึงในเอกสารประกอบของ Kotlin

ตัวแปรสมาชิก

พร็อพเพอร์ตี้ภายในคลาสหรือตัวแปรสมาชิกจะpublicโดยค่าเริ่มต้น หากคุณกำหนดด้วย var จะแก้ไขได้ นั่นคือ อ่านและเขียนได้ หากคุณกำหนดด้วย val จะเป็นแบบอ่านอย่างเดียวหลังจากเริ่มต้น

หากต้องการพร็อพเพอร์ตี้ที่โค้ดของคุณอ่านหรือเขียนได้ แต่โค้ดภายนอกอ่านได้อย่างเดียว คุณสามารถปล่อยให้พร็อพเพอร์ตี้และ Getter เป็นแบบสาธารณะ และประกาศ Setter เป็นแบบส่วนตัวได้ ดังที่แสดงด้านล่าง

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

ในงานนี้ คุณจะได้เรียนรู้วิธีการทำงานของคลาสย่อยและการรับค่าใน Kotlin โดยจะคล้ายกับสิ่งที่คุณเคยเห็นในภาษาอื่นๆ แต่ก็มีความแตกต่างอยู่บ้าง

ใน Kotlin คลาสจะสร้างคลาสย่อยไม่ได้โดยค่าเริ่มต้น ในทำนองเดียวกัน คลาสย่อยจะลบล้างพร็อพเพอร์ตี้และตัวแปรสมาชิกไม่ได้ (แม้ว่าจะเข้าถึงได้ก็ตาม)

คุณต้องทําเครื่องหมายชั้นเรียนเป็น open เพื่ออนุญาตให้สร้างคลาสย่อยได้ ในทำนองเดียวกัน คุณต้องทำเครื่องหมายพร็อพเพอร์ตี้และตัวแปรสมาชิกเป็น open เพื่อลบล้างในคลาสย่อย ต้องมีคีย์เวิร์ด open เพื่อป้องกันไม่ให้รายละเอียดการติดตั้งใช้งานรั่วไหลโดยไม่ตั้งใจเป็นส่วนหนึ่งของอินเทอร์เฟซของคลาส

ขั้นตอนที่ 1: เปิดชั้นเรียนพิพิธภัณฑ์สัตว์น้ำ

ในขั้นตอนนี้ คุณจะสร้างAquariumคลาสopenเพื่อให้คุณลบล้างได้ในขั้นตอนถัดไป

  1. ทำเครื่องหมายคลาส Aquarium และพร็อพเพอร์ตี้ทั้งหมดด้วยคีย์เวิร์ด open
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. เพิ่มพร็อพเพอร์ตี้ shape ที่เปิดอยู่โดยมีค่า "rectangle"
   open val shape = "rectangle"
  1. เพิ่มพร็อพเพอร์ตี้ water แบบเปิดที่มี Getter ซึ่งแสดงผล 90% ของปริมาณของ Aquarium
    open var water: Double = 0.0
        get() = volume * 0.9
  1. เพิ่มโค้ดลงในเมธอด printSize() เพื่อพิมพ์รูปร่างและปริมาณน้ำเป็นเปอร์เซ็นต์ของปริมาตร
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. ใน buildAquarium() ให้เปลี่ยนโค้ดเพื่อสร้าง Aquarium ที่มี width = 25, length = 25 และ height = 40
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุตใหม่
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

ขั้นตอนที่ 2: สร้างคลาสย่อย

  1. สร้างคลาสย่อยของ Aquarium ชื่อ TowerTank ซึ่งใช้ถังทรงกระบอกกลมแทนถังสี่เหลี่ยม คุณเพิ่ม TowerTank ใต้ Aquarium ได้เนื่องจากเพิ่มชั้นเรียนอื่นในไฟล์เดียวกันกับชั้นเรียน Aquarium ได้
  2. ใน TowerTank ให้ลบล้างพร็อพเพอร์ตี้ height ซึ่งกำหนดไว้ในตัวสร้าง หากต้องการลบล้างพร็อพเพอร์ตี้ ให้ใช้คีย์เวิร์ด override ในคลาสย่อย
  1. สร้างตัวสร้างสำหรับ TowerTank เพื่อใช้ diameter ใช้ diameter สำหรับทั้ง length และ width เมื่อเรียกเครื่องมือสร้างในคลาสย่อย Aquarium
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. ลบล้างพร็อพเพอร์ตี้ปริมาตรเพื่อคำนวณทรงกระบอก สูตรสำหรับทรงกระบอกคือพายคูณรัศมียกกำลัง 2 คูณความสูง คุณต้องนำเข้าค่าคงที่ PI จาก java.lang.Math
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. ใน TowerTank ให้ลบล้างพร็อพเพอร์ตี้ water เป็น 80% ของระดับเสียง
override var water = volume * 0.8
  1. ลบล้าง shape เป็น "cylinder"
override val shape = "cylinder"
  1. TowerTank คลาสสุดท้ายควรมีลักษณะคล้ายกับโค้ดด้านล่าง

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. ใน buildAquarium() ให้สร้าง TowerTank ที่มีเส้นผ่านศูนย์กลาง 25 ซม. และสูง 45 ซม. พิมพ์ขนาด

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

บางครั้งคุณอาจต้องการกำหนดลักษณะการทำงานหรือพร็อพเพอร์ตี้ทั่วไปเพื่อแชร์ในคลาสที่เกี่ยวข้องบางคลาส Kotlin มี 2 วิธีในการทำเช่นนั้น ได้แก่ อินเทอร์เฟซและคลาส Abstract ในงานนี้ คุณจะสร้างคลาส AquariumFish ที่เป็นนามธรรมสำหรับพร็อพเพอร์ตี้ที่ปลาทุกตัวมีเหมือนกัน คุณสร้างอินเทอร์เฟซที่ชื่อ FishAction เพื่อกำหนดลักษณะการทำงานที่ปลาทุกตัวมีเหมือนกัน

  • ทั้งคลาสแบบนามธรรมและอินเทอร์เฟซไม่สามารถสร้างอินสแตนซ์ได้ด้วยตัวเอง ซึ่งหมายความว่าคุณไม่สามารถสร้างออบเจ็กต์ของประเภทเหล่านั้นได้โดยตรง
  • คลาส Abstract มีตัวสร้าง
  • อินเทอร์เฟซต้องไม่มีตรรกะของตัวสร้างหรือจัดเก็บสถานะใดๆ

ขั้นตอนที่ 1 สร้างคลาส Abstract

  1. สร้างไฟล์ใหม่ AquariumFish.kt ในส่วน example.myapp
  2. สร้างคลาสหรือที่เรียกว่า AquariumFish แล้วทำเครื่องหมายด้วย abstract
  3. เพิ่มพร็อพเพอร์ตี้ String, color และทำเครื่องหมายด้วย abstract
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. สร้างคลาสย่อย 2 คลาสของ AquariumFish ได้แก่ Shark และ Plecostomus
  2. เนื่องจาก color เป็นนามธรรม คลาสย่อยจึงต้องใช้ เปลี่ยน Shark เป็นสีเทาและ Plecostomus เป็นสีทอง
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. ใน main.kt ให้สร้างmakeFish()ฟังก์ชันเพื่อทดสอบคลาส สร้างอินสแตนซ์ของ Shark และ Plecostomus จากนั้นพิมพ์สีของแต่ละอินสแตนซ์
  2. ลบโค้ดทดสอบก่อนหน้าใน 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()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Shark: gray 
Plecostomus: gold

แผนภาพต่อไปนี้แสดงคลาส Shark และคลาส Plecostomus ซึ่งเป็นคลาสย่อยของคลาสนามธรรม AquariumFish

แผนภาพแสดงคลาสนามธรรม AquariumFish และคลาสย่อย 2 คลาส ได้แก่ Shark และ Plecostumus

ขั้นตอนที่ 2 สร้างอินเทอร์เฟซ

  1. ใน AquariumFish.kt ให้สร้างอินเทอร์เฟซชื่อ FishAction ที่มีเมธอด eat()
interface FishAction  {
    fun eat()
}
  1. เพิ่ม 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")
    }
}
  1. ในฟังก์ชัน makeFish() ให้ปลาแต่ละตัวที่คุณสร้างกินอะไรบางอย่างโดยเรียกใช้ eat()
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

แผนภาพต่อไปนี้แสดงคลาส Shark และคลาส Plecostomus ซึ่งทั้ง 2 คลาสประกอบด้วยและใช้การติดตั้งใช้งานอินเทอร์เฟซ FishAction

กรณีที่ควรใช้คลาสแบบนามธรรมเทียบกับอินเทอร์เฟซ

ตัวอย่างข้างต้นเป็นตัวอย่างง่ายๆ แต่เมื่อคุณมีคลาสที่เกี่ยวข้องกันจำนวนมาก คลาสและอินเทอร์เฟซแบบนามธรรมจะช่วยให้การออกแบบของคุณสะอาดตา จัดระเบียบได้ดีขึ้น และดูแลรักษาง่ายขึ้น

ดังที่กล่าวไว้ข้างต้น คลาส Abstract มีตัวสร้างได้ แต่ Interface ไม่มี อย่างไรก็ตาม คลาสทั้ง 2 ประเภทนี้มีความคล้ายคลึงกันมาก ดังนั้น คุณควรใช้แต่ละรายการเมื่อใด

เมื่อใช้อินเทอร์เฟซเพื่อสร้างคลาส ฟังก์ชันการทำงานของคลาสจะขยายออกไปโดยใช้อินสแตนซ์ของคลาสที่มีอยู่ การคอมโพสิตมักจะทำให้โค้ดนำกลับมาใช้ซ้ำและให้เหตุผลได้ง่ายกว่าการรับค่าจากคลาสแอบสแทรกต์ นอกจากนี้ คุณยังใช้อินเทอร์เฟซหลายรายการในคลาสได้ แต่จะสร้างคลาสย่อยจากคลาสแอบสแทรกต์ได้เพียงคลาสเดียว

การคอมโพสิตมักส่งผลให้การแคปซูลดีขึ้น การคัปปลิ้ง (การพึ่งพาอาศัยกัน) ลดลง อินเทอร์เฟซสะอาดตาขึ้น และโค้ดใช้งานได้มากขึ้น ด้วยเหตุผลเหล่านี้ การใช้การคอมโพสกับอินเทอร์เฟซจึงเป็นดีไซน์ที่แนะนำ ในทางกลับกัน การรับค่าจากคลาสนามธรรมมักจะเหมาะกับปัญหาบางอย่าง ดังนั้นคุณควรเลือกใช้การคอมโพสิต แต่ Kotlin ก็อนุญาตให้คุณใช้การสืบทอดได้เช่นกันหากเหมาะสม

  • ใช้อินเทอร์เฟซหากคุณมีเมธอดจำนวนมากและมีการติดตั้งใช้งานเริ่มต้น 1 หรือ 2 รายการ เช่น ใน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")
}

งานก่อนหน้านี้ได้แนะนำคลาสนามธรรม อินเทอร์เฟซ และแนวคิดของการคอมโพสิต การมอบสิทธิ์อินเทอร์เฟซเป็นเทคนิคขั้นสูงที่ใช้วิธีการของอินเทอร์เฟซโดยออบเจ็กต์ผู้ช่วย (หรือผู้รับมอบสิทธิ์) ซึ่งคลาสจะใช้ในภายหลัง เทคนิคนี้มีประโยชน์เมื่อคุณใช้อินเทอร์เฟซในชุดของคลาสที่ไม่เกี่ยวข้อง โดยคุณจะเพิ่มฟังก์ชันการทำงานของอินเทอร์เฟซที่จำเป็นลงในคลาส Helper แยกต่างหาก และแต่ละคลาสจะใช้อินสแตนซ์ของคลาส Helper เพื่อใช้ฟังก์ชันการทำงาน

ในงานนี้ คุณจะใช้การมอบสิทธิ์อินเทอร์เฟซเพื่อเพิ่มฟังก์ชันการทำงานให้กับคลาส

ขั้นตอนที่ 1: สร้างอินเทอร์เฟซใหม่

  1. ใน AquariumFish.kt ให้นำคลาส AquariumFish ออก แทนที่จะรับค่าจากคลาส AquariumFish คลาส Plecostomus และ Shark จะใช้การเชื่อมต่อสำหรับทั้งการดำเนินการกับปลาและสีของปลา
  2. สร้างอินเทอร์เฟซใหม่ FishColor ที่กำหนดสีเป็นสตริง
interface FishColor {
    val color: String
}
  1. เปลี่ยน Plecostomus เพื่อใช้ 2 อินเทอร์เฟซ ได้แก่ FishAction และ FishColor คุณต้องลบล้าง color จาก FishColor และ eat() จาก FishAction
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. เปลี่ยนคลาส Shark ให้ใช้ 2 อินเทอร์เฟซ FishAction และ FishColor แทนการรับค่าจาก AquariumFish
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. โค้ดที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังนี้
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

ขั้นตอนที่ 2: สร้างคลาส Singleton

จากนั้น ให้ใช้การตั้งค่าสำหรับส่วนการมอบสิทธิ์โดยสร้างคลาสตัวช่วยที่ใช้ FishColor คุณสร้างคลาสพื้นฐานชื่อ GoldColor ที่ใช้ FishColor ซึ่งมีหน้าที่เพียงระบุว่าสีของคลาสคือสีทอง

การสร้างอินสแตนซ์ของ GoldColor หลายรายการไม่มีประโยชน์ เนื่องจากทั้งหมดจะทำงานเหมือนกันทุกประการ ดังนั้น Kotlin จึงช่วยให้คุณประกาศคลาสที่สร้างได้เพียงอินสแตนซ์เดียวโดยใช้คีย์เวิร์ด object แทน class Kotlin จะสร้างอินสแตนซ์นั้นขึ้นมา และอินสแตนซ์นั้นจะอ้างอิงตามชื่อคลาส จากนั้นออบเจ็กต์อื่นๆ ทั้งหมดจะใช้ออบเจ็กต์อินสแตนซ์นี้ได้เพียงอินสแตนซ์เดียวเท่านั้น เนื่องจากไม่มีวิธีสร้างออบเจ็กต์อินสแตนซ์อื่นๆ ของคลาสนี้ หากคุณคุ้นเคยกับรูปแบบ Singleton นี่คือวิธีใช้ Singleton ใน Kotlin

  1. ใน AquariumFish.kt ให้สร้างออบเจ็กต์สำหรับ GoldColor ลบล้างสี
object GoldColor : FishColor {
   override val color = "gold"
}

ขั้นตอนที่ 3: เพิ่มการมอบสิทธิ์อินเทอร์เฟซสำหรับ FishColor

ตอนนี้คุณก็พร้อมที่จะใช้การมอบสิทธิ์อินเทอร์เฟซแล้ว

  1. ใน AquariumFish.kt ให้นำการลบล้างของ color ออกจาก Plecostomus
  2. เปลี่ยนคลาส Plecostomus เพื่อรับสีจาก GoldColor โดยทำได้โดยเพิ่ม by GoldColor ลงในการประกาศคลาสเพื่อสร้างการมอบสิทธิ์ ซึ่งหมายความว่าแทนที่จะใช้ FishColor ให้ใช้การติดตั้งใช้งานที่ GoldColor จัดหาให้ ดังนั้นทุกครั้งที่มีการเข้าถึง color ระบบจะมอบสิทธิ์ให้ GoldColor
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

หากใช้ชั้นเรียนตามที่เป็นอยู่ ปลาพลีโคทั้งหมดจะเป็นสีทอง แต่ในความเป็นจริงแล้วปลาเหล่านี้มีหลายสี คุณแก้ไขปัญหานี้ได้โดยการเพิ่มพารามิเตอร์ตัวสร้างสำหรับสีที่มี GoldColor เป็นสีเริ่มต้นสำหรับ Plecostomus

  1. เปลี่ยนคลาส Plecostomus เพื่อรับค่าที่ส่งใน fishColor พร้อมกับตัวสร้าง และตั้งค่าเริ่มต้นเป็น GoldColor เปลี่ยนการมอบสิทธิ์จาก by GoldColor เป็น by fishColor
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

ขั้นตอนที่ 4: เพิ่มการมอบสิทธิ์อินเทอร์เฟซสำหรับ FishAction

ในทำนองเดียวกัน คุณสามารถใช้การมอบสิทธิ์อินเทอร์เฟซสำหรับ FishAction ได้

  1. ใน AquariumFish.kt ให้สร้างคลาส PrintingFishAction ที่ใช้ FishAction ซึ่งรับ String, food แล้วพิมพ์สิ่งที่ปลาตัวนั้นกิน
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. ในคลาส Plecostomus ให้นำฟังก์ชันการลบล้าง eat() ออก เนื่องจากคุณจะแทนที่ด้วยการมอบสิทธิ์
  2. ในการประกาศของ Plecostomus ให้มอบสิทธิ์ FishAction ให้กับ PrintingFishAction โดยส่ง "eat algae"
  3. เมื่อมีการมอบสิทธิ์ทั้งหมดแล้ว ก็จะไม่มีโค้ดในเนื้อหาของคลาส Plecostomus ดังนั้นให้นำ {} ออก เนื่องจากส่วนที่เขียนทับทั้งหมดจะได้รับการจัดการโดยการมอบสิทธิ์อินเทอร์เฟซ
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

แผนภาพต่อไปนี้แสดงคลาส Shark และ Plecostomus ซึ่งทั้ง 2 คลาสประกอบด้วยอินเทอร์เฟซ PrintingFishAction และ FishColor แต่จะมอบหมายการใช้งานให้กับอินเทอร์เฟซเหล่านั้น

การมอบสิทธิ์อินเทอร์เฟซมีประสิทธิภาพ และโดยทั่วไปคุณควรพิจารณาวิธีใช้ทุกครั้งที่อาจใช้คลาสแบบนามธรรมในภาษาอื่น ซึ่งช่วยให้คุณใช้การคอมโพสเพื่อเสียบพฤติกรรมแทนที่จะต้องใช้คลาสย่อยจำนวนมาก ซึ่งแต่ละคลาสมีความเชี่ยวชาญในลักษณะที่แตกต่างกัน

คลาสข้อมูลคล้ายกับ struct ในภาษาอื่นๆ ซึ่งมีไว้เพื่อจัดเก็บข้อมูลบางอย่างเป็นหลัก แต่ออบเจ็กต์คลาสข้อมูลก็ยังคงเป็นออบเจ็กต์ ออบเจ็กต์คลาสข้อมูล Kotlin มีประโยชน์เพิ่มเติมบางอย่าง เช่น ยูทิลิตีสำหรับการพิมพ์และการคัดลอก ในงานนี้ คุณจะได้สร้างคลาสข้อมูลอย่างง่ายและเรียนรู้เกี่ยวกับการรองรับคลาสข้อมูลที่ Kotlin มีให้

ขั้นตอนที่ 1: สร้างคลาสข้อมูล

  1. เพิ่มแพ็กเกจใหม่ decor ภายใต้แพ็กเกจ example.myapp เพื่อเก็บโค้ดใหม่ คลิกขวาที่ example.myapp ในแผงโปรเจ็กต์ แล้วเลือกไฟล์ > ใหม่ > แพ็กเกจ
  2. สร้างคลาสใหม่ชื่อ Decoration ในแพ็กเกจ
package example.myapp.decor

class Decoration {
}
  1. หากต้องการทําให้ Decoration เป็นคลาสข้อมูล ให้เติมคำหลัก data ไว้หน้าการประกาศคลาส
  2. เพิ่มพร็อพเพอร์ตี้ String ที่ชื่อ rocks เพื่อให้ข้อมูลแก่คลาส
data class Decoration(val rocks: String) {
}
  1. ในไฟล์ นอกคลาส ให้เพิ่มฟังก์ชัน makeDecorations() เพื่อสร้างและพิมพ์อินสแตนซ์ของ Decoration ด้วย "granite"
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. เพิ่มmain()ฟังก์ชันเพื่อเรียก makeDecorations() แล้วเรียกใช้โปรแกรม โปรดสังเกตเอาต์พุตที่สมเหตุสมผลซึ่งสร้างขึ้นเนื่องจากนี่คือคลาสข้อมูล
⇒ Decoration(rocks=granite)
  1. ใน makeDecorations() ให้สร้างออบเจ็กต์ Decoration อีก 2 รายการซึ่งเป็น "slate" ทั้งคู่ แล้วพิมพ์ออบเจ็กต์เหล่านั้น
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. ใน makeDecorations() ให้เพิ่มคำสั่งพิมพ์ที่พิมพ์ผลลัพธ์ของการเปรียบเทียบ decoration1 กับ decoration2 และคำสั่งที่สองที่เปรียบเทียบ decoration3 กับ decoration2 ใช้วิธี equals() ที่คลาสข้อมูลมีให้
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. เรียกใช้โค้ด
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

ขั้นตอนที่ 2 ใช้การแยกโครงสร้าง

หากต้องการเข้าถึงพร็อพเพอร์ตี้ของออบเจ็กต์ข้อมูลและกำหนดให้กับตัวแปร คุณสามารถกำหนดทีละรายการได้ ดังนี้

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

แต่คุณสามารถสร้างตัวแปร 1 ตัวสำหรับแต่ละพร็อพเพอร์ตี้ และกำหนดออบเจ็กต์ข้อมูลให้กับกลุ่มตัวแปรได้ Kotlin จะใส่ค่าพร็อพเพอร์ตี้ในตัวแปรแต่ละตัว

val (rock, wood, diver) = decoration

ซึ่งเรียกว่าการแยกโครงสร้างและเป็นรูปแบบย่อที่มีประโยชน์ จำนวนตัวแปรควรตรงกับจำนวนพร็อพเพอร์ตี้ และระบบจะกำหนดตัวแปรตามลำดับที่ประกาศในคลาส ต่อไปนี้คือตัวอย่างที่สมบูรณ์ซึ่งคุณลองใช้ได้ใน Decoration.kt

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

หากไม่ต้องการพร็อพเพอร์ตี้อย่างน้อย 1 รายการ คุณสามารถข้ามพร็อพเพอร์ตี้นั้นได้โดยใช้ _ แทนชื่อตัวแปร ดังที่แสดงในโค้ดด้านล่าง

    val (rock, _, diver) = d5

ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับคลาสพิเศษบางคลาสใน Kotlin ซึ่งรวมถึงคลาสต่อไปนี้

  • คลาส Singleton
  • Enum
  • ชั้นเรียนที่ปิด

ขั้นตอนที่ 1: เรียกคืนคลาส Singleton

โปรดจำตัวอย่างก่อนหน้าที่มีคลาส GoldColor

object GoldColor : FishColor {
   override val color = "gold"
}

เนื่องจากอินสแตนซ์ทั้งหมดของ GoldColor ทำสิ่งเดียวกัน จึงมีการประกาศเป็น object แทนที่จะเป็น class เพื่อให้เป็น Singleton โดยจะมีได้เพียงอินสแตนซ์เดียวเท่านั้น

ขั้นตอนที่ 2: สร้าง Enum

นอกจากนี้ Kotlin ยังรองรับ Enum ซึ่งช่วยให้คุณแจงนับรายการและอ้างอิงถึงรายการนั้นๆ ตามชื่อได้เช่นเดียวกับในภาษาอื่นๆ ประกาศ Enum โดยนำหน้าการประกาศด้วยคีย์เวิร์ด enum การประกาศ Enum พื้นฐานต้องการเพียงรายการชื่อ แต่คุณยังกำหนดฟิลด์อย่างน้อย 1 รายการที่เชื่อมโยงกับแต่ละชื่อได้ด้วย

  1. ใน Decoration.kt ให้ลองใช้ตัวอย่าง Enum
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Enums คล้ายกับ Singleton ตรงที่สามารถมีได้เพียงหนึ่งเดียว และมีได้เพียงค่าเดียวในแต่ละค่าของการแจงนับ เช่น มี Color.RED, Color.GREEN และ Color.BLUE ได้อย่างละ 1 รายการเท่านั้น ในตัวอย่างนี้ ค่า RGB จะกำหนดให้กับพร็อพเพอร์ตี้ rgb เพื่อแสดงถึงองค์ประกอบสี นอกจากนี้ คุณยังรับค่าลำดับของ Enum ได้โดยใช้พร็อพเพอร์ตี้ ordinal และรับชื่อของ Enum ได้โดยใช้พร็อพเพอร์ตี้ name

  1. ลองดูตัวอย่าง Enum อื่น
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

ขั้นตอนที่ 3: สร้างคลาสที่ปิดผนึก

คลาสที่ปิดผนึกคือคลาสที่สามารถสร้างคลาสย่อยได้ แต่จะทำได้เฉพาะภายในไฟล์ที่ประกาศคลาสนั้น หากพยายามสร้างคลาสย่อยของคลาสในไฟล์อื่น คุณจะได้รับข้อผิดพลาด

เนื่องจากคลาสและคลาสย่อยอยู่ในไฟล์เดียวกัน Kotlin จึงรู้จักคลาสย่อยทั้งหมดแบบคงที่ กล่าวคือ ในเวลาคอมไพล์ คอมไพเลอร์จะเห็นคลาสและคลาสย่อยทั้งหมด และทราบว่านี่คือคลาสและคลาสย่อยทั้งหมด ดังนั้นคอมไพเลอร์จึงสามารถทำการตรวจสอบเพิ่มเติมให้คุณได้

  1. ใน AquariumFish.kt ให้ลองใช้ตัวอย่างของ Sealed Class โดยยังคงใช้ธีมสัตว์น้ำ
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

Seal คลาสไม่สามารถสร้างคลาสย่อยในไฟล์อื่นได้ หากต้องการเพิ่มSealประเภทอื่นๆ คุณต้องเพิ่มในไฟล์เดียวกัน ซึ่งทำให้คลาสที่ปิดผนึกเป็นวิธีที่ปลอดภัยในการแสดงประเภทจำนวนคงที่ เช่น คลาสที่ปิดผนึกเหมาะสำหรับการแสดงผลสำเร็จหรือข้อผิดพลาดจาก API เครือข่าย

บทเรียนนี้ครอบคลุมเนื้อหามากมาย แม้ว่าหลายๆ อย่างจะคุ้นเคยจากภาษาโปรแกรมเชิงวัตถุอื่นๆ แต่ Kotlin ก็เพิ่มฟีเจอร์บางอย่างเพื่อให้โค้ดกระชับและอ่านง่าย

คลาสและตัวสร้าง

  • กำหนดคลาสใน Kotlin โดยใช้ class
  • Kotlin จะสร้าง Setter และ Getter สำหรับพร็อพเพอร์ตี้โดยอัตโนมัติ
  • กำหนดตัวสร้างหลักในคำจำกัดความของคลาสโดยตรง เช่น
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • หากตัวสร้างหลักต้องมีโค้ดเพิ่มเติม ให้เขียนโค้ดในบล็อก init อย่างน้อย 1 บล็อก
  • คลาสสามารถกำหนดตัวสร้างรองอย่างน้อย 1 รายการโดยใช้ constructor แต่รูปแบบ Kotlin คือการใช้ฟังก์ชัน Factory แทน

ตัวแก้ไขระดับการเข้าถึงและคลาสย่อย

  • คลาสและฟังก์ชันทั้งหมดใน Kotlin จะเป็น public โดยค่าเริ่มต้น แต่คุณสามารถใช้ตัวแก้ไขเพื่อเปลี่ยนระดับการมองเห็นเป็น internal, private หรือ protected ได้
  • หากต้องการสร้างคลาสย่อย คุณต้องทำเครื่องหมายคลาสหลักเป็น open
  • หากต้องการลบล้างเมธอดและพร็อพเพอร์ตี้ในคลาสย่อย คุณต้องทำเครื่องหมายเมธอดและพร็อพเพอร์ตี้เป็น open ในคลาสหลัก
  • คลาสที่ปิดผนึกจะสร้างคลาสย่อยได้เฉพาะในไฟล์เดียวกันกับที่กำหนดไว้ สร้างคลาสที่ปิดผนึกโดยนำหน้าการประกาศด้วย sealed

คลาสข้อมูล Singleton และ Enum

  • สร้างคลาสข้อมูลโดยนำหน้าการประกาศด้วย data
  • การแยกโครงสร้างเป็นรูปแบบย่อสำหรับการกำหนดพร็อพเพอร์ตี้ของออบเจ็กต์ data ให้กับตัวแปรแยกต่างหาก
  • สร้างคลาส Singleton โดยใช้ object แทน class
  • กำหนด enum โดยใช้ enum class

คลาสนามธรรม อินเทอร์เฟซ และการมอบสิทธิ์

  • คลาสและอินเทอร์เฟซแบบนามธรรมเป็น 2 วิธีในการแชร์ลักษณะการทำงานทั่วไประหว่างคลาส
  • คลาสแอบสแทรกต์กำหนดพร็อพเพอร์ตี้และลักษณะการทำงาน แต่ปล่อยให้คลาสย่อยเป็นผู้ใช้งาน
  • อินเทอร์เฟซกำหนดลักษณะการทำงาน และอาจมีการใช้งานเริ่มต้นสำหรับลักษณะการทำงานบางอย่างหรือทั้งหมด
  • เมื่อใช้อินเทอร์เฟซเพื่อสร้างคลาส ฟังก์ชันการทำงานของคลาสจะขยายออกไปโดยใช้อินสแตนซ์ของคลาสที่มีอยู่
  • การมอบสิทธิ์อินเทอร์เฟซใช้การคอมโพสิต แต่ยังมอบสิทธิ์การใช้งานให้กับคลาสอินเทอร์เฟซด้วย
  • การคอมโพสิตเป็นวิธีที่มีประสิทธิภาพในการเพิ่มฟังก์ชันการทำงานให้กับคลาสโดยใช้การมอบสิทธิ์อินเทอร์เฟซ โดยทั่วไปแล้ว เราจะชอบการคอมโพสิต แต่การรับค่าจากคลาสแอบสแทรกต์จะเหมาะกับปัญหาบางอย่างมากกว่า

เอกสารประกอบ Kotlin

หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้ หรือหากคุณติดขัด https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด

บทแนะนำ Kotlin

เว็บไซต์ https://try.kotlinlang.org มีบทแนะนำที่สมบูรณ์ซึ่งเรียกว่า Kotlin Koans, ตัวแปลภาษาบนเว็บ และชุดเอกสารอ้างอิงที่สมบูรณ์พร้อมตัวอย่าง

หลักสูตร Udacity

หากต้องการดูหลักสูตร Udacity ในหัวข้อนี้ โปรดดูค่ายฝึก Kotlin สำหรับโปรแกรมเมอร์

IntelliJ IDEA

เอกสารประกอบสำหรับ IntelliJ IDEA อยู่ในเว็บไซต์ของ JetBrains

ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้

  • มอบหมายการบ้านหากจำเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
  • ให้คะแนนงานการบ้าน

ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม

หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ

ตอบคำถามต่อไปนี้

คำถามที่ 1

คลาสมีเมธอดพิเศษที่ทำหน้าที่เป็นพิมพ์เขียวสำหรับการสร้างออบเจ็กต์ของคลาสนั้น วิธีการนี้เรียกว่าอะไร

▢ ผู้สร้าง

▢ ผู้สร้างอินสแตนซ์

▢ ผู้สร้าง

▢ พิมพ์เขียว

คำถามที่ 2

ข้อความใดต่อไปนี้เกี่ยวกับอินเทอร์เฟซและคลาส Abstract ไม่ถูกต้อง

▢ คลาส Abstract มีตัวสร้างได้

▢ อินเทอร์เฟซต้องไม่มีตัวสร้าง

▢ สร้างอินเทอร์เฟซและคลาสแบบนามธรรมได้โดยตรง

▢ คลาสย่อยของคลาส Abstract ต้องใช้พร็อพเพอร์ตี้ Abstract

คำถามที่ 3

ข้อใดต่อไปนี้ไม่ใช่ตัวแก้ไขระดับการเข้าถึงของ Kotlin สำหรับพร็อพเพอร์ตี้ เมธอด ฯลฯ

internal

nosubclass

protected

private

คำถามที่ 4

พิจารณาคลาสข้อมูลต่อไปนี้
data class Fish(val name: String, val species:String, val colors:String)
โค้ดใดต่อไปนี้ไม่ถูกต้องในการสร้างและแยกโครงสร้างออบเจ็กต์ Fish

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

คำถามที่ 5

สมมติว่าคุณเป็นเจ้าของสวนสัตว์ที่มีสัตว์มากมายที่ต้องได้รับการดูแล ข้อใดต่อไปนี้ไม่ใช่ส่วนหนึ่งของการดูแลรักษา

interface สำหรับอาหารประเภทต่างๆ ที่สัตว์กิน

abstract Caretaker ชั้นเรียนที่คุณสร้างผู้ดูแลประเภทต่างๆ ได้

interface สำหรับให้น้ำสะอาดแก่สัตว์

data ชั้นเรียนสำหรับรายการในตารางเวลาการให้อาหาร

ไปยังบทเรียนถัดไป: 5.1 ส่วนขยาย

ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยังโค้ดแล็บอื่นๆ ได้ที่ "Kotlin Bootcamp for Programmers: Welcome to the course"