본문 바로가기

Study/Kotlin

Kotlin / 클래스 관계 등 추가사항 알아보기

반응형

1. 클래스 연관관계 알아보기

클래스 관계

  • 상속관계 (is a) : 클래스를 상속해서 하나의 클래스처럼 사용한다. 
  • 연관관계 (has a) : 클래스를 상속하지 않고 내부적인 속성에 객체를 만들어서 사용한다.
  • 결합관계 (약한 has a) : 연관관계를 구성하는 방식 중에 클래스 간의 주종관계 없이 단순하게 사용하는 관계를 말한다.
  • 조합관계 (강한 has a) : 연관관계를 구성하는 방식 중에 클래스 간의 주종관계가 있어서 따로 분해해서 사용할 수 없는 관계를 말한다.
  • 의존관계 (사용 has a) : 필요한 클래스를 매개변수 등으로 받아 필요한 시점에 사용하는 관계를 말한다.

결합(Aggregation) 관계

단순하게 사용하는 클래스에서 사용된 클래스의 객체를 속성으로 만들어서 사용

보통 주 생성자에 객체를 전달받아서 구성

다른 클래스를 단순하게 사용할 뿐 두 클래스가 만든 객체가 동일한 생명주기일 필요는 없다

즉, 한 클래스의 객체가 소멸해도 다른 클래스의 객체는 계속 활용할 수 있다.

class Address(
    val streetNum: Int,
    val city: String,
    val state: String,
    val country: String
) {
    fun printAddr() {
        println("주소  = $streetNum $city $state $country")
    }
}

class College(
    val collegeName: String,
    val collegeAddr: Address // 다른 클래스를 속성에 할당
)

val ad1 = Address(55,"관악구","서울시","대한민국")// 주소 생성
val obj1 = College("서울대",ad1) // 주소 배정

println(obj1.collegeName) // 서울대
obj1.collegeAddr.printAddr() // 주소  = 55 관악구 서울시 대한민국

 

조합(Composition) 관계

결합관계보다 더 결합이 강해서 두 클래스는 주종관계이고 생명주기도 동일하다.

두 클래스는 항상 같이 생성되고 같이 소멸하는 구조일 경우에만 사용하는 관계이다.

class CarEngine { // 조합대상 클래스
    fun startEngine() {
        println("엔진 가동")
    }
}

open class Car( // 베이스 클래스
    var colour: String,
    var maxi_speed: Int
) {
    fun carDetails() {
        println("차 색상 : $colour , 최고 속도 : $maxi_speed")
    }
}

class CarProduct(color: String, max_speed: Int) : Car(color, max_speed) { // 조합을 구성하는 클래스
    lateinit var carEngine: CarEngine
    fun startCarProduct() {
        carEngine = CarEngine() // 다른 클래스로 조합 구성
        carEngine.startEngine()
    }
}

val carJazz = CarProduct("Red", 240)
carJazz.carDetails()
carJazz.startCarProduct()

//차 색상 : Red , 최고 속도 : 240
//엔진 가동

자동차와 생산된 자동차의 의미를 분리하기 위해 자동차 클래스를 상위클래스로 지정. 

  • CarProduct 클래스는 Car를 상속.
  • CarProduct 클래스 내부에 부품인 CarEngine 클래스의 객체를 속성에 할당.

의존(Dependency) 관계

특정 메서드 등에 객체를 전달받아서 사용만 하는 관계이다.

class Account(val accountNo: Int, var balance: Int = 0) { // 의존 처리 클래스
    fun depoosit(amount: Int) {
        println("입금")
        balance += amount
    }
}

class Customer(var balance: Int) {
    fun makeDeposit(acc: Account) { // 메서드의 매개변수로 의존 클래스 전달
        acc.depoosit(balance) // 내부에서 의존 클래스의 메서드 실행
    }
}

val acc = Account(123)
val cus = Customer(3000)
cus.makeDeposit(acc)

println(acc.accountNo)
println(acc.balance)

//입금
//123
//3000

위 코드는 두 클래스를 정의하고 특정 기능을 처리하는 메서드에 객체를 전달해서 그 메서드가 처리가 완료되도록 지원한다.

특정 고객이 계좌를 만들 때 Account클래스의 객체를 전달해서 처리하도록 지정했다.

고객이 계좌를 소유하지만 변경할 수 있다.

 

2. 속성과 메서드 재정의

속정 정의

코틀린은 속성을 일반적인 변수처럼 정의하고 살용한다.

속서으로 사용하려면 게터와 세터를 사용자 정의를 통해 재정의할 수 있다.

 

속성의 메서드 세터를 비공개로 처리 -> 외부에서 변경 불가

class Counterr {
    var value: Int = 0 // 변경 가능한 속성 정의
        get() {
            println("get value $field")
            return field
        }
        private set // 비공개 속성을 사용해서 외부 갱신 금지

    fun inc_() = value++ // 메서드로 내부에서 비공개 속성 갱신
}

val counter = Counterr()
for (i in 1..5) {
    counter.inc_() // 외부에서는 메서드로 속성 갱신
}
println(counter.value)
//get value 0
//get value 1
//get value 2
//get value 3
//get value 4
//get value 5
//5

클래스 내 속성의 세터를 비공개 처리했다. (게터는 공개)

객체를 생성하고 속성을 조회할 수 있지만, 갱신은 할 수 없다.

