[Kotlin] 5. 클래스와 객체

문상훈·2022년 7월 21일
0

Kotlin

목록 보기
6/7

🛴 05. 클래스와 객체

📌 05-1 클래스와 객체의 정의

객체 지향 프로그밍 (OOP: Object-Oriented Programming) : 프로그램의 구조를 객체 간 상호작용으로서 표현하는 프로그래밍 방식
↔ 절차적 프로그래밍 (Procedural Programming) : 코딩한 순서대로 프로그램이 수행될 수 있도록 작성하는 방법

  • 연속적인 코드의 순서에 따라 작동하기 때문에 단순하고 오류를 예측하기 쉽지만 구조적이지 못해 복잡한 코드를 설계하기 어려움

📫 알아둘 개념

  • 추상화 : 특정 클래스를 만들 때 기본 형식을 규정하는 방법
  • 인스턴스 : 클래스로부터 생성한 객체
  • 상속 : 부모 클래스의 내용을 자식 클래스가 그대로 물려받음
  • 다형성 : 하나의 이름으로 다양한 처리를 제공
  • 캡슐화 : 내용을 숨기고 필요한 부분만 사용
  • 메시지 전송 : 객체 간에 주고받는 메시지
  • 연관 : 클래스 간의 관계

객체 지향 프로그래밍과 용어

  • 객체 지향 프로그래밍의 용어 정리
코틀린에서 사용하는 언어다른 언어에서 사용하는 언어
클래스 (Class)분류, 범주
프로퍼티 (Property)속성, 변수, 필드, 데이터
메서드 (Method)함수, 동작, 행동
객체 (Object)인스턴스

📫 자바와 코틀린 비교

자바코틀린
메서드 (클래스에 포함된 기능을 나타내는 함수)메서드 (클래스에 포함된 기능을 나타내는 함수)
필드 (변수)프로퍼티 (변수 or 필드에 내부적으로 접근 메서드가 포함되어 있기 때문)
  • 통합 모델링 언어(UML : Unified Modeling Language) : 클래스를 이해하기 쉽게 표현하기 위해 사용
    • 객체 지향 프로그램 설계를 위한 다이어그램 표기법
이름설명사용
클래스 다이어그램 (Class Diagram)클래스의 정의와 관계를 나타내는 다이어그램클래스의 이름, 프로퍼티, 메서드를 손쉽게 파악할 수 있음
시퀀스 다이어그램 (Sequence Diagram)시간의 개념을 통해 클래스에서 생성된 객체의 실행 흐름을 나타냄실행 순서를 보기에 적합
유스 케이스 다이어그램 (Use Case Diagram)사용자 관점에서 사용 방법에 대해 설명시스템에 사용자가 어떤 식으로 접근해 사용할 것인지 보여줌
상태 머신 다이어그램 (State-Machine Diagram)시스템 관점에서 상태가 어떻게 변화하는지 나타냄객체의 상태 변화를 볼 수 있음
  • 클래스 다이어그램
    • 클래스의 정의를 한눈에 파악할 수 있다.
    • +public, -private을 의미

클래스와 추상화

추상화(Abstraction) : 속성과 동작을 정의하는 과정

Bird
+name: Sting
+wing: Int = 2
+beak: String
+color: String
-----------------------------------------------------------------------
+fly(): Unit
+sing(vol:Int): Unit
  • 새의 특징 : name, wing, beak, color
  • 새의 동작 : fly(), sing()

클래스 선언하기

class Bird {}
class Bird // 중괄호는 생략가능
fun main() {
    val coco = Bird() // 객체 생성
    coco.color = "blue" // 프로퍼티에 값 할당

    println("coco.color : ${coco.color}")
    coco.fly()
    coco.sing(3)
}

class Bird {
	// 프로퍼티 선언
    var name: String = "mybird"
    var wing: Int = 2
    var beak: String = "short"
    var color: String = "blue"
	// 메서드 (함수) 선언
    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}

출력 :

coco.color : blue
Fly wing : 2
Sing vol : 3

객체와 인스턴스 정리하기

  • Bird 클래스란 일종의 선언일 뿐 실제 메모리에 존재해 실행되고 있는 것은 아님
    📍 클래스로부터 객체를 생성해야만 객체가 물리적인 메모리 영역에서 실행됨 -> 구체화, 인스턴스화

  • 인스턴스 : 메모리에 올라간 객체

  • 특정 클래스로부터 만들어진 객체는 그 클래스의 인스턴스
    📍 객체는 더 포괄적인 용어, 특정 클래스가 아닌 실체화된 것

