[TIL] 객체 지향 프로그래밍 <클래스와 객체> (2/2)

30298 단어 TIL_KotlinTILTIL

Do it! 코틀린 프로그래밍 [둘째마당, 객체 지향 프로그래밍] 학습

✏️4. super와 this의 참조✏️

4-1. super

  • 상위 클래스를 가리키는 키워드
  • 상위 클래스의 메서드나 프로퍼티를 참조하기 위해 super.메서드() super.프로퍼티의 형식으로 사용
  • super()를 사용해 생성자를 호출할 수 있음
// 부모클래스
open class Bird(var vol: Int) { 
	open fun sing(vol: Int) = println("Sing vol: $vol) // (1)
}

// 자식클래스
class Parrot(name: String) { 
	override fun sing(vol: Int) {
    	super.sing(vol) // Bird 클래스의 sing() 실행. (1) 출력
        println("I'm a parrot! Ths volume level is $vol")
    }
}

4-2. this

  • 현재 객체를 참조하는 키워드


📌 바깥 클래스 호출하기

  • 이너 클래스(Inner Class) : 특정 클래스 안에 선언된 클래스
  • (1) 이너 클래스에서 바깥 클래스의 상위(부모) 클래스를 호출하려면 super 키워드와 함께 @ 기호 옆에 바깥 클래스 이름을 작성


📌 인터페이스에서 참조하기

  • 인터페이스(Interface)
    • 일종의 구현 약속으로 인터페이스를 참조하는 클래스는 인터페이스가 가지고 있는 내용을 구현해야 하는 가이드를 제시
    • 기본적으로 open으로 선언
  • 코틀린은 한 번에 2개 이상의 클래스를 상속받는 다중 상속이 안됨
  • 필요한 만큼 다수의 인터페이스를 지정해 구현
  • 각 인터페이스의 프로퍼티나 메서드의 이름이 중복될 경우 앵클 브래킷(<>) 을 사용해 접근하려는 클래스나 인터페이스의 이름을 정해줌
open class A {
	open fun f() { }
    fun a() { }
}

interface B {
	fun f() { } //oepn , 클래스A의 f()와 중복
    fun b() { }
}

class C: A(), B { // 쉼표를 사용해 클래스와 인터페이스 지정

	// 클래스A와 인터페이스B에서 f()함수의 이름이 중복되므로
    // 클래스C에서 필수적으로 f()를 오버라이딩 해주어야함
	override fun f() { /* 재정의 */}
    
    fun test() {
    	f() // 클래스C의 fun()
        b() // 인터페이스B의 b()
        super<A>.f() // A 클래스와 B인터페이스의 함수명이 겹침
        super<B>.f() // 앵클 브래킷을 사용해 접근하려는 클래스와 인터페이스의 이름을 정해줌
    }
}

✏️5. 정보 은닉 캡슐화✏️

클래스를 작성할 때 속성이나 기능을 숨기는것을 캡슐화(Encapsulation)라고 함. 이러한 정보 은닉은 객체 지향 프로그래밍의 가장 큰 특징

📌 가시성 지시자

  • 가시성(Visibility) : 각 클래스나 메서드, 프로퍼티의 접근 범위
  • 코틀린의 가시성 지시자 : private, public, protected, internal
  • 가시성 지시자를 선언하지 않으면 public이 기본값
  • 주 생성자 앞에 가시성 지시자를 사용하는 경우 constructor 키워드 생략 불가능

📌 가시성 지시자의 접근 범위


5-1. private

  • 같은 클래스 또는 같은 파일 안의 멤버만 접근 가능
  • 같은 파일에서는 PrivateClass의 객체를 생성 할 수 있음
  • 만일 다른 클래스에서 프로퍼티로서 PrivateClass의 객체를 지정하려면 프로퍼티의 가시성 지정자를 private로 선언해야함
private class PrivateClass {
    private var i = 1
    private fun privateFunc(){ }
}

class OtherClass{
    val opc = PrivateClass() // 불가 - opc는 private이 되어야 함
    fun test() {
        val pc = PrivateClass() // 생성 가능
    }
}

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

fun TopFunction() {
    val tpc = PrivateClass() // 객체 생성 가능
}

5-2. protected

  • 하위 상속 요소(자식 클래스)에서 접근 가능
  • 최상위에 선언된 요소에는 지정 불가, 클래스나 인터페이스와 같은 요소의 멤버에만 지정 가능
open class Base {
    protected var i = 1
    protected fun protectedFunc() {}

    fun access() {
        protectedFunc() // 접근 허용 
    }
    protected class Nested // 내부 클래스에는 지시자 허용
}

class Derived : Base() {
    fun text(base: Base): Int{
        protectedFunc() // Base 클래스의 자식 클래스이므로 접근 가능
        return i
    }
}

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

5-3. internal

