객체지향 프로그래밍에 한번 짚고 넘어가 볼까요?
객체지향 프로그래밍 (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
메서드를 호출합니다. 이렇게 하면 코드의 유연성이 증가합니다.