📫 새의 개념 (Brid)으로 부터 인스터화해 객체(coco)를 만들었다.

  • Bird 클래스로부터 coco라는 객체가 생성된다.
  • coco는 Bird 클래스의 인스턴스이다.

📌 05-2 생성자

생성자 : 클래스를 통해 객체가 만들어질 때 기본적으로 호출되는 함수 (=객체를 생성할 때 자동 실행되는 함수)

  • constructor() : 외부에서 인자를 받아 초기화할 수 있도록 특별한 함수 정의
    • 객체를 생성할 때 필요한 값을 설정하여 객체를 만듦
class 클래스이름 contructor(필요한 매개변수 ...){ // 주 생성자의 위치
	constructor (필요한 매개변수 ...) { // 부 생성자의 위치
    // 프로퍼티의 초기화
   }
   constructor (필요한 매개변수 ...) { // 추가 부 생성자
   }
}

부 생성자

  • 부 생성자는 클래스의 본문에 함수처럼 선언
  • 부 생성자를 통해 매개변수를 다르게 여러 번 정의할 수 있다.
fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
    coco.color = "yellow"

    println("coco.color : ${coco.color}")
    coco.fly()
    coco.sing(3)
}

class Bird {
    var name: String
    var wing: Int
    var beak: String
    var color: String

    constructor(name: String, wing: Int, beak: String, color: String) {
        this.name = name
        this.wing = wing
        this.beak = beak
        this.color = color
    }

    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}

출력 :

coco.color : yellow
Fly wing : 2
Sing vol : 3
  • 부 생성자를 이용하면 프로퍼티를 선언할 때 초기화해 둘 필요가 없다.
    📍 객체를 생성할 때 생성자가 초깃값을 전달해 올 것이기 때문에
  • costruct()name과 프로퍼티의 name은 서로 다른 변수
    📍this 키워드로 클래스 내부에 있는 함수의 프로퍼티를 참조한다.

📍 매개변수 앞에 언더스코어(_)를 사용해 this키워드를 대신할 수 있다.

    constructor(_name: String, _wing: Int, _beak: String, _color: String) {
        name = _name
        wing = _wing
        beak = _beak
        color = _color
    }

부 생성자를 여러 개 포함한 클래스 사용하기

  • 코틀린에서는 부 생성자를 클래스에 하나 이상 포함할 수 있다.
    • constructor() 함수 형태로 매개변수가 다르게 여러 번 선언할 수 있다.
fun main() {
    val bird1 = Bird("mybird", 2, "short", "blue")
    val bird2 = Bird("mybird2", "long")
}

class Bird {
    var name: String
    var wing: Int
    var beak: String
    var color: String

    constructor(_name: String, _wing: Int, _beak: String, _color: String) {
        name = _name
        wing = _wing
        beak = _beak
        color = _color
    }

    constructor(_name: String, _beak: String) {
        name = _name
        wing = 2
        beak = _beak
        color = "grey"
    }

    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}
  • 해당 인자의 개수에 따라 생성자를 다르게 호출할 수 있다.

주 생성자

주 생성자 : 클래스 이름과 함꼐 생성자 정의를 이용한다.

  • 주 생성자는 클래스 이름과 블록 시작 부분 사이에 정의
class Bird constructor(_name: String, _wing: Int, _beak: String, _color: String) {
    var name = _name
    var wing = _wing
    var beak = _beak
    var color = _color


    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}
  • constructor() 생략 가능
fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
    coco.color = "yellow"

    println("coco.color : ${coco.color}")
    coco.fly()
    coco.sing(3)
}

class Bird(var name: String, var wing: Int, var beak: String, var color: String) {


    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}

출력 :

coco.color : yellow
Fly wing : 2
Sing vol : 3
  • 주 생성자의 매개변수에 프로퍼티 선언 -> 본문에서 프로퍼티 선언을 생략할 수 있다.

초기화 블록을 가진 주 생성자

  • 클래스 이름 다음에 주 생성자를 표현하는 경우 클래스 블록 {} 안에 코드를 넣을 수 없다
    -> 초기화에 사용할 코드가 있다면 init{...}초기화 블록을 선언부에 넣어준다.
fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
    coco.color = "yellow"

    println("coco.color : ${coco.color}")
    coco.fly()
    coco.sing(3)
}

class Bird(var name: String, var wing: Int, var beak: String, var color: String) {

    init {
        println("~초기화 블록 시작~")
        println("이름은 $name 부리는 $beak")
        sing(5)
        println("~초기화 블록 끝~")
    }

    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}

출력 :

~초기화 블록 시작~
이름은 mybird 부리는 short
Sing vol : 5
~초기화 블록 끝~
coco.color : yellow
Fly wing : 2
Sing vol : 3

+) 생성자의 매개변수에 기본값 사용할 수 있다.


📌 05-3 상속과 다형성

상속(Inheritance) : 클래스는 자식 클래스를 만들 때 상위 클래스(부모 클래스) 의 속성과 기능을 물려받아 계승
📍 하위 클래스는 상위 클래스의 모든 내용을 다시 만들지 않아도 된다.

상속과 클래스의 계층

  • 코틀린의 모든 클래스는 Any 클래스의 하위 클래스가 된다.
    • 클래스를 명시하지 않으면 Any클래스를 상속받게 된다.
  • open 키워드 사용 : 클래스를 상속할 수 있는 상태
    • open이 없으면 상속할 수 없는 기본 클래스

📫 자바에서는 기본적으로 선언하는 클래스가 상속 가능한 클래스
-> 자바에서 상속할 수 없는 클래스로 선언하려면 final 키워드 사용

fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
    val lark = Lark("mylark", 2, "long", "brown")
    val parrot = Parrot("myparrot", 2, "short", "multiple", "korean")
    coco.color = "yellow"

    println("Coco : ${coco.name}, ${coco.wing}, ${coco.beak}, ${coco.color}")
    println("Lark : ${lark.name}, ${lark.wing}, ${lark.beak}, ${lark.color}")
    println("Parrot : ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")
    lark.singHitone()
    parrot.speak()
    lark.fly()
}

open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {

    fun fly() = println("Fly wing : $wing")
    fun sing(vol: Int) = println("Sing vol : $vol")
}

// 주 생성자를 사용하는 상속
class Lark(name: String, wing: Int, beak: String, color: String) : Bird(name, wing, beak, color) {
    fun singHitone() = println("Happy song!")
}

// 부 생성자를 사용하는 상속
class Parrot : Bird {
    val language: String

    constructor(name: String, wing: Int, beak: String, color: String, language: String) : super(
        name,
        wing,
        beak,
        color
    ) {
        this.language = language
    }

    fun speak() = println("Speak $language")
}

출력 :

Coco : mybird, 2, short, yellow
Lark : mylark, 2, long, brown
Parrot : myparrot, 2, short, multiple, korean
Happy song!
Speak korean
Fly wing : 2
  • 클래스를 선언할 때 ㅤ:ㅤ 콜론 앞 뒤에 공백이 있으면 상속
    • 변수 선언처럼 공백 없이 붙여 써도 문제 없지만 클래스 선언, 변수선언을 구분하기 위한 코딩 관례
      📍 하위 클래스는 상위 클래스의 메서드 or 프로퍼티를 그대로 상속하면서 상위 클래스 없는 프로퍼티나 메서드로 확장시킬 수 있다.

다형성

다형성(polymorphism) : 이름이 동일하지만 매개변수가 서로 다른 형태를 취하거나 실행 결과를 다르게 가질 수 있다.

  • 오버로딩(overloading) : 동작은 동일하지만 인자의 형식만 달라짐
  • 오버라이딩(overriding) : 상위와 하위 클래스에서 메서드나 프로퍼티의 이름은 같지만 기존의 동작을 다른 동작으로 재정의

오버로딩

오버로딩 : 같은 클래스 안에서 같은 이름의 메서드가 매개변수만 달리해서 여러 번 정의될 수 있다.

fun main() {
    val calc = Calc()
    println(calc.add(3, 2))
    println(calc.add(3.2, 1.5))
    println(calc.add(3, 3, 2))
    println(calc.add("Hello", "World"))
}