  • 같은 정의의 모듈 내부에서 접근 가능
  • internal은 프로젝트 단위의 모듈을 가리키기도 함
  • 다른 모듈이 없이 하나만 있는 경우 internal의 접근 범위는 프로젝트 전체
  • 다른 파일에서도 접근 가능하나 패키지 이름이 다르다면 해당 패키지를 import해야 해당 클래스를 사용 가능

📍자바의 가시성 지시자 기본값인 package는 코틀린에서 사용하지 않음. 프로젝트 단위 묶음의 .jar 파일이 달라져도 패키지 이름이 동일하면 다른 파일에서도 접근 할 수 있는 보안 문제를 막고자 코틀린에서는 internal을 사용

internal class InternalClass {
    internal var i = 1
    internal fun icFunc() { }
    
    fun access(){
        icFunc() // 접근 가능
    }
}

class Other {
    val ic = InternalClass() // 불가
    internal val ic = InternalClass() // 가능 - 프로퍼티를 internal로 맞춰주어야함
    
    fun test(){
        ic.i // 접근 가능
        ic.icFunc() // 접근 가능
    }
}

fun main() {
    val mic = InternalClass()
    mic.i // 접근 가능
    mic.icFunc() // 접근 가능
}

5-4. 가시성 지시자와 클래스의 관계

open class Base{
    private val a=1
    protected open val b=2
    internal val c =3
    val d = 4 // public
    
    protected class Nested {
        // a, b, c, d, e, f 접근 가능
        public val e: Int = 5
        private val f: Int = 6
    }
}

class Derived: Base(){
    // b, c, d, e 접근 가능
    // a, f 접근 불가
    override  val b = 5
}

class Other(base: Base){
    // base.a, base.b -> 접근 불가
    // base.c, base.d -> 접근 가능
    // Base.Nested ->접근 불가
}

✏️6. 클래스와 클래스의 관계✏️

클래스 간의 관계는 두 클래스가 서로 참조하느냐 아니냐에 따라 나뉘고, 그런 다음 두 클래스가 생명 주기에 영향을 주는지에 따라 나뉠 수 있음



6-1. 연관(Association) 관계

  • 서루 분리된 2개의 클래스가 연결을 가지는 것
  • 단방향 혹은 양방향으로 연결
  • 두 요소가 서로 다른 생명주기를 가짐
class Patient(val name: String) {
    // Doctor 클래스를 인자로 참조
    fun doctorList(d: Doctor) { }
}

class Doctor(val name: String) {
    // Patient 클래스를 인자로 참조
    fun patientList(p: Patient) { }
}

fun main() {
    val doc1 = Doctor("KimSabu")
    val patient1 = Patient("Kildon")
}

-> 서로 독립적인 생명주기를 가짐
-> 양방향 참조


6-2. 의존(Dependency) 관계

  • 한 클래스가 다른 클래스에 의존되어 있어 영향을 주는 경우
class Patient(val name: String) {
    fun doctorList(d: Doctor) { }
}

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

fun main() {
    val patient1 = Patient("Kildon")
    // Patient를 매개변수로 받아야 하므로 Patient 객체가 먼저 생성되어있어야 함
    // 따라서 Doctor클래스는 Patient 클래스에 의존
    val doc1 = Doctor("KimSabu", patient1)
}

6-3. 집합(Aggregation) 관계

  • 연관 관계와 거의 동일하지만 특정 객체를 소유한다는 개념이 추가
class Pond(_members: MutableList<Duck>){
    val members: MutableList<Duck> = _members
    
    // 주생성자가 존재한다면 부생성자는 무조건 주생성자에게 직간접적으로 생성을 위임해야함
    // 따라서 부생성자는 this를 통해 주생성자에게 생성을 위임
    constructor(): this(mutableListOf<Duck>())
}

class Duck()

fun main() {
    val pond = Pond()
    val duck1 = Duck()
    val duck2 = Duck()
    
    pond.members.add(duck1)
    pond.members.add(duck2)
}

-> 연못 객체 pond는 오리 객체 duck1과 duck2를 소유


6-4. 구성(Composition) 관계

  • 특정 클래스가 어느 한 클래스의 부분이 되는 것
  • 구성품으로 지정된 클래스는 생명주기가 소유자 클래스에 의존되어 있음
  • 만일 소유자 클래스가 삭제되면 구성하고 있떤 클래스도 같이 삭제
class Car(val power: String) {
    private var engine = Engine(power)
    
    fun startEngine() = engine.start()
}

// Engine 클래스는 Car클래스의 생명주기에 의존적
class Engine(power: String){
    fun start() { }
}

fun main() {
	// car객체 생성과 동시에 engine 객체도 생성
    // car객체가 삭제되면 engine 객체도 삭제
    val car = Car("100hp")
    car.startEngine()
}

좋은 웹페이지 즐겨찾기