이 속성의 값을 변경하려면 별도의 메서들르 만들어야 한다.

 

연산자 오버로딩

직접 사용자가 클래스를 정의할 때 연산자에 해당하는 메서드를 작성하면 객체가 연산자로 처리할 수 있다.

operator 예약어를 연산자를 재정의하는 메서드 앞에 붙인다.

class Amount(var total: Int, var balance: Int) { // 클래스 정의
    operator fun plus(other: Amount) = Amount( // 메서드로 연산자 오버로딩
        this.total + other.total,
        this.balance + other.balance
    )

    operator fun plus(scale: Int) = Amount(
        this.total + scale,
        this.balance + scale
    )

    override fun toString() = "Amount($total, $balance)"
}

val amt = Amount(200, 100)
val amt2 = Amount(300, 100)
val amt3 = amt + amt2
println(amt3) // Amount(500, 200)

val amt4 = amt2 + 100
println(amt4) // Amount(400, 200)

클래스 내부에 operator로 지정한 plus 메서드를 정의했다.

동일한 이름이지만 매개변수 자료형이 달라서 메서드 오버로딩 방식으로 2개의 메서드를 정의할 수 있다.

 

infix처리

두개의 변수 가운데에 오는 함수

예약어 infix는 함수나 메서드를 정의할 때 가장 앞에 지정한다.

infix fun Int.add(x:Int):Int{
    return this + x
}

println(3.add(5)) //8
println(3 add 5) //8

 

3. 특정 자료를 다루는 클래스 알아보기

데이터 클래스

클래스 간 전송 등을 하려면 데이터 즉 속성만을 가진 클래스가 필요하다. 이때 데이터 클래스를 정의해서 사용하면 편리하다.

toString(), hashCode(), equals(), copy(), componentsN() 메소드를 자동으로 만들어주는 클래스이다.

data class User(val name: String, var age: Int)

val u1 = User("Ej", 20)
val u2 = User("Ej", 20)

println(u1 == u2) // true
println(u1 === u2) // false

val u3 = User("Eunjeong", 20)
val u4 = u3.copy(age = 10)

println(u3 == u4) // false
println(u3 === u4) // false

동일한 값을 갖는 데이터 클래스의 객체를 생성하고 변수에 할당했다.

이 두 객체는 값은 같지만 다른 객체라는 것을 알 수 있다.

다른 객체를 만들고 이 객체를 복사해서 비교하면 값도 다르고 객체도 다른 것을 알 수 있다.

 

이넘 클래스

특정 상숫값을 관리하는 클래스이다.

  • 예약어 enum을 클래스 앞에 작성
  • 생성할 객체를 모두 내부에 정의하고 이 객체의 이름을 상수처럼 사용한다. -> 이름을 모두 대문자로 작성
enum class CardType{
    SILVER, GOLD, PLATINUM
}

println(CardType.PLATINUM.name) // 객체의 이름
println(CardType.PLATINUM.ordinal) // 객체의 순서
println(CardType.SILVER < CardType.PLATINUM) // 객체간 비교

//PLATINUM
//2
//true


fun changeCardType(cardType:CardType) = when(cardType){
    CardType.SILVER -> CardType.GOLD
    CardType.GOLD -> CardType.PLATINUM
    CardType.PLATINUM -> CardType.SILVER
}

이넘 클래스는 특정 범주를 객체로 처리한다.

이 범주 조건으로 처리하면 when 조건식으로 표현할 때 else를 정의하지 않아도 된다.

 

.ordianl 추천하지 않는다. 

=> 중간에 순서를 바꾸면  ordinal이 바뀔 수 있기 때문.

대신, 프로퍼티를 하나 더 추가하는것이 더 안전

 

enum class CardType(val color: String) { // 클래스에 속성 추가
    SILVER("gray"),
    GOLD("yellow"),
    PLATINUM("black")
}

println(CardType.SILVER.color) // 이넘 객체 내부의 속성 조회
println(CardType.valueOf("SILVER")) // 클래스 메서드르 이넘값 조회

이넘 클래스에 속성을 추가하여 사용할 수 있다.

 

The values() function is still supported, but we recommend that you use the entries property instead.

 

인라인 클래스

기본 자료형과 구별해서 새로운 자료형이 필요한 경우 인라인 클래스를 만든다.

@JvmInline을 사용하고, 예약어 value를 클래스 앞에 작성한다.  (inline 키워드는 deprecated)

// For JVM backends
@JvmInline
value class Password(private val s: String)

// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production")

인라인 클래스에는 기본 생성자에서 초기화된 단일 속성이 있어야 한다.

런타임 시 인라인 클래스의 인스턴스는 이 단일 속성을 사용하여 표시된다. 

 

@JvmInline
value class Person(private val fullName: String) {
    // Allowed since Kotlin 1.4.30:
    init {
        check(fullName.isNotBlank()) {
            "Full name shouldn't be empty"
        }
    }
    // Allowed by default since Kotlin 1.9.0:
    constructor(name: String, lastName: String) : this("$name $lastName") {
        check(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }
}

 

+)

data object - 1.9.0

toString(), equals(), hashCode() 존재.

내부 프로퍼티가 없는 경우에 사용

- dataclass와 차이점 : copy, componentN이 없다. 

 

data object EndOfFile : ReadResult

https://kotlinlang.org/docs/inline-classes.html#inline-classes-vs-type-aliases

 

Inline value classes | Kotlin

 

kotlinlang.org