class Calc {
    fun add(x: Int, y: Int): Int = x + y
    fun add(x: Double, y: Double): Double = x + y
    fun add(x: Int, y: Int, z: Int): Int = x + y + z
    fun add(x: String, y: String): String = x + y
}
  • 오버로딩을 사용하면 같은 이름의 메서드로 다양한 인자를 처리할 수 있기 때문에 메서드를 손쉽게 확장할 수 있다.

오버라이딩

오버라이딩 : 하위 클래스에서 새로 만들어지는 메서드가 이름, 매개변수, 반환값이 이전의 메서드와 똑같지만 새로 작성된다.
📍 재정의한다 : 하위의 새로운 메서드는 상위 클래스의 메서드의 내용를 새로 만들어 다른 기능을 하도록 정의한다.

  • 기반 클래스에서는 open, 파생 클래스에서는 override 키워드를 사용한다.
  • 메서드, 프로퍼티 둘 다 오버라이딩 가능하다.
  • final 키워드를 통해 하위 클래스에서 오버라이딩되는 것을 막을 수 있다.
fun main() {
    val parrot = Parrot("myparrot", 2, "short", "multiple", "korean")
    parrot.language = "English"

    println("Parrot : ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")
    parrot.sing(5)
}


open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {

    fun fly() = println("Fly wing : $wing")
    open fun sing(vol: Int) = println("Sing vol : $vol")
}

class Parrot(
    name: String,
    wing: Int = 2,
    beak: String,
    color: String,
    var language: String = "natural"
) : Bird(name, wing, beak, color) {

    fun speak() = println("Speak $language")
    override fun sing(vol: Int) {
        println("I'm a parrot! The volume level is $vol")
        speak()
    }
}

출력 :

Parrot : myparrot, 2, short, multiple, English
I'm a parrot! The volume level is 5
Speak English
  • 오버라이딩은 하위 클래스에서 특정 메서드를 재설계할 때 유용한 기법으로 유연하면서도 사용하기 쉬운 메서드를 설계할 수 있다.

📌 05-4 super와 this의 참조

  • 상위 클래스는 super 하위 클래스는 this 키워드로 참조가 가능하다.
superthis
super.프로퍼티 이름this.프로퍼티 이름
super.메서드 이름()this.메서드 이름()
super()this()

super로 상위 객체 참조하기

  • super로 상위 클래스의 프로퍼티나 메서드, 생성자를 사용할 수 있다.
class Parrot(
    name: String,
    wing: Int = 2,
    beak: String,
    color: String,
    var language: String = "natural"
) : Bird(name, wing, beak, color) {

    fun speak() = println("Speak $language")
    override fun sing(vol: Int) {
        super.sing(vol)
        println("I'm a parrot! The volume level is $vol")
        speak()
    }
}
  • 앞서 사용해온 parrot class에 super.sing(vol)을 추가하면 Sing vol : 5가 출력
    📍 일부는 상위 클래스의 동작을 하고 일부는 현재 클래스에서 새롭게 재정의할 수 있다.

this로 현재 객체 참조하기

fun main() {
    val sean = Devoloper("Jimuk")
}


open class Person {
    constructor(firstName: String) {
        println("[Person] firstName : $firstName")
    }

    constructor(firstName: String, age: Int) {
        println("[Person] firstName : $firstName, age : $age")
    }
}

class Devoloper : Person {
    constructor(firstName: String) : this(firstName, 20) {
        println("[Devoloper] $firstName")
    }

    constructor(firstName: String, age: Int) : super(firstName, age) {
        println("[Devoloper] $firstName, $age")
    }
}

출력 :

[Person] firstName : Jimuk, age : 20
[Devoloper] Jimuk, 20
[Devoloper] Jimuk

응애 나 스무살👶

  • 생성자 코드를 실행하기 전에 this - 현재 클래스를 가리킴, super - 상위 클래스를 가리킴 을 사용해 다른 생성자를 처리할 수 있다.

주 생성자와 부 생성자 함께 사용하기

fun main() {
    val p1 = Person("Jimuk", 20)
    println()
    val p2 = Person("baby")
}


class Person(firstName: String, out: Unit = println("[Primary Constructor] Parameter")) {
    val fName = println("[Property] Person fName : $firstName")

    init {
        println("[Init] Person init block")
    }

    constructor(firstName: String, age: Int, out: Unit = println("[Second Constructor] Parameter")) : this(firstName) {
        println("[Second Constructor] Body : $firstName, $age")
    }
}

출력 :

