탐비의 개발 낙서장

[Kotlin] 코틀린 객체지향 설계와 다양한 클래스 타입 본문

프로그래밍/Kotlin

[Kotlin] 코틀린 객체지향 설계와 다양한 클래스 타입

탐비_ 2021. 7. 27. 23:44
클래스 / 객체 / 인스턴스

 

클래스(Class) - 설계도

 - 객체를 만들어내기 위한 설계도

 - 연관되어 있는 변수들과 메소드 들이 저장되어 있습니다.

 

객체(Object) - 설계도로 구현한 모든 대상

 - 소프트웨어에 구현할 대상으로, 클래스에 선언된 대로 생성한 실체

 - OOP의 관점에서, 클래스의 타입으로 선언되었을 때, "객체"라고 부릅니다.

 

인스턴스(Instance) - 객체가 메모리에 할당된 것

 - 클래스라는 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체

 - 객체를 소프트웨어에 실체화 한 것

 - JAVA에서는 객체를 new를 이용해 메모리에 할당 한 경우에 인스턴스가 생성되었다고 합니다.

 

 

 

Data Class  / Sealed Class
Data Class

 

data class DataClass(val data1: Int, var data2: Int)

 

 - 데이터만 담기 위한 클래스

 - 변수 및 getter / setter를 자동으로 생성해 줍니다.

 - component 함수 생성 : 객체의 값을 여러 변수로 분리해서 사용할 수 있음

 

val (var1, var2) = DataClass(10, 20)

 

 - copy() 함수 지원 : 객체를 복사 할 때 일부 값만 변경할 때 사용

 

// 인자를 사용하지 않으면 주소가 다른 객체 반환
dc = dc.copy(data2=500)

 

 - toString, hashCode, equals 등 Any에서 작성된 메소드 자동 오버라이딩

 

 

Sealed Class

 

// Kotlin Reference Example
sealed class Expr{
    class Const(val number : Double) : Expr()
    class Sum(val e1 : Expr, val e2 : Expr) : Expr()
    object NotANumber : Expr()
}

 

 - object를 이용하면 객체를 한 개만 생성하도록 제한하는 싱글톤 디자인을 지원합니다.

 - Sealed class의 가장 강력한 점은 when 표현식의 용이함입니다.

 

// SealedClass when
fun eval(expr : Expr) : Double= when (expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1)+eval(expr.e2)
    Expr.NotANumber -> Double.NaN
}
// NotSealedClass when
fun tmpEval(exprNonSealed: ExprNonSealed) : Double=when(exprNonSealed){
    is ExprNonSealed.Const -> exprNonSealed.number
    is ExprNonSealed.Sum -> tmpEval(exprNonSealed.e1)+tmpEval(exprNonSealed.e2)
    is ExprNonSealed.NotANumber->1.2
}

 

 - 이 외에도 sealed Class를 이용하여 안드로이드 UI 상태 관리를 하면 생산성 향상 및 유지보수를 용이하게 해줍니다.

   참고

 

Kotlin Sealed class를 사용한 UI 상태 관리 (1/3)

드로이드나이츠 2018 에서 발표한 Kotlin Sealed class 를 이용한 뷰상태 관리 를 조금 더 자세하게 정리해서 공유합니다. SealedClass는 무엇이고? 뷰상태 는 무엇인가?? 하나씩 천천히 정리하겠습니다.

medium.com

 

 

 

this / super 키워드

 

this

 - 현재 클래스의 메소드, 프로퍼티, 생성자를 사용하는 키워드

  ex) this.method / this.property / this()

 

super

 - 상위 클래스의 메소드, 프로퍼티, 생성자를 사용하는 키워드

  ex) super.method / super.property / super()

 

 

 

객체 인스턴스 비교

 

동일성과 동등성

 

val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Shakespeare", "Twain", "Hemingway") 

