안녕하세요, '생각하는 개발자'입니다.
지난 시간에는 변수, 함수 등 코드를 구성하는 가장 작은 단위들을 배웠습니다. 하지만 앱이 복잡해지면 수많은 변수와 함수들이 뒤죽박죽 섞여 관리하기 어려워지겠죠.
그래서 우리는 **객체지향 프로그래밍(Object-Oriented Programming, OOP)**이라는 강력한 설계 방법을 사용합니다. 말이 조금 어렵게 들리지만, 사실은 **'세상의 사물을 흉내 내어 코드를 정리하는 방법'**이라고 생각하면 쉽습니다. 이 개념을 이해하면 훨씬 더 체계적이고 재사용하기 좋은 코드를 작성할 수 있습니다.
3.1. 클래스와 객체: 붕어빵 틀과 붕어빵

OOP의 가장 핵심적인 두 가지 개념은 바로 **클래스(Class)**와 **객체(Object)**입니다.
- 클래스(Class): 객체를 만들기 위한 설계도 또는 틀입니다. (예: 붕어빵 틀)
- 객체(Object): 그 설계도를 바탕으로 실제로 만들어진 실체입니다. (예: 붕어빵)
붕어빵 틀(클래스) 하나만 있으면, 수많은 붕어빵(객체)을 똑같은 모양으로 찍어낼 수 있죠. 코틀린 코드로 직접 확인해 봅시다.
// '몬스터'라는 설계도를 만든다.
class Monster {
var name: String = "슬라임"
var level: Int = 1
fun attack() {
println("${name}(Lv.${level})이(가) 공격했다!")
}
}
fun main() {
// 'Monster' 설계도로 실제 몬스터 2마리를 만든다.
val monster1 = Monster() // 첫 번째 몬스터 객체 생성
val monster2 = Monster() // 두 번째 몬스터 객체 생성
monster1.attack() // 출력: 슬라임(Lv.1)이(가) 공격했다!
// 두 번째 몬스터의 능력치를 바꿔보자.
monster2.name = "고블린"
monster2.level = 3
monster2.attack() // 출력: 고블린(Lv.3)이(가) 공격했다!
}
Monster라는 클래스(설계도)를 한 번 정의해두니, monster1과 monster2라는 속성(이름, 레벨)과 행동(공격)을 가진 실제 객체들을 쉽게 만들어낼 수 있습니다.
3.2. 생성자: 객체가 태어날 때 정해지는 것
위 예제에서는 모든 몬스터가 일단 '슬라임'으로 태어난 뒤에 이름을 바꿔줘야 했습니다. 불편하죠? **생성자(Constructor)**를 사용하면 객체가 만들어지는 그 순간에 속성 값을 지정해 줄 수 있습니다.
// 클래스 이름 옆에 ( )를 열고 생성자를 정의한다.
class Monster(val name: String, var level: Int) {
fun attack() {
println("${name}(Lv.${level})이(가) 공격했다!")
}
}
fun main() {
// 객체를 만들 때 바로 이름과 레벨을 지정해준다.
val monster1 = Monster("오크", 5)
val monster2 = Monster("드래곤", 99)
monster1.attack() // 출력: 오크(Lv.5)이(가) 공격했다!
monster2.attack() // 출력: 드래곤(Lv.99)이(가) 공격했다!
}
이제 몬스터를 만들 때마다 각기 다른 초기 능력치를 부여할 수 있게 되어 훨씬 더 유용해졌습니다.

3.3. 상속: 설계도를 물려받아 확장하기
만약 '보스 몬스터'처럼 일반 몬스터의 특징을 모두 가지면서, 추가로 특별한 스킬까지 가진 존재를 만들고 싶다면 어떡할까요? Monster 클래스의 코드를 전부 복사해서 붙여넣어야 할까요?
이럴 때 사용하는 것이 바로 **상속(Inheritance)**입니다. 부모 클래스(설계도)의 모든 속성과 행동을 물려받아, 자식 클래스(설계도)에서 기능을 추가하거나 변경할 수 있습니다.
// 'open' 키워드를 붙여 다른 클래스가 상속할 수 있도록 허용한다.
open class Monster(val name: String, var level: Int) {
fun attack() {
println("${name}(Lv.${level})이(가) 공격했다!")
}
}
// Monster 클래스를 상속받는 BossMonster 클래스를 만든다.
class BossMonster(name: String, level: Int, val specialSkill: String) : Monster(name, level) {
fun useSpecialSkill() {
println("${name}이(가) 필살기 '${specialSkill}'을(를) 사용했다!")
}
}
fun main() {
val boss = BossMonster("발록", 150, "파이어 브레스")
boss.attack() // 부모(Monster)로부터 물려받은 기능
boss.useSpecialSkill() // 자식(BossMonster)만의 새로운 기능
// 출력:
// 발록(Lv.150)이(가) 공격했다!
// 발록이(가) 필살기 '파이어 브레스'을(를) 사용했다!
}
BossMonster는 Monster의 attack() 기능을 그대로 물려받았기 때문에 코드를 중복해서 작성할 필요가 없었습니다. 이것이 바로 상속의 강력함입니다.
지금까지 객체지향 프로그래밍의 가장 기본적인 세 가지 기둥인 클래스/객체, 생성자, 상속에 대해 알아보았습니다. 이 개념들은 앞으로 우리가 만들 모든 안드로이드 앱의 뼈대를 이루게 될 것입니다.
'Android (Kotlin & Compose) > Part 1. 개발 시작과 코틀린' 카테고리의 다른 글
| [오늘의 코드 조각] [1-2] Jetpack Compose를 위한 코틀린 핵심 문법 (1) | 2025.08.25 |
|---|---|
| [오늘의 코드 조각] [1-1] Modern Android 개발 첫걸음 (0) | 2025.08.24 |