[Second Constructor] Parameter
[Primary Constructor] Parameter
[Property] Person fName : Jimuk
[Init] Person init block
[Second Constructor] Body : Jimuk, 20

[Primary Constructor] Parameter
[Property] Person fName : baby
[Init] Person init block

확실히 책으로 보는 것보다 직접 작성해 보는 게 더 눈에 잘 보인다..~

  • 생성하는 객체의 인자 수에 따라 부 생성자 or 주 생성자를 호출한다.
  • 헷갈린 부분 : outprintln()을 할당해 인자에 접근할 때 마다 출력
    -> 인자가 들어오면 냅다 출력 🤪

바깥 클래스 호출하기

이너 클래스(Inner Class) : 특정 클래스 안에 선언된 클래스

  • 클래스 안에 다시 클래스를 선언하는 것이 가능하다.
  • 이너 클래스에서 상위 클래스를 호출하려면 super@바깥클래스이름
open class Base {
    open val x: Int = 1
    open fun f() = println("Base Class f()")
}

class Child : Base() {
    override val x: Int = super.x + 1
    override fun f() = println("Child Class f()")
    inner class Inside {
        fun f() = println("Inside Class f()")
        fun test() {
            f()
            Child().f()
            super@Child.f()
            println("[Inside] super@Child.x : ${super@Child.x}")
        }
    }
}

fun main() {
    val c1 = Child()
    c1.Inside().test()
}

출력 :

Inside Class f()
Child Class f()
Base Class f()
[Inside] super@Child.x : 1

인터페이스에서 참조하기

인터페이스(Interfacce) : 일종의 구현 약속으로 인터페이스를 참조하는 클래스는 인터페이스가 가지고 있는 내용을 구현해야 하는 가이드를 제시한다.

  • 인터페이스 자체로는 객체로 만들 수 없고 항상 인터페이스를 구현하는 클래스에서 생성해야 한다.
  • 동일한 이름의 프로퍼티나 메서드가 있으면 앵클 브래킷<>을 사용하여 접근하려난 클래스나 인터페이스의 이름을 정해준다. super<A>.f(), super<B>.f()
  • 인터페이스는 기본적으로 open이다.
  • 클래스는 하나만 지정, 인터페이스는 여러 개 지정 가능
open class A {
    open fun f() = println("A Class f()")
    fun a() = println("A class a()")
}

interface B {
    fun f() = println("B interface f()")
    fun b() = println("B interFACE b()")
}

class C : A(), B {
    override fun f() = println("C Class f()")

    fun test() {
        f()
        b()
        super<A>.f()
        super<B>.f()
    }
}

fun main() {
    val c = C()
    c.test()
}

출력 :

C Class f()
B interFACE b()
A Class f()
B interface f()

📫 코틀린은 자바처럼 한 번에 2개 이상의 클래스를 상속받는 다중 상속이 되지 않는다.


📌 05-5 정보 은닉 캡슐화

가시성 지시자

가시성(Visibility) : 각 클래스, 메서드, 프로퍼티의 접근 범위
📍 공개할 부분과 숨길 부분을 정해줄 수 있다.

private : 외부에서 접근할 수 없다.
public : 어디서든 접근이 가능하다. (default)
protected : 외부에서 접근할 수 없으나, 하위 상속 요소에서 접근 가능하다.
internal : 같은 정의의 모듈 내부에서는 접근 가능하다.

✏️ 선언되는 위치

[가시성 지시자] <val | var> 전역 변수 이름
[가시성 지시자] fun 함수이름 () {...}
[가시성 지시자] [특정 키워드] class 클래스이름 [가시성 지시자] constructor(매개변수) {
	[가시성 지시자] constructor() {...}
    [가시성 지시자] 프로퍼티
    [가시성 지시자] 메서드
}
  • default는 public
  • 주 생성자 앞에 가시성 지시자를 사용하면, constructor 키워드는 생략할 수 없다.

private

: 그 클래스 안의 멤버만 접근할 수 있다.

private class PrivateClass {
    private var num = 1
    private fun privateFunc() {
        num += 1 // 접근 O
    }

    fun access() {
        privateFunc() // 접근 O
    }
}

class OtherClass {
    val opc = PrivateClass() // 불가능 ! private으로 선언해주어야함
    fun test() {
        val pc = PrivateClass() // 생성 가능
    }
}

