개발자/kotlin 코틀린

kotlin 클래스 개념 확실하게 이해하기 3

지구빵집 2022. 1. 24. 09:35
반응형

 

 

계층 구조의 클래스 수정 

 

바닥 면적 계산

 

이 연습에서는 추상 클래스에서 추상 함수를 선언한 다음 서브클래스에서 그 기능을 구현하는 방법을 알아봅니다.

 

모든 주택에는 바닥 면적이 있지만 주택의 형태에 따라 다르게 계산됩니다.

 

Dwelling 클래스에서 floorArea() 정의

 

1. 먼저 abstract floorArea() 함수를 Dwelling 클래스에 추가합니다. Double을 반환합니다. Double은 String, Int와 같은 데이터 유형입니다. 소수점 뒤에 소수 부분이 오는 숫자(예: 5.8793)인 부동 소수점 숫자에 사용됩니다.

 

abstract fun floorArea(): Double

 

추상 클래스에서 정의된 모든 추상 메서드는 추상 클래스의 서브클래스에서 구현되어야 합니다. 코드를 실행하려면 먼저 서브클래스에서 floorArea()를 구현해야 합니다.

 

 

kotlin 클래스 개념 확실하게 이해하기 3

 

 

SquareCabin의 floorArea() 구현

 

buildingMaterial, capacity와 마찬가지로 상위 클래스에서 정의된 abstract 함수를 구현하므로 override 키워드를 사용해야 합니다.

 

1. SquareCabin 클래스에서 아래와 같이 키워드 override로 시작하여 floorArea() 함수의 실제 구현 순으로 작성합니다.

 

override fun floorArea(): Double {

}

 

2. 계산된 바닥 면적을 반환합니다. 직사각형이나 정사각형 면적은 가로 길이에 세로 길이를 곱한 값입니다. 함수의 본문은 return length * length입니다.

 

override fun floorArea(): Double {
    return length * length
}

 

길이는 클래스의 변수가 아니며 인스턴스마다 다르므로 SquareCabin 클래스의 생성자 매개변수로 추가할 수 있습니다.

 

3. SquareCabin의 클래스 정의를 변경하여 Double 유형의 length 매개변수를 추가합니다. 속성을 val로 선언합니다. 건물의 길이는 변경되지 않기 때문입니다.

 

