출처: https://www.boostcourse.org/mo234/lecture/154220?isDesc=false
OOP (Object-Oriented Programming)
자바와 코틀린에서는 OOP 지원 (C언어는 대표적인 절차적 프로그래밍 언어)
객체지향의 기본 용어
객체지향 개념상의 용어가 언어마다 약간씩 다르다!
자바에서 사용하는 필드는 코틀린에서 프로퍼티라고 부른다.
class Car {
val wheel: Int = 4
fun start() {
println("Engine Start!")
}
}
fun main() {
val sonata = Car()
println(sonata.wheel)
sonata.start()
}
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")
}
fun main() {
val coco = Bird()
coco.color = "yellow"
println("coco.color: ${coco.color}")
coco.fly()
coco.sing(3)
}
Bird 클래스란 일종의 선언일 뿐, 실제 메모리에 존재하여 실행되고 있는 것이 아니다!
반면에, 객체는 물리적인 메모리 영역에서 실행되고 있는 클래스의 실체이다.
따라서 클래스로부터 객체를 생성해내며, 이를 인스턴스화 (instantiation)라고 한다. 즉, 메모리에 올라간 객체를 인스턴스 (instance)라고도 부른다.
package chap01.section2
class Bird {
var name: String
var wing: Int
var beak: String
// 부 생성자
constructor(name: String, wing: Int, beak: String){
this.name = name
this.wing = wing
this.beak = beak
}
fun fly() = println("Fly wing: $wing")
}
fun main() {
val coco = Bird("coco", 2, "long")
coco.fly()
println(coco.name)
}
package chap01.section2
class Bird constructor(_name: String, _wing: Int, _beak: String) {
var name: String = _name
var wing: Int = _wing
var beak: String = _beak
fun fly() = println("Fly wing: $wing")
}
fun main() {
val coco = Bird("coco", 2, "long")
coco.fly()
println(coco.name)
}
주 생성자의 constructor 키워드는 생략할 수 있다.
package chap01.section2
class Bird(var name: String, var wing: Int, var beak: String) {
fun fly() = println("Fly wing: $wing")
}
fun main() {
val coco = Bird("coco", 2, "long")
coco.fly()
println(coco.name)
}
이렇게 프로퍼티 선언을 주 생성자의 선언부에서 해주면, 코드량을 더 줄일 수 있다.
package chap01.section2
class Bird(var name: String, var wing: Int, var beak: String) {
init {
println("------- init start -------")
name = name.capitalize()
println("name: $name, wing: $wing, beak: $beak")
println("------- init end ---------")
}
fun fly() = println("Fly wing: $wing")
}
fun main() {
val coco = Bird("coco", 2, "long")
coco.fly()
println(coco.name)
}
------- init start -------
name: Coco, wing: 2, beak: long
------- init end ---------
Fly wing: 2
Coco
package chap01.section2
class Bird {
var name: String
var wing: Int
var beak: String
constructor(name: String, wing: Int, beak: String){
this.name = name
this.wing = wing
this.beak = beak
}
constructor(name: String, beak: String){
this.name = name
this.wing = 2
this.beak = beak
}
fun fly() = println("Fly wing: $wing")
}
fun main() {
val coco = Bird("coco", 4,"long")
val coco2 = Bird("coco2", "short")
println("coco - name: ${coco.name}, wing: ${coco.wing}, beak: ${coco.beak}")
println("coco2 - name: ${coco2.name}, wing: ${coco2.wing}, beak: ${coco2.beak}")
}
coco - name: coco, wing: 4, beak: long
coco2 - name: coco2, wing: 2, beak: short
package chap01.section3
open class Bird(var name: String, var wing: Int, var beak: String) {
fun fly(){
println("Fly")
}
}
class Lark(name: String, wing: Int, beak: String) : Bird(name, wing, beak){
fun sing(){
println("sing high tone")
}
}
class Parrot : Bird {
var lang: String
// 부 생성자에서는 프로퍼티 선언할 수 없음.
constructor(name: String, wing: Int, beak: String, lang: String)
: super(name, wing, beak) {
this.lang = lang
}
fun speak(){
println("Speak: $lang")
}
}
fun main() {
val lark = Lark("mylark", 2, "short")
val parrot = Parrot("myparrot", 2, "long", "English")
println("lark - name: ${lark.name}")
println("parrot - name: ${parrot.name}")
lark.fly()
lark.sing()
parrot.fly()
parrot.speak()
}
package chap01.section3
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
}
fun main() {
val calc = Calc()
println(calc.add(3, 2))
println(calc.add(3.2, 2.2))
println(calc.add(3, 2, 3))
println(calc.add("Hello", "World"))
}
package chap01.section3
// 상속을 위해 open
open class Bird(var name: String, var wing: Int, var beak: String) {
open fun fly(){ // 오버라이딩을 위해 open
println("Fly")
}
}
class Lark(name: String, wing: Int, beak: String) : Bird(name, wing, beak){
override fun fly(){
println("Fast Fly")
}
}
class Parrot : Bird {
var lang: String
constructor(name: String, wing: Int, beak: String, lang: String)
: super(name, wing, beak) {
this.lang = lang
}
override fun fly(){
println("Slow Fly")
}
}
fun main() {
// 컴파일 타임에 정적으로 Bird 타입이라고 인식됨.
val lark: Bird = Lark("mylark", 2, "short")
val parrot: Bird = Parrot("myparrot", 2, "long", "English")
lark.fly() // 오버라이딩 (동적 바인딩)
//lark.sing() // error
parrot.fly() // 오버라이딩 (동적 바인딩)
//parrot.speak() // error
}
Fast Fly
Slow Fly
동적 바인딩에 의해 lark와 parrot은 런타임에 각각 Lark, Parrot 타입으로 인식되어서 부모 클래스의 fly()가 아니라 자신의 fly() 메서드를 호출한다. 이를 동적 다형성이라 부른다.
open class Lark() : Bird() {
// 하위 클래스에서 재정의 할 수 없음.
final override fun sing() { /* 구현부를 새롭게 정의 */ }
}
하위 클래스에서 오버라이딩을 금지하고 싶을 때는 final 키워드를 붙이면 된다.
상위 클래스는 super, 현재 클래스는 this 키워드로 참조할 수 있다.
open class Person {
constructor(firstName: String){
println("[Person] $firstName")
}
constructor(firstName: String, age: Int){
println("[Person] $firstName, $age") // 1
}
}
class Developer: Person {
constructor(firstName: String) : this(firstName, 10){
println("[Developer] $firstName") // 3
}
constructor(firstName: String, age: Int): super(firstName, age){
println("[Developer] $firstName, $age") // 2
}
}
fun main() {
val sean = Developer("Sean")
}
[Person] Sean, 10
[Developer] Sean, 10
[Developer] Sean
class Person(firstName: String,
out: Unit = println("[Primary] Parameter")) { // 3
val firstName = println("[Property] firstName: $firstName") // 4
init { // 5
println("[init] Person init block")
}
constructor(firstName: String, age: Int,
out: Unit = println("[Secondary] Parameter")): this(firstName){ // 2
println("[Secondary] Body: $firstName, $age") // 6
}
}
fun main() {
val person = Person("Kildong", 30) // 1
}
[Secondary] Parameter
[Primary] Parameter
[Property] firstName: Kildong
[init] Person init block
[Secondary] Body: Kildong, 30
class Person(firstName: String,
out: Unit = println("[Primary] Parameter")) { // 2
val firstName = println("[Property] firstName: $firstName") // 3
init { // 4
println("[init] Person init block")
}
constructor(firstName: String, age: Int,
out: Unit = println("[Secondary] Parameter")): this(firstName){
println("[Secondary] Body: $firstName, $age")
}
}
fun main() {
val person = Person("Dooly") // 1
}
[Primary] Parameter
[Property] firstName: Dooly
[init] Person init block
inner 클래스에서 outer 클래스의 상위 클래스를 호출하려면, super 키워드와 함께 @ 기호 옆에 outer 클래스의 이름을 작성하여 호출하면 된다.
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() // Child 클래스의 메서드
super@Child.f() // Base 클래스의 메서드
println("[Inside] super@Child.x: ${super@Child.x}") // 1
}
}
}
fun main() {
val c1 = Child()
c1.Inside().test()
}
Inside Class f()
Child Class f()
Base Class f()
[Inside] super@Child.x: 1
open class A {
open fun f() = println("A Class f()")
fun a() = println("A Class a()")
}
interface B { // 인터페이스는 기본적으로 open 되어 있음.
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() // 인터페이스 B
super<A>.f() // 클래스 A
super<B>.f() // 인터페이스 B
}
}
fun main() {
val c = C()
c.test()
}
C Class f()
B interface b()
A Class f()
B interface f()
클래스를 작성할 때 외부에 속성이나 기능을 숨기는 것을 캡슐화 라고 한다.
가시성 지시자 (visibility modifiers)를 통해 외부 접근 범위를 결정할 수 있다.
- private: 외부에서 접근 불가
- public: 어디서든 접근 가능 (디폴트)
- protected: 외부에서 접근할 수 없으나, 하위 상속 요소에서는 접근 가능
- internal: 같은 정의의 모듈 (프로젝트) 내부에서는 접근 가능 (자바에서 사용되던 package 키워드를 대체함.)
cf) 가시성 지시자가 붙은 생성자는 constructor 키워드를 생략할 수 없다.
package chap01.section5
private class PrivateClass {
private var i = 1
private fun privateFunc(){
i += 1
println(i)
}
fun access(){ // 기본은 public
privateFunc()
}
}
class OtherClass { // 기본은 public
//val opc = PrivateClass() // public으로 생성 불가
// public 함수이긴 하지만, OtherClass 내부에서 한번 더 가려지므로
// private 클래스 생성 가능
fun test(){
val pc = PrivateClass() // 생성 가능
pc.access()
}
}
fun main() { // 최상위 함수
val pc = PrivateClass() // 생성 가능
//pc.i = 3 // 접근 불가
//pc.privateFunc() // 접근 불가
pc.access() // ok
}
// 최상위 함수에서 private 클래스 생성 가능
fun TopLevelFunc(){
val tpc = PrivateClass() // 생성 가능
tpc.access()
}
package chap01.section5
open class Base {
protected var i = 1
protected fun protectedFunc(){
i += 1
println("Base: $i")
}
fun access(){
protectedFunc()
}
protected class Nested
}
class Derived: Base(){
fun test() {
protectedFunc() // 접근 가능
i += 1 // 프로퍼티 값 변경 가능
println("Derived: $i")
}
}
class Other {
fun other(){
val base = Base()
//base.i = 3 // 프로퍼티 값 변경 불가
}
}
fun main() {
val base = Base() // 생성 가능
// 외부에서는 접근 불가
//base.i
//base.protectedFunc()
base.access() // 접근 가능
val derived = Derived()
derived.test()
}
Base: 2
Base: 2
Derived: 3
internal class InternalClass { // 같은 모듈 내에서 접근 가능
internal var i = 1
internal fun icFunc(){
i += 1
println(i)
}
fun access(){
icFunc()
}
}
class Other {
internal val ic = InternalClass()
fun test(){
ic.i
ic.icFunc()
}
}
fun main() {
val mic = InternalClass()
println(mic.i) // 1
mic.icFunc() // 2
mic.access() // 3
}
cf) UML 다이어그램에서의 표기법
- private
# protected
~ package, internal
+ public
package chap01.section6
open class Base {
// a, b, c, d, e 접근 가능
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4
protected class Nested {
// a, b, c, d, e, f 접근 가능
val e: Int = 5
private val f: Int = 6
}
}
class Derived: Base() {
// b, c, d, e 접근 가능
override val b = 5
}
class Other(val base: Base) {
fun test(){
//base.a
//base.b
base.c
base.d
//Base.Nested
//Nested::e
}
}