fun main() {
    val pc = PrivateClass() // 생성 가능
    pc.num // 접근 불가
    pc.pricvateFunc() // 접근 불가
}

fun TopFunction() {
    val tpc = PrivateClass() // 객체 생성 가능
}
  • private으로 선언되어 있어, 다른 파일에서 접근 불가능
  • 같은 파일에서는 PrivateClass의 객체를 생성할 수 있음 -> 다른 클래스에서 지정하려면 private으로 선언 해야함
    • 객체를 생성해도 멤버인 num, 메서드인 privateFuncprivate이라 다른 클래스, main()에서 접근 불가능

protected

: 클래스, 인터페이스와 같은 요소의 멤버에 지정할 수 있다.
📍 최상위에 선언된 요소에는 지정 불가능

open class Base {
    protected var i = 1
    protected fun protectedFunc() {
        i += 1
    } // 같은 요소의 멤버에서 접근 가능

    fun access() {
        protectedFunc()
    } // 접근 가능

    protected class Nested // 내부 클래스에는 지시자 허용
} // 최상위 class에는 protected 사용할 수 없음

class Derived : Base() {
    fun test(base: Base): Int {
        protectedFunc() // 하위 클래스에서 접근 가능 (메서드 접근)
        return i // 하위 클래스에서 접근 가능 (프로퍼티 접근)
    }
}

fun main() {
    val base = Base() // 생성 가능
    base.i // 접근 불가
    base.protectedFunc() // 접근 불가
    base.access() // 접근 가능
}

internal

: 프로젝트 단위의 모듈을 가리킴 -> 모듈이 달라지면 접근할 수 없다.

📫 자바의 package 지시자

  • 접근 요소가 package 내부에 있으면 접근 가능
    • .jar 파일이 달라도 package 이름이 동일하면 접근할 수 있었기 때문에 보안 문제가 있었음

📍 코틀린에서는 package를 버리고 프로젝트의 같은 모듈이 아니면 외부에서 접근할 수 없게 함 !!

internal class InternalClass {
    internal var i = 1
    internal fun icFunc() {
        i += 1 // 접근 허용
    }

    fun access() {
        icFunc() // 접근 허용
    }
}

class Other {
    internal val ic = InternalClass() // 프로퍼티를 만들 때 internal로 통일
    fun test() {
        ic.i
        ic.icFunc()
    }
}

fun main() {
    val mic = InternalClass() // 생성 가능
    mic.i // 접근허용
    mic.icFunc() // 접근허용
}
  • 패키지 이름이 다르면 import 해와서 해당 클래스를 사용하면 된다.

가시성 지시자와 클래스의 관계

  • 하위 클래스에서 가시성 지시자를 사용하거나 다른 클래스와의 연관 관계를 지정하기 위해서도 사용할 수 있다.
open class Car protected constructor(_year: Int, _model: String, _power: String, _wheel: String) {
    private var year: Int = _year
    public var model: String = _model
    protected open var power: String = _power
    internal var wheel: String = _wheel

    protected fun start(key: Boolean) {
        if (key) println("Start the Engine!")
    }

    class Driver(_name: String, _license: String) {
        private var name: String = _name
        var license: String = _license
        internal fun driving() = println("[Driver] Driving() - $name")
    }
}

class Tico(_year: Int, _model: String, _power: String, _wheel: String, var name: String, private var key: Boolean) :
    Car(_year, _model, _power, _wheel) {
    override var power: String = "50hp"
    val driver = Driver(name, "first class")

    constructor(_name: String, _key: Boolean) : this(2014, "basic", "100hp", "normal", _name, _key) {
        name = _name
        key = _key
    }

    fun access(password: String) {
        if (password == "gotico") {
            println("---[Tico] access()---")
            println("super.model = ${super.model}")
            println("super.power = ${super.power}")
            println("super.wheel = ${super.wheel}")
            super.start(key)

            println("Driver().license = ${driver.license}")
            driver.driving()
        } else {
            println("You're a burglar")
        }
    }
}

class Burglar() {
    fun steal(anycar: Any) {
        if (anycar is Tico) {
            println("---[Burglar] steal()---")
            println("anycar.name = ${anycar.name}")
            println("anycar.wheel = ${anycar.wheel}")
            println("anycar.model = ${anycar.model}")

            println(anycar.driver.license)
            anycar.driver.driving()
            anycar.access("dontknow")
        } else {
            println("Nothing to steal")
        }
    }
}