class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {

 

따라서 Dwelling과 모든 서브클래스에는 생성자 인수로 residents가 있습니다. Dwelling 생성자의 첫 번째 인수이므로 모든 서브클래스 생성자의 첫 번째 인수로 만들고 모든 클래스 정의에서 동일한 순서로 인수를 배치하는 것이 좋습니다. 따라서 새 length 매개변수를 residents 매개변수 뒤에 삽입합니다.

 

4. main()에서 squareCabin 인스턴스 만들기를 업데이트합니다. 50.0을 SquareCabin 생성자에 length로 전달합니다.

 

val squareCabin = SquareCabin(6, 50.0)

 

5. squareCabin의 with 문 내에서 바닥 면적의 print 문을 추가합니다.

 

println("Floor area: ${floorArea()}")

 

코드가 실행되지 않습니다. RoundHut의 floorArea()도 구현해야 하기 때문입니다.

 

RoundHut의 floorArea() 구현

 

같은 방법으로 RoundHut의 바닥 면적을 구현합니다. RoundHut도 Dwelling의 직접적인 서브클래스이므로 override 키워드를 사용해야 합니다.

 

원형 주택의 바닥 면적은 PI * 반지름^2 또는 PI * 반지름 * 반지름입니다.

 

PI는 수학 값으로, 수학 라이브러리에 정의되어 있습니다. 라이브러리는 프로그램이 사용할 수 있는 프로그램 외부에 정의된 함수 및 값의 사전 정의된 모음입니다. 라이브러리 함수 또는 값을 사용하려면 컴파일러에 함수나 값을 사용할 거라고 알려야 합니다. 이렇게 하려면 프로그램으로 함수나 값을 가져오면 됩니다. 프로그램에서 PI를 사용하려면 kotlin.math.PI를 가져와야 합니다.

 

1. Kotlin 수학 라이브러리에서 PI를 가져옵니다. 파일 상단의 main() 앞에 배치합니다.

 

import kotlin.math.PI

 

2. RoundHut의 floorArea() 함수를 구현합니다.

 

override fun floorArea(): Double {
    return PI * radius * radius
}

 

경고: 가져오지 않는 경우

 

kotlin.math.PI

 

오류가 발생하므로 이 라이브러리를 가져온 후 사용해야 합니다. 또는 정규화된 버전을 작성할 수 있습니다.

 

PI

 

예를 들면 아래와 같습니다.

 

kotlin.math.PI * radius * radius

 

그러면 import 문이 필요하지 않습니다.

 

3. RoundHut 생성자를 업데이트하여 radius를 전달합니다.

 

open class RoundHut(
   val residents: Int,
   val radius: Double) : Dwelling(residents) {

 

4. main()에서 radius 10.0을 RoundHut 생성자에 전달하여 roundHut의 초기화를 업데이트합니다.

 

val roundHut = RoundHut(3, 10.0)

 

roundHut의 with 문 내에 print 문을 추가합니다.

 

println("Floor area: ${floorArea()}")

 

RoundTower의 floorArea() 구현

 

코드가 아직 실행되지 않고 다음 오류와 함께 실패합니다.

 

 

Error: No value passed for parameter 'radius'

 

RoundTower에서 프로그램이 컴파일되려면 RoundHut에서 상속되므로 floorArea()를 구현하지 않아도 되지만 상위 RoundHut.과 radius 인수도 같도록 RoundTower 클래스 정의를 업데이트해야 합니다.

 

1. radius도 사용하도록 RoundTower 생성자를 변경합니다. radius를 residents 뒤, floors 앞에 배치합니다. 기본값이 있는 변수는 끝에 나열하는 것이 좋습니다. radius를 상위 클래스 생성자에 전달해야 합니다.

 

class RoundTower(
    residents: Int,
    radius: Double,
    val floors: Int = 2) : RoundHut(residents, radius) {

 

2. main()에서 roundTower의 초기화를 업데이트합니다.

 

val roundTower = RoundTower(4, 15.5)

 

3. floorArea()를 호출하는 print 문을 추가합니다.

 

println("Floor area: ${floorArea()}")

 

4. 이제 코드를 실행할 수 있습니다.

 

5. RoundTower의 계산이 정확하지 않습니다. RoundHut에서 상속되고 floors 수를 고려하지 않기 때문입니다.

 

6. RoundTower에서 override floorArea()하여 면적에 층수를 곱하는 다른 구현을 제공할 수 있습니다. 추상 클래스(Dwelling)에서 함수를 정의하여 서브클래스(RoundHut)에서 구현한 다음 서브클래스의 서브클래스(RoundTower)에서 다시 재정의할 수 있습니다. 원하는 기능은 상속하고 원하지 않는 기능은 재정의할 수 있어서 매우 유용합니다.

 

override fun floorArea(): Double {
    return PI * radius * radius * floors
}

 

이 코드는 작동하지만 이미 RoundHut 상위 클래스에 있는 반복되는 코드를 방지하는 방법이 있습니다. 상위 RoundHut 클래스에서 floorArea() 함수를 호출하면 PI * radius * radius.가 반환됩니다. 그러면 이 결과에 floors 수를 곱합니다.

 

7. RoundTower에서 floorArea()의 슈퍼클래스 구현을 사용하도록 floorArea()를 업데이트합니다. super 키워드를 사용하여 상위 클래스에 정의된 함수를 호출합니다.

 

override fun floorArea(): Double {
    return super.floorArea() * floors
}

 

8. 다시 코드를 실행하면 RoundTower에서 여러 층의 바닥 면적을 올바르게 출력합니다.

 

완성된 코드는 다음과 같습니다.

 

import kotlin.math.PI

fun main() {

    val squareCabin = SquareCabin(6, 50.0)
    val roundHut = RoundHut(3, 10.0)
    val roundTower = RoundTower(4, 15.5)

    with(squareCabin) {
        println("\nSquare Cabin\n============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundHut) {
        println("\nRound Hut\n=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundTower) {
        println("\nRound Tower\n==========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }
 }

abstract class Dwelling(private var residents: Int) {

    abstract val buildingMaterial: String
    abstract val capacity: Int

    fun hasRoom(): Boolean {
        return residents < capacity
}

    abstract fun floorArea(): Double
}

class SquareCabin(residents: Int,
    val length: Double) : Dwelling(residents) {

    override val buildingMaterial = "Wood"
    override val capacity = 6

    override fun floorArea(): Double {
       return length * length
    }
}

open class RoundHut(val residents: Int,
    val radius: Double) : Dwelling(residents) {

    override val buildingMaterial = "Straw"
    override val capacity = 4

    override fun floorArea(): Double {
        return PI * radius * radius
    }
}

class RoundTower(residents: Int, radius: Double,
    val floors: Int = 2) : RoundHut(residents, radius) {

    override val buildingMaterial = "Stone"
    override val capacity = 4 * floors

    override fun floorArea(): Double {
        return super.floorArea() * floors
    }
}

 

출력은 다음과 같이 표시됩니다.

 

Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Floor area: 2500.0

Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
Floor area: 314.1592653589793

Round Tower
==========
Material: Stone
Capacity: 8
Has room? true
Floor area: 1509.5352700498956

 

참고: 면적 값의 경우 소수점 이하 두 자리만 표시하는 것이 사용자 환경에 더 좋습니다. 이러한 내용은 이 Codelab에서 다루지 않지만 다음 코드를 사용하여 바닥 면적을 출력할 수 있습니다.

 

println("Floor area: %.2f".format(floorArea()))

 

새 거주자가 방을 갖도록 허용

 

거주자 수를 1씩 늘리는 getRoom() 함수를 사용하여 새 거주자가 방을 갖도록 하는 기능을 추가합니다. 이 로직은 모든 주택에 동일하므로 Dwelling에서 함수를 구현할 수 있습니다. 이를 통해 모든 서브클래스와 그 하위 요소에서 함수를 사용할 수 있습니다. 간단합니다.

 

참고

  • 수용 인원이 남은 경우에만 거주자를 추가하는 if 문을 사용합니다.
  • 결과 메시지를 출력합니다.
  • residents++를 residents = residents + 1의 약어로 사용하여 residents 변수에 1을 추가할 수 있습니다.

 

1. Dwelling 클래스에서 getRoom() 함수를 구현합니다.

 

fun getRoom() {
    if (capacity > residents) {
        residents++
        println("You got a room!")
    } else {
        println("Sorry, at capacity and no rooms left.")
    }
}

 

2. roundHut의 with 문 블록에 print 문을 추가하여 getRoom()과 hasRoom()이 함께 사용되면 어떤 일이 발생하는지 관찰합니다.

 

println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()

 

이러한 print 문은 다음과 같이 출력됩니다.

 

Has room? true
You got a room!
Has room? false
Sorry, at capacity and no rooms left.

 

자세한 내용은 솔루션 코드를 참고하세요.

 

참고: 이 예는 상속이 매우 강력한 이유를 보여줍니다. Dwelling의 모든 서브클래스에서 이러한 클래스에 코드를 추가하지 않고도 getRoom() 함수를 호출할 수 있습니다.

 

둥근 주택에 카펫 맞추기

 

RoundHut이나 RoundTower에 사용할 카펫 크기를 알아야 한다고 가정해보겠습니다. SquareCabin의 경우 최대 카펫은 길이에서 바닥 면적과 동일하므로 추가로 계산하지 않아도 됩니다. 함수를 RoundHut에 넣어 모든 둥근 주택에서 사용할 수 있도록 합니다.

 

1. 먼저 kotlin.math 라이브러리에서 sqrt() 함수를 가져옵니다.

 

import kotlin.math.sqrt

 

2. RoundHut 클래스에서 calculateMaxCarpetSize() 함수를 구현합니다. 직사각형을 원에 맞추는 공식은 2로 나눈 지름 제곱의 제곱근입니다. sqrt (diameter * diameter / 2)

 

fun calculateMaxCarpetSize(): Double {
    val diameter = 2 * radius
    return sqrt(diameter * diameter / 2)
}

 

3. 이제 RoundHut 및 RoundTower 인스턴스에서 calculateMaxCarpetSize() 메서드를 호출할 수 있습니다. print 문을 main() 함수의 roundHut과 roundTower에 추가합니다.

 

println("Carpet size: ${calculateMaxCarpetSize()}")

 

자세한 내용은 솔루션 코드를 참고하세요.

 

참고: calculateMaxCarpetSize()를 RoundHut에 추가했으며 RoundTower가 이를 상속받습니다. 그러나 squareCabin 인스턴스에서는 이 함수를 호출할 수 없습니다. 이 함수를 정의하지 않거나 또는 정의하는 슈퍼클래스가 없기 때문입니다.

 

축하합니다. 속성과 함수가 포함된 완전한 클래스 계층 구조를 만들어 더 유용한 클래스를 만드는 데 필요한 모든 것을 알아봤습니다. 

 

솔루션 코드: 다음은 주석이 포함된 이 Codelab의 전체 솔루션 코드입니다. 

 

/**
* Program that implements classes for different kinds of dwellings.
* Shows how to:
* Create class hierarchy, variables and functions with inheritance,
* abstract class, overriding, and private vs. public variables.
*/

import kotlin.math.PI
import kotlin.math.sqrt

fun main() {
   val squareCabin = SquareCabin(6, 50.0)
   val roundHut = RoundHut(3, 10.0)
   val roundTower = RoundTower(4, 15.5)

   with(squareCabin) {
       println("\nSquare Cabin\n============")
       println("Capacity: ${capacity}")
       println("Material: ${buildingMaterial}")
       println("Floor area: ${floorArea()}")
   }

   with(roundHut) {
       println("\nRound Hut\n=========")
       println("Material: ${buildingMaterial}")
       println("Capacity: ${capacity}")
       println("Floor area: ${floorArea()}")
       println("Has room? ${hasRoom()}")
       getRoom()
       println("Has room? ${hasRoom()}")
       getRoom()
       println("Carpet size: ${calculateMaxCarpetSize()}")
   }

   with(roundTower) {
       println("\nRound Tower\n==========")
       println("Material: ${buildingMaterial}")
       println("Capacity: ${capacity}")
       println("Floor area: ${floorArea()}")
       println("Carpet size: ${calculateMaxCarpetSize()}")
   }
}

/**
* Defines properties common to all dwellings.
* All dwellings have floorspace,
* but its calculation is specific to the subclass.
* Checking and getting a room are implemented here
* because they are the same for all Dwelling subclasses.
*
* @param residents Current number of residents
*/
abstract class Dwelling(private var residents: Int) {
   abstract val buildingMaterial: String
   abstract val capacity: Int

   /**
    * Calculates the floor area of the dwelling.
    * Implemented by subclasses where shape is determined.
    *
    * @return floor area
    */
   abstract fun floorArea(): Double

   /**
    * Checks whether there is room for another resident.
    *
    * @return true if room available, false otherwise
    */
   fun hasRoom(): Boolean {
       return residents < capacity
   }

   /**
    * Compares the capacity to the number of residents and
    * if capacity is larger than number of residents,
    * add resident by increasing the number of residents.
    * Print the result.
    */
   fun getRoom() {
       if (capacity > residents) {
           residents++
           println("You got a room!")
       } else {
           println("Sorry, at capacity and no rooms left.")
       }
   }

   }

/**
* A square cabin dwelling.
*
*  @param residents Current number of residents
*  @param length Length
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
   override val buildingMaterial = "Wood"
   override val capacity = 6

   /**
    * Calculates floor area for a square dwelling.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return length * length
   }

}

/**
* Dwelling with a circular floorspace
*
* @param residents Current number of residents
* @param radius Radius
*/
open class RoundHut(
       val residents: Int, val radius: Double) : Dwelling(residents) {

   override val buildingMaterial = "Straw"
   override val capacity = 4

   /**
    * Calculates floor area for a round dwelling.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return PI * radius * radius
   }

   /**
    *  Calculates the max length for a square carpet
    *  that fits the circular floor.
    *
    * @return length of carpet
    */
   fun calculateMaxCarpetSize(): Double {
       val diameter = 2 * radius
       return sqrt(diameter * diameter / 2)
   }

}

/**
* Round tower with multiple stories.
*
* @param residents Current number of residents
* @param radius Radius
* @param floors Number of stories
*/
class RoundTower(
       residents: Int,
       radius: Double,
       val floors: Int = 2) : RoundHut(residents, radius) {

   override val buildingMaterial = "Stone"

   // Capacity depends on the number of floors.
   override val capacity = floors * 4

   /**
    * Calculates the total floor area for a tower dwelling
    * with multiple stories.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return super.floorArea() * floors
   }
}

 

 

여기까지 실습한 클래스 개념 잡기 내용을 요약합니다. 

 

  • 하위 클래스가 상위 클래스에서 기능을 상속받는 클래스 트리인 클래스 계층 구조를 만드는 방법. 속성과 함수가 서브클래스에 상속됩니다.
  • 일부 기능을 서브클래스에서 구현하도록 남기는 abstract 클래스를 만드는 방법. 따라서 abstract 클래스는 인스턴스화할 수 없습니다.
  • abstract 클래스의 서브클래스를 만드는 방법
  • override 키워드를 사용하여 서브클래스의 속성과 함수를 재정의하는 방법
  • super 키워드를 사용하여 상위 클래스의 함수와 속성을 참조하는 방법
  • 서브클래스로 분류할 수 있도록 클래스를 open으로 만드는 방법
  • 속성을 private으로 만들어 클래스 내에서만 사용할 수 있도록 하는 방법
  • with 구문을 사용하여 동일한 객체 인스턴스에서 여러 호출을 실행하는 방법
  • kotlin.math 라이브러리에서 기능을 가져오는 방법 

 

하~ 어렵다. 어려워.

 

 

 

반응형