[TIL] 객체 지향 프로그래밍 <클래스와 객체> (2/2)
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()
}
Author And Source
이 문제에 관하여([TIL] 객체 지향 프로그래밍 <클래스와 객체> (2/2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@zzangdd/TIL-객체-지향-프로그래밍-클래스와-객체-22저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)