fun main() {
    val tico = Tico("Jimuk", true)
    tico.access("gotico")

    val burglar = Burglar()
    burglar.steal(tico)
}

출력 :

---[Tico] access()---
super.model = basic
super.power = 100hp
super.wheel = normal
Start the Engine!
Driver().license = first class
[Driver] Driving() - Jimuk
---[Burglar] steal()---
anycar.name = Jimuk
anycar.wheel = normal
anycar.model = basic
first class
[Driver] Driving() - Jimuk
You're a burglar

📌 05-6 클래스와 클래스의 관계

클래스 혹은 객체 간의 관계

연관 관계 : 클래스 간의 참조를 유지
의존 관계 : 클래스 간의 참조를 유지하지 않음
집합 관계 : 연관 관계에 있으면서 객체의 생명주기가 서로 유지
구성 관계 : 연관 관계에 있으면서 객체의 생명주기가 서로 유지되지 않음

연관 관계

  • 서로 분리된 클래스가 연결을 갖고 있다.
  • 단방향, 양방향으로 연결될 수 있으며 두 요소가 서로 다른 생명주기를 가진다.
class Patient(val name: String) {

    fun doctorList(d: Doctor) {  // 인자로 참조
        println("Patient: $name, Doctor: ${d.name}")
    }
}

class Doctor(val name: String) {
    fun patientList(p: Patient) { // 인자로 참조
        println("Doctor: $name, Patient: ${p.name}")
    }
}

fun main() {
    val doc1 = Doctor("Hong")
    val patient1 = Patient("KIM")
    doc1.patientList(patient1)
    patient1.doctorList(doc1)
}

출력 :

Doctor: Hong, Patient: KIM
Patient: KIM, Doctor: Hong

의존 관계

  • 한 클래스가 다른 클래스에 의존되어 있어 영향을 주는 경우
    • Doctor 클래스는 Patient를 매개변수로 받아야 하므로 Patient 객체가 먼저 생성되어 있어야함
class Patient(val name: String, var id: Int) {

    fun doctorList(d: Doctor) {
        println("Patient: $name, Doctor: ${d.name}")
    }
}

class Doctor(val name: String, val p: Patient) {

    val customerId: Int = p.id

    fun patientList() {
        println("Doctor: $name, Patient: ${p.name}")
        println("Patient Id: $customerId")
    }
}

fun main() {
    val patient1 = Patient("Jimuk", 1234)
    val doc1 = Doctor("Kim", patient1)
    doc1.patientList()
}

출력 :

Doctor: Kim, Patient: Jimuk
Patient Id: 1234

집합 관계

  • 특정 객체를 소유한다는 개념이 추가됨
    • 오리가 특정 연못을 주거지로 삼는다면, 연못이 오리를 소유할 수 있다.
  • 2개의 객체는 따로 생성되어 서로의 생명주기에 영향을 주진 않는다.
class Pond(_name: String, _members: MutableList<Duck>) {
    val name: String = _name
    val members: MutableList<Duck> = _members

    constructor(_name: String) : this(_name, mutableListOf<Duck>())
}

class Duck(val name: String)

fun main() {

    // 두 개체는 서로 생명주기에 영향을 주지 않는다.
    val pond = Pond("myFavorite")
    val duck1 = Duck("Duck1")
    val duck2 = Duck("Duck2")

    // 연못에 오리를 추가 - 연못에 오리가 집합한다
    pond.members.add(duck1)
    pond.members.add(duck2)


    // 연못에 있는 오리들
    for (duck in pond.members) {
        println(duck.name)
    }
}

출력 :

Duck1
Duck2

구성 관계

  • 특정 클래스가 어느 한 클래스의 부분이 된다.
class Car(val name: String, val power: String) {

    private var engine = Engine(power) // Engine클래스 객체는 Car에 의존적이다

    fun startEngine() = engine.start()
    fun stopEngine() = engine.stop()
}

class Engine(power: String) {
    fun start() = println("Engine has been started.")
    fun stop() = println("Engine has been stopped.")
}

fun main() {
    val car = Car("tico", "100hp")
    car.startEngine()
    car.stopEngine()
}

출력 :

Engine has been started.
Engine has been stopped.
profile
내가 왜 개발잔거지

0개의 댓글