티스토리 뷰
객체 지향 프로그래밍에 대해 배워보자.
1. 객체 지향 프로그래밍
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그램을 명령어의 집합을 보는 것을 넘어, 여러 "객체"들의 모임으로 보는 프로그래밍 패러다임이다.
개념적으로 "객체"를 사용하며, 이러한 객체는 데이터와 그 데이터를 처리하는 메서드(함수)로 구성된다.
설계에 많은 시간이 소요되며 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느리다.
클래스와 객체
- 클래스(Class): 객체를 만들기 위한 일종의 틀 or 설계도
- 객체(Object): 클래스의 인스턴스로, 클래스에서 정의한 속성(멤버 변수)과 메서드(멤버 함수)를가진다.
class Car {
var brand: String
var model: String
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
func startEngine() {
print("Engine started.")
}
}
let myCar = Car(brand: "Toyota", model: "Camry")
myCar.startEngine()
- 클래스: Car
- 객체: myCar
myCar는 brand와 model이라는 속성을 가지며, startEngine()이라는 메서드를 수행할 수 있다.
2. 객체 지향 프로그래밍 특징
추상화
: 복잡한 세부사항을 감추고, 중요한 부분만을 강조한다.
자동차를 예를 들어보자.
자동차는 브랜드, 모델, 엔진등의 속성을 가지며, 시동을 켜고 끄는 기능을 수행한다.
이러한 특징 중에서 일부분인 브랜드와 모델, 엔진을 시작하는 동작을 가진 객체를 추상화할 수 있다.
=> 추상화를 통해 복잡성을 단순화하고, 객체의 핵심 특성에 집중할 수 있다.
캡슐화
: 객체의 속성과 메서드를 외부에서 접근하지 못하도록 은닉하는 것을 말한다.
은행 계좌를 예로 들어보자.
class BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
// 예금 메서드
func deposit(amount: Double) {
if amount > 0 {
balance += amount
print("\(amount)원 입금되었습니다. 현재 잔액: \(balance)원")
} else {
print("유효하지 않은 입금 금액입니다.")
}
}
// 출금 메서드
func withdraw(amount: Double) {
if amount > 0 && amount <= balance {
balance -= amount
print("\(amount)원 출금되었습니다. 현재 잔액: \(balance)원")
} else {
print("유효하지 않은 출금 금액이거나 잔액이 부족합니다.")
}
}
// 잔액 조회 메서드
func getBalance() -> Double {
return balance
}
}
// BankAccount 객체 생성
let account = BankAccount(initialBalance: 1000.0)
// 예금과 출금
account.deposit(amount: 500.0)
account.withdraw(amount: 200.0)
// 잔액 조회
let currentBalance = account.getBalance()
print("현재 잔액: \(currentBalance)원")
BankAccount 클래스는 은행 계좌를 나타내며, 'balance' 프로퍼티는 private로 선언되어 외부에서 직접 접근할 수 없다.
대신 deposit(), withdraw(), getBalance()와 같은 메서드를 통해 간접적으로 계좌의 상태에 접근할 수 있다.
=> 'balance'의 상태를 외부에서 직접 조작하지 않고, 정의된 메서드를 통해 안전하게 상호작용한다.
객체의 내부 구현을 감추고, 외부에서 객체의 인터페이스만을 보게 함으로써 외부에서 부적절한 접근을 방지한다.
상속성
: 상위 클래스의 특성을 하위 클래스가 이어받아 재사용하거나, 확장하는 것을 의미한다.
코드의 재사용성을 높이고, 계층 구조를 통해 코드를 조직화하며 유지 보수에 도움이 된다.
다형성
: 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것을 말한다.
대표적으로 오버로딩, 오버라이딩이 있다.
1️⃣ 오버로딩
: 같은 이름을 가진 메서드를 여러 개 정의하되, 매개변수의 타입이나 개수, 반환 타입 등을 다르게 정의하는 것을 의미한다.
=> 정적 다형성
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func add(_ a: Double, _ b: Double) -> Double {
return a + b
}
// 사용 예시
let sum1 = add(3, 5) // Int 타입의 함수 호출
let sum2 = add(3.5, 2.7) // Double 타입의 함수 호출
add 함수는 이름은 같지만 매개변수의 타입이 다르기 때문에 오버로딩이 가능하다.
2️⃣ 오버라이딩
: 상위 클래스에서 정의된 메서드를 하위 클래스에서 동일한 이름으로 다시 정의하는 것을 의미한다.
=> 동적 다형성
class Animal {
func makeSound() {
print("make sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Woof!")
}
}
// 사용 예시
let myDog = Dog()
myDog.makeSound() // "Woof!" 출력
Dog 클래스는 Animal 클래스를 상속받고, makeSound()메서드를 오버라이딩 하여 자체적인 동작을 구현했다.
myDog 객체에서 makeSound()를 호출하면 하위 클래스의 구현이 실행되어 "Woof"가 출력된다.
=> 상속 계층에서 메서드의 동작을 변경하거나 확장할 때 사용된다.
다형성은 코드의 유연성과 확장성을 증가시키고 재사용성을 높인다.
🟢 장점
- 유지보수 용이: 코드가 객체 단위로 분할되어 있어 해당 객체만 수정하면 되므로 유지보수 용이
- 객체지향적이기 때문에 현실 세계의 개념과 유사하여 가독성이 높고 이해하기 쉽다.
- 재사용 용이: 상속을 통해 코드를 재사용할 수 있다.
🔴 단점
- 메모리 사용량: 상대적으로 높은 메모리 사용량을 필요로 하여, 오버헤드로 이어질 수 있다.
- 코드 실행 속도: 일반적으로 절차 지향 프로그래밍이나 함수형 프로그래밍과 비교했을 때 실행 속도가 느릴 수 있다.