OOP: Object-Oriented Programming
프로그램의 구조를 객체 간 상호작용으로서 표현하는 프로그램이 방식
절차적 프로그램밍의 한계를 극복하고자 나온 방법론 중 하나
코틀린은 함수형 프로그래밍과 객체 지향 프로그래밍을 지원
- 추상화(Abstraction): 특정 클래스를 만들 때 기본 형식을 규정하는 방법
- 인스턴스(Instance): 클래스로부터 생성한 객체
- 상속(Inheritance): 부모 클래스의 내용을 자식 클래스가 그대로 물려받음
- 다형성(Polymorphism): 하나의 이름으로 다양한 처리를 제공
- 캡슐화(Encapsulation): 내용을 숨기고 필요한 부분만 사용
- 메시지 전송(Message Sending): 객체 간에 주고받는 메시지
- 연관(Association): 클래스 간의 관계
코틀린에서 사용하는 용어 | 다른 언어에서 사용하는 용어 |
클래스 | - |
프로퍼티 | 속성, 변수, 필드, 데이터 |
메서드 | 함수 |
객체 | 인스턴스 |
자바에서는 클래스에 포함된 기능을 나타내는 함수를 메서드, 변수를 필드라고 한다.
코틀린에서는 필드 대신에 프로퍼티라는 용어를 사용한다.
변수 또는 필드에 내부적으로 접근 메서드가 포함되어 있기 때문.
추상화
목표로 하는 대성에 대해 필요한 만큼 속성과 동적을 정의하는 과정
객체와 인스턴스
클래스로부터 객체를 생성해야만 클래스라는 개념의 실체인 객체가 물리적인 메모리 영역에서 실행됨.
이것을 인스턴스화(Instantiate)되었다고 함.
메모리에 올라간 객체를 인스턴스(Instance)라고 함.
생성자(Constructor)
클래스를 통해 객체가 만들어질 때 기본적으로 호출되는 함수
class 클래스이름 constructor(매개변수) { // 주 생성자의 위치
...
constructor(매개변수) { // 부 생성자의 위치
// 프로퍼티 초기화
}
[constructor(매개변수) { ... }] // 추가 부 생성자
...
}
주 생성자는 다음과 같이 간소화 할 수 있다.
*하지만 가시성 지시자나 어노테이션 표기가 클래스 선언에 있다면 constructor를 생략할 수 없다.
class Bird(val name: String, var color: String){ ... }
클래스 내부에 init{...}를 선언하여 클래스 초기화 블록을 사용할 수 있다.
다음과 같이 디폴트 프로퍼티를 지정할 수 있다.
class Bird(var name: String = "noname", var wing: Int = 2, var color: String) { ... }
fun main(){
val coco = Bird(color = "red")
}
상속
자식 클래스를 만들 때 상위 클래스(부모 클래스)의 속성과 기능을 물려받아 계승하는데 이것을 상속(Inheritance)이라고 한다.
상속을 이용하면 하위 클래스는 일부러 상위 클래스의 모든 내용을 다시 만들지 않아도 된다.
코틀린의 모든 클래스는 Any 클래스의 하위 클래스가 되며, 상위 클래스를 명시하지 않으면 Any 클래스를 상속받게 된다.
open 키워드로 선언하지 않은 클래스는 상속할 수 없는 기본 클래스가 된다.
*코틀린과 달리 자바에서는 기본적으로 선언하는 클래스가 상속 가능한 클래스가 된다.
*자바에서 상속할 수 없는 클래스로 선언하려면 final 키워드를 사용해야 한다.
val someVal: Int // 일반 변수의 선언
open class BaseClass(SomeArgs: Int) // 상속 가능한 클래스
class SomeClass(someArgs: Int) : BaseClass(someArgs) // 클래스 상속의 선언
class someClass : BaseClass { ..constructor()..} // 부 생성자를 사용할 때 클래스 상속 선언
open class Base(val a: String = "Base!", var b: Int) {
fun merge() {
println("$a + $b")
}
}
class Bose(b: Int, val c: String) : Base(c, b) {
fun hihi() {
println("hi C! $c")
}
}
class Bise : Base {
constructor(a:String, b:Int, d: String) : super(d, b)
}
fun main() {
val base = Base("Bose", 5)
base.merge()
val bose = Bose(6, "cccccc")
bose.hihi()
bose.merge()
val bise = Bise("abc", 9, "zzz")
bise.merge()
}
/*
Bose + 5
hi C! cccccc
cccccc + 6
zzz + 9
*/
다형성
메서드가 같은 이름을 사용하지만 구현 내용이 다르거나 매개변수가 달라서 하나의 이름으로 다양한 기능을 수행할 수 있는 개념을 다형성(Polymorphism)이라고 한다.
오버로딩
동작은 일정하지만 인자의 형식만 달라지는 것은 오버로딩(Overloading)이라고 부른다.
fun add(x: Int, y: Int) : Int { return x + y }
fun add(x: Double, y:Double) : Double = x + y
오버라이딩
상위와 하위 클래스에서 메서드나 프로퍼티의 이름은 같지만 기존의 동작을 다른 동작으로 재정의 하는 것을 오버라이딩(Overriding)이라고 한다.
*사전적 의미로 (기존 작업을) 중단하다, 뒤엎다 등으로 해석.
open class Bird{ // open은 상속 가능을 나타냄
fun fly() { ... } // 최종 메서드, 오버라이딩 불가
open fun sing() { ... } // 하위 클래스에서 오버라이딩 가능
}
class Lark() : Bird() { // 하위 클래스
fun fly() { 재정의 } // Error! 상위 메서드에 open 키워드가 없어 오버라이딩 불가
override fun sing() { 재정의 } // 오버라이딩 가능
}
super와 this의 참조
상위 클래스는 super 키워드로, 현재 클래스는 this 키워드로 참조가 가능.
super | this |
super.프로퍼티 // 상위 클래스의 프로퍼티 참조 super.메서드() // 상위 클래스의 메서드 참조 super() // 상위 클래스의 생성자 참조 |
this.프로퍼티 // 현재 클래스의 프로퍼티 참조 this.메서드() // 현재 클래스의 메서드 참조 this() // 현재 클래스의 생성자 참조 |
this로 현재 객체 참조하기
package com.momokddi.demo
open class Base {
constructor(str : String) {
println("base $str")
}
constructor(str : String, num : Int) {
println("base $str $num")
}
}
class Demo : Base {
constructor(str : String) : this(str, 11){
println("demo $str")
}
constructor(str : String, num : Int) : super(str, num) {
println("demo $str $num")
}
}
fun main() {
val demo = Demo("momokddi")
}
//base momokddi 11
//demo momokddi 11
//demo momokddi
인터페이스에서 참조하기
package com.momokddi.demo
open class Base {
open fun f() = println("base f()")
}
interface Case {
fun f() = println("case f()")
fun b() = println("case b()")
}
class Demo : Base(), Case {
// Base를 상속할 때 괄호 없이
// Demo : Base 로 쓴다면
// 아래와 같이 생성자 선언 필요함.
// constructor()
override fun f() = println("demo fun()")
fun test(){
f()
b()
super<Base>.f()
super<Case>.f()
}
}
fun main() {
val d = Demo()
d.test()
}
//demo fun()
//case b()
//base f()
//case f()
정보 은닉 캡슐화
가시성 지시자
- private: 이 요소는 외부에서 접근할 수 없다.
- public: 이 요소는 어디서든 접근이 가능하다.(기본값)
- protected: 외부에서 접근할 수 없으나 하위 상속 요소에서는 가능하다.
- internal: 같은 정의의 모듈 내부에서는 접근이 가능하다.
private
package com.momokddi.demo
private open class Base {
private var num: Int = 0
private fun f() = num + 1
fun call() = f()
}
class Demo {
// val base = Base() // error! private 변수가 돼야함
fun text() {
val base = Base()
}
}
fun main() {
val base = Base()
// base.num = 1 // 접근 불가
// base.f() // 접근 불가
}
protected
최상위에 선언된 요소에는 지정할 수 없고 클래스나 인터페이스와 같은 요소의 멤버에만 지정할수 있다.
멤버가 클래스인 경우에는 protected로 선언할 수 있다.
internal
internal은 프로젝트 단위의 모듈을 가리키기도 한다.
모듈이 달라지면 접근할 수 없다.
기존의 자바에서 package란느 지시자에 의해 패키지 이름이 같은 경우에 접근을 허용했다.
코틀린에서는 패키지에 제한하지 않고 하나의 모듈 단위를 대변하는 internal을 사용한다.
자바에서는 package로 지정된 경우 접근 요소가 패키지 내부에 있다면 접근할 수 있다.
하지만 프로젝트 단위 묶음의 .jar 파일이 달라져도 패키지 이름이 동일하면 다른 .jar에서도 접근할 수 있었기 때문에 보안 문제가 발생할 수 있었다.
따라서 코틀린에서는 internal 지시자를 통하여 프로젝트의 같은 모듈이 아니면 외부에서 접근할 수 없게 했다.
이것은 모듈이 다른 .jar 파일에서는 internal로 선언된 요소에 접근할 수 없다는 뜻이다.
클래스와 클래스의 관계
약하게 참조되고 있는 연관(Association)이나 의존(Dependency) 관계가 있다.
어떤 객체에서 또 다른 객체를 '이용한다.'라고 할 수 있다.
연관 관계
2개의 서로 분리된 클래스가 연결을 가지는 것.
단방향 또는 양방향으로 연결될 수 있다.
두 요소가 서로 다른 생명주기를 가진다.
package com.momokddi.demo
class AAA(val str : String) {
fun f(b : BBB){
println("AAA ${b.str}")
}
}
class BBB(val str : String) {
fun f(a : AAA) {
println("BBB ${a.str}")
}
}
fun main() {
val a = AAA("에이요")
val b = BBB("비요")
a.f(b)
b.f(a)
}
//AAA 비요
//BBB 에이요
클래스의 객체는 따로 생성되며 서로 독립적인 생명주기를 가진다.
양방향 참조를 가진다.
각각의 객체의 생명주기에 영향을 주지 않을 때는 연관 관계라고 한다.
의존 관계
한 클래스가 다른 클래스에 영향을 주는 경우를 의존 관계라고 한다.
A 클래스를 생성하려고 하는데 먼저 B 객체가 필요한 경우 A는 B 객체에 의존하는 관계이다.
package com.momokddi.demo
class A(val b: B) {
fun f() {
println("A ${b.str}")
}
}
class B(val str: String) {
}
fun main() {
val b = B("비입니다.")
val a = A(b)
a.f()
}
//A 비입니다.
집합(Aggregation) 관계
연관 관계와 거의 동일하지만 특정 객체를 소유한다는 개념이 추가.
package com.momokddi.demo
class A(val s : String, val m : MutableList<B>) {
constructor(s : String) : this(s, mutableListOf<B>())
}
class B(val n : String)
fun main() {
val a = A("연못")
a.m.add(B("오리1"))
a.m.add(B("오리2"))
for (duck in a.m) {
println(duck.n)
}
}
//오리1
//오리2
구성(Composition) 관계
구성 관계는 집합 관계와 거의 동일하지만 특정 클래스가 어느 한 클래스의 부분이 되는 것.
구성품으로 지정된 클래스는 생명주기가 소유자 클래스에 의존되어 있다.
소유자 클래스가 삭제되면 구성하고 있던 클래스도 같이 삭제 된다.
package com.momokddi.demo
class Car() {
private val e = Engine()
init{
e.start()
e.stop()
}
}
class Engine() {
fun start(){
println("engine start!")
}
fun stop(){
println("engine stop!")
}
}
fun main() {
val c = Car()
}
//engine start!
//engine stop!
'코틀린' 카테고리의 다른 글
Do it! 코틀린 프로그래밍, 08-2 배열 다루기 (0) | 2021.08.22 |
---|---|
Do it! 코틀린 프로그래밍, 08-1 제네릭 다루기 (0) | 2021.08.02 |
Do it! 코틀린 프로그래밍, 07-2 데이터 클래스와 기타 클래스 (0) | 2021.08.01 |
Do it! 코틀린 프로그래밍, 07-1 추상 클래스와 인터페이스 (0) | 2021.07.21 |
Do it! 코틀린 프로그래밍 - 06 프로퍼티와 초기화 (0) | 2021.07.13 |