객체지향 프로그래밍에 한번 짚고 넘어가 볼까요?
객체지향 프로그래밍 (OOP)은 프로그래밍 패러다임 중 하나로, 프로그램을 단순히 함수나 명령어의 집합으로 보는 것이 아니라, 데이터를 객체라는 단위로 묶어 처리하는 방법입니다. 객체는 상태와 행동을 갖는 독립적인 단위로, 현실 세계의 사물이나 개념을 프로그래밍으로 표현할 때 유용합니다.
객체지향 프로그래밍은 코드의 재사용성과 유지보수성을 높이는 데 매우 유용한 패러다임입니다. 코틀린은 이러한 객체지향 프로그래밍을 쉽게 구현할 수 있는 언어로, 클래스, 상속, 캡슐화, 다형성, 추상화, 인터페이스 등의 개념을 통해 효율적인 프로그램을 작성할 수 있습니다.
객체는 상태와 행동을 가진 독립적인 단위입니다. 예를 들어, 고양이라는 객체는 "이름", "나이", "색깔" 등의 상태(속성)를 가지며, "울기", "걷기" 등의 행동(메서드)을 가질 수 있습니다. 객체는 특정 클래스의 인스턴스입니다.
클래스는 객체를 만들기 위한 청사진입니다. 클래스는 객체의 속성과 행동을 정의합니다. 클래스는 속성(멤버 변수)과 메서드(멤버 함수)로 구성됩니다.
// 고양이 클래스를 정의합니다.
class Cat(val name: String, var age: Int, val color: String) {
// 고양이의 행동을 정의합니다.
fun meow() {
println("$name: Meow!")
}
fun getOlder() {
age++
}
}
// 고양이 객체를 생성합니다.
val myCat = Cat("Whiskers", 2, "Gray")
// 고양이 객체의 메서드를 호출합니다.
myCat.meow() // 출력: Whiskers: Meow!
myCat.getOlder()
println(myCat.age) // 출력: 3
위의 코드에서 Cat 클래스는 고양이의 이름, 나이, 색깔을 속성으로 가지고, meow와 getOlder라는 두 개의 메서드를 가집니다. myCat 객체는 Cat 클래스의 인스턴스로 생성되어, 해당 객체의 속성과 메서드를 사용할 수 있습니다.
상속은 기존 클래스의 속성과 메서드를 다른 클래스가 물려받아 사용하는 기능입니다. 상속을 통해 코드 재사용성을 높일 수 있습니다. 상속을 사용하면, 새로운 클래스는 기존 클래스의 기능을 확장하거나 변경할 수 있습니다.
// 동물 클래스를 정의합니다.
open class Animal(val name: String, var age: Int) {
fun eat() {
println("$name is eating.")
}
}
// 동물 클래스를 상속받아 고양이 클래스를 정의합니다.
class Cat(name: String, age: Int, val color: String) : Animal(name, age) {
fun meow() {
println("$name: Meow!")
}
}
val myCat = Cat("Whiskers", 2, "Gray")
myCat.eat() // 출력: Whiskers is eating.
myCat.meow() // 출력: Whiskers: Meow!
위의 예제에서 Animal 클래스는 name과 age 속성을 가지고, eat 메서드를 정의합니다. Cat 클래스는 Animal 클래스를 상속받아 name과 age 속성을 물려받고, meow라는 추가적인 메서드를 정의합니다. Cat 클래스는 Animal 클래스의 모든 속성과 메서드를 사용할 수 있습니다.
캡슐화는 객체의 속성을 외부에서 직접 접근하지 못하도록 하고, 메서드를 통해서만 접근할 수 있도록 하는 것입니다. 이를 통해 객체의 상태를 보호하고, 잘못된 사용으로부터 객체를 안전하게 지킬 수 있습니다. 코틀린에서는 접근 제한자를 사용하여 캡슐화를 구현합니다.
class Person(private var name: String, private var age: Int) {
// Getter 메서드
fun getName() = name
fun getAge() = age
// Setter 메서드
fun setName(newName: String) {
if (newName.isNotBlank()) {
name = newName
}
}
fun setAge(newAge: Int) {
if (newAge > 0) {
age = newAge
}
}
}
val person = Person("John", 30)
println(person.getName()) // 출력: John
person.setAge(31)
println(person.getAge()) // 출력: 31
위의 예제에서 Person 클래스는 name과 age 속성을 private로 정의하여 외부에서 직접 접근할 수 없도록 했습니다. 대신, getName, getAge, setName, setAge 메서드를 통해서만 name과 age 속성에 접근하고 변경할 수 있습니다. 이렇게 하면 속성에 대한 잘못된 접근을 방지할 수 있습니다.
다형성은 동일한 인터페이스나 상위 클래스를 통해 서로 다른 형태의 객체를 다룰 수 있게 하는 기능입니다. 이를 통해 유연하고 확장성 있는 코드를 작성할 수 있습니다. 다형성은 주로 메서드 오버라이딩과 인터페이스를 통해 구현됩니다.
open class Animal {
open fun sound() {
println("Some sound")
}
}
class Dog : Animal() {
override fun sound() {
println("Bark")
}
}
class Cat : Animal() {
override fun sound() {
println("Meow")
}
}
fun makeSound(animal: Animal) {
animal.sound()
}
val dog = Dog()
val cat = Cat()
makeSound(dog) // 출력: Bark
makeSound(cat) // 출력: Meow
위의 예제에서 Animal 클래스는 sound 메서드를 정의하고, Dog와 Cat 클래스는 이를 오버라이딩하여 각각의 소리를 정의합니다. makeSound 함수는 Animal 타입의 객체를 받아서 sound 메서드를 호출합니다. 이 함수는 전달받은 객체의 실제 타입에 따라 적절한 sound 메서드를 호출합니다. 이렇게 하면 코드의 유연성이 증가합니다.