println(authors == writers) // 1 
println(authors === writers) // 2 
println(authors === authors) // 3

 - 동등성 ( == / JVM에서 내부적으로 equals() 호출 )

  단순히 값이 같은지만 비교하기 때문에 1번 케이스는 true를 반환합니다.

 

 - 동일성 ( === )

  같은 주소 값을 가지는지를 판단하기 때문에  2번 케이스는 false, 실제로 같은 3번 케이스는 true를 반환합니다.

 

 

is

 

open class KotlinA {
    val a = 5
}
 
class KotlinB : KotlinA() {
    val b = 10
}
 
fun main() {
    val obj: KotlinA = KotlinB()
    if (obj is KotlinB) {  // smart casting
        println(obj.b)  // 10
    }
}

 

 - 자바에서의 instanceof가 없고, 대신 is 키워드가 존재합니다.

 - is를 사용시 바로 스마트캐스팅이 되어 위 예시에서 KotlinB 타입이라는 것을 확인한 이후부터 KotlinB 타입처럼 사용 가능합니다.

 

 

 

SOLID 원칙
Single Responsiblity Principle (단일 책임 원칙)

 

소프트웨어의 설계 부품(클래스, 함수 등)은 단 하나의 책임만을 가져야 한다.

 

 책임이란, 일반적으로 '기능' 정도의 의미로 해석됩니다. 설계를 잘 한 프로그램은 새로운 요구사항과 프로그램 변경에 영향을 받는 부분이 적습니다. 이는 높은 응집도와 낮은 결합도를 가진 프로그램을 의미합니다.

 

 만약, 한 클래스가 책임이 많아지면 클래스 내부의 함수끼리 강한 결합이 발생하여 유지보수 비용이 증가하게 됩니다.

 

Open-Closed Principle (개방-패쇄 원칙)

 

기존의 코드를 변경하지 않고(Closed) 기능을 수정하거나 추가할 수 있도록(Open) 설계해야 한다.

 

 OCP를 위한 설계를 할 때, 변경되는 것이 무엇인지에 초점을 맞춥니다. 이를 위해 사용하는 문법이 인터페이스입니다.

 

Liskov Substitution Principle (리스코프 치환 원칙)

 

자식 클래스는 부모클래스에서 가능한 행위를 수행할 수 있어야 한다.

 

 리스코프 치환 원칙은 MIT 컴퓨터 사이언스 교수인 리스코프가 제안한 설계 원칙입니다.

 

 부모 클래스와 자식 클래스 사이의 행위에는 일관성이 있어야 한다는 원칙이며, 이는 객체 지향 프로그래밍에서 부모 클래스의 인스턴스 대신 자식 클래스의 인스턴스를 사용해도 문제가 없어야 한다는 것을 의미합니다.

 

 상속 관계에서는 일반화 관계(IS-A)가 성립해야 한다. 일반화 관계에 있다는 것은 일관성이 있다는 것으로, 리스코프 치환 원칙은 일반화 관계에 대해 묻는 것이라 할 수 있습니다.

 

 

Interface Segregation Principle (인터페이스 분리 원칙)

 

한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다.

 

이는 다시 말해서, 자신이 사용하지 않는 기능(인터페이스)에는 영향을 받지 말아야 한다는 의미입니다. 

 

 

Dependency Inversion Principle (의존 역전 원칙)

 

의존 관계를 맺을 때, 변화하기 쉬운것 보단 변화하기 어려운 것에 의존해야 한다는 원칙이다. 

 

 여기서 말하는 변화하기 쉬운것이란 구체적인 것을 말하고, 변화하기 어려운 것이란 추상적인 것을 말합니다.

 

 객체지향적인 관점에서 보자면 변화하기 쉬운것이란 구체화 된 클래스를 의미하고, 변화하기 어려운 것은 추상클래스나 인터페이스를 의미하는 것입니다.

 

 따라서 DIP를 만족한다는 것은 의존관계를 맺을 때, 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺는다는 것을 의미합니다.