코드의 세계를 지배하는 4가지 기둥, 객체지향 프로그래밍의 모든 것
개발의 길 2025. 4. 5. 22:41 |코드의 세계를 지배하는 4가지 기둥, 객체지향 프로그래밍의 모든 것
객체지향 프로그래밍이란?
객체지향 프로그래밍(Object-Oriented Programming, OOP)은 데이터와 이를 조작하는 메소드를 하나의 '객체(Object)'라는 단위로 묶어 프로그램을 구성하는 패러다임입니다. 현실 세계의 개념을 코드로 표현하기 쉽게 해주며, 재사용성과 확장성이 뛰어납니다.
대표적인 객체지향 언어로는 Java Python C++ JavaScript 등이 있습니다.
객체지향 프로그래밍의 4대 특징
캡슐화는 관련된 데이터와 함수를 하나의 단위로 묶고, 외부에서의 접근을 제한하는 것을 의미합니다. 이는 데이터 은닉(Data Hiding)을 통해 객체 내부 구현을 보호합니다.
public class BankAccount {
private double balance; // 외부에서 직접 접근 불가
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("입금 완료: " + amount);
}
}
public double getBalance() {
return balance;
}
}
위 예시에서 balance
변수는 private
으로 선언되어 외부에서 직접 접근할 수 없습니다. 대신 deposit()
과 getBalance()
메소드를 통해서만 데이터를 조작하거나 조회할 수 있습니다.
상속은 기존 클래스의 속성과 메소드를 새로운 클래스가 재사용할 수 있게 해주는 메커니즘입니다. 코드 재사용성을 높이고 계층 구조를 형성합니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal): # Animal 클래스 상속
def speak(self):
return f"{self.name}가 멍멍 짖습니다."
class Cat(Animal): # Animal 클래스 상속
def speak(self):
return f"{self.name}가 야옹 웁니다."
dog = Dog("바둑이")
cat = Cat("나비")
print(dog.speak()) # 출력: 바둑이가 멍멍 짖습니다.
print(cat.speak()) # 출력: 나비가 야옹 웁니다.
위 예시에서 Dog
와 Cat
클래스는 Animal
클래스를 상속받아 name
속성과 speak()
메소드를 재사용합니다. 각 자식 클래스는 speak()
메소드를 자신의 특성에 맞게 재정의합니다.
다형성은 같은 인터페이스나 메소드가 객체의 타입에 따라 다른 동작을 할 수 있게 해주는 특성입니다. 유연하고 확장 가능한 코드를 작성할 수 있게 해줍니다.
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() {
return 0;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14 * radius * radius;
}
};
int main() {
Shape* shapes[2];
shapes[0] = new Rectangle(5, 4);
shapes[1] = new Circle(3);
for (int i = 0; i < 2; i++) {
cout << "도형의 면적: " << shapes[i]->area() << endl;
}
return 0;
}
위 예시에서 Rectangle
과 Circle
클래스는 모두 Shape
클래스를 상속받고 area()
메소드를 재정의합니다. 실행 시점에 객체의 실제 타입에 따라 적절한 area()
메소드가 호출됩니다.
추상화는 복잡한 시스템에서 핵심적인 개념이나 기능만을 간추려 표현하는 것을 의미합니다. 불필요한 세부 사항을 숨기고 중요한 부분만 드러냅니다.
// 스마트폰의 추상화
class Smartphone {
constructor(brand, model) {
this.brand = brand;
this.model = model;
this.isOn = false;
}
turnOn() {
this.isOn = true;
console.log(`${this.brand} ${this.model} 전원이 켜졌습니다.`);
}
turnOff() {
this.isOn = false;
console.log(`${this.brand} ${this.model} 전원이 꺼졌습니다.`);
}
makeCall(number) {
if (this.isOn) {
console.log(`${number}로 전화 연결 중...`);
} else {
console.log(`전원이 꺼져 있습니다.`);
}
}
}
const myPhone = new Smartphone('iPhone', '13');
myPhone.turnOn();
myPhone.makeCall('010-1234-5678');
위 예시에서 Smartphone
클래스는 스마트폰의 복잡한 내부 동작(하드웨어 작동 방식, 전화 연결 프로토콜 등)을 숨기고, 사용자에게 필요한 기능(전원 켜기/끄기, 전화 걸기)만을 제공합니다.
실전에서의 객체지향 프로그래밍 적용 사례
객체지향 프로그래밍은 다양한 실제 애플리케이션 개발에 광범위하게 사용됩니다:
- GUI 애플리케이션: 버튼, 텍스트 필드, 창과 같은 UI 요소들이 객체로 표현됩니다.
- 게임 개발: 캐릭터, 아이템, 환경 요소 등을 객체로 모델링합니다.
- 웹 개발: MVC(Model-View-Controller) 패턴을 활용한 웹 프레임워크들이 객체지향 원칙을 따릅니다.
- 기업 소프트웨어: 복잡한 비즈니스 로직을 객체와 클래스로 구조화하여 관리합니다.
객체지향 프로그래밍의 장단점
장점 | 단점 |
---|---|
코드 재사용성 향상 | 초기 설계 시간이 많이 소요됨 |
유지보수 용이성 | 작은 프로그램에서는 오버헤드가 발생할 수 있음 |
대규모 프로젝트에 적합 | 학습 곡선이 가파름 |
현실 세계 모델링이 직관적 | 객체 간 상호작용이 복잡해질 수 있음 |
확장성이 좋음 | 일부 문제 도메인에는 적합하지 않을 수 있음 |
다양한 프로그래밍 패러다임
객체지향 프로그래밍 외에도 다양한 프로그래밍 패러다임이 존재합니다. 각각의 패러다임은 특정 문제 해결에 더 적합할 수 있습니다.
절차적 프로그래밍은 프로그램을 순차적인 함수 호출로 구성하는 방식입니다. 데이터와 함수가 분리되어 있으며, 주로 작은 문제를 해결하는 데 효과적입니다.
대표 언어: C Pascal Fortran
#include <stdio.h>
void printGreeting(char* name) {
printf("안녕하세요, %s님!\n", name);
}
int add(int a, int b) {
return a + b;
}
int main() {
printGreeting("홍길동");
int result = add(5, 3);
printf("5 + 3 = %d\n", result);
return 0;
}
함수형 프로그래밍은 수학적 함수 개념을 기반으로 하며, 상태 변경과 가변 데이터를 피하고 순수 함수를 사용합니다. 병렬 처리와 테스트가 용이하고 버그가 적은 코드를 작성할 수 있습니다.
대표 언어: Haskell Clojure Erlang F#
-- 숫자 리스트의 총합 계산
sum' :: [Int] -> Int
sum' [] = 0
sum' (x:xs) = x + sum' xs
-- 리스트의 모든 원소에 2를 곱하기
doubleList :: [Int] -> [Int]
doubleList = map (*2)
-- 리스트에서 짝수만 필터링
evenOnly :: [Int] -> [Int]
evenOnly = filter even
main = do
print $ sum' [1, 2, 3, 4, 5] -- 출력: 15
print $ doubleList [1, 2, 3] -- 출력: [2, 4, 6]
print $ evenOnly [1..10] -- 출력: [2, 4, 6, 8, 10]
논리형 프로그래밍은 형식 논리에 기반하여 프로그램을 작성하는 패러다임입니다. 사실(facts)과 규칙(rules)을 정의하고, 시스템이 자동으로 답을 추론하도록 합니다. 인공지능, 자연어 처리, 데이터베이스 쿼리 등에 활용됩니다.
대표 언어: Prolog Mercury
% 가족 관계 데이터베이스
parent(john, bob).
parent(john, lisa).
parent(bob, ann).
parent(bob, mike).
% X가 Y의 조부모인지 확인하는 규칙
grandparent(X, Y) :- parent(X, Z), parent(Z, Y).
% 쿼리: john이 조부모인 모든 사람 찾기
% ?- grandparent(john, X).
% X = ann ;
% X = mike ;
데이터 지향 프로그래밍은, 데이터와 동작의 분리를 강조합니다. 데이터는 불변하는 것으로 취급하며, 일반적인 데이터 구조와 독립적인 함수들로 프로그램을 구성합니다.
대표 언어: Go Clojure
package main
import (
"fmt"
)
// 사용자 데이터 구조
type User struct {
ID int
Name string
Email string
}
// 사용자 정보 출력 함수
func printUserInfo(user User) {
fmt.Printf("ID: %d, 이름: %s, 이메일: %s\n", user.ID, user.Name, user.Email)
}
// 이메일 유효성 검사 함수
func isValidEmail(email string) bool {
// 간단한 검증 로직
return len(email) > 0 && len(email) < 255
}
func main() {
user := User{
ID: 1,
Name: "홍길동",
Email: "hong@example.com",
}
printUserInfo(user)
fmt.Println("유효한 이메일:", isValidEmail(user.Email))
}
'개발의 길' 카테고리의 다른 글
눈에 보이지 않는 소프트웨어의 일꾼들: 멀티스레드의 모든 것 (1) | 2025.04.24 |
---|---|
코드의 마법사 되기: Node.js와 npm으로 시작하는 현대적 웹 개발 여정 (0) | 2025.04.24 |
에러 해결사의 비밀노트: HTTP 상태 코드 완전정복 (0) | 2025.04.01 |
REST API vs SOAP API 완벽 비교: 현대 웹 서비스 아키텍처의 두 기둥 (1) | 2025.03.15 |
MIME와 MHTML 완벽 가이드: 웹페이지를 그대로 저장하기 (1) | 2025.02.20 |