개발 공부를 하다 보니 '객체 지향적으로 설계하라'는 말을 자주 듣게 되는데, 이게 뭔지 확실히 정리해보려고 한다. 객체 지향 프로그래밍(OOP)의 기본 개념이랑 특징에 대해 알아보자.
객체 지향 프로그래밍이란?
객체 지향 프로그래밍은 현실 세계의 사물이나 개념을 '객체(Object)'라는 단위로 모델링해서 프로그램을 구성하는 방식이다. 각 객체는 자신만의 상태(데이터)와 행동(메서드)을 갖고, 이 객체들이 서로 메시지를 주고받으며 협력하는 방식으로 프로그램이 돌아간다.
예를 들어, 커피숍 주문 시스템을 만든다고 생각해보자
- '고객' 객체: 이름, 포인트 같은 정보와 '주문하기' 같은 행동을 가짐
- '메뉴' 객체: 음료 종류, 가격 정보와 '항목 추가/삭제' 같은 행동을 가짐
- '주문' 객체: 주문 항목, 총액 정보와 '결제하기' 같은 행동을 가짐
이전의 절차적 프로그래밍은 "A 단계, B 단계, C 단계를 순서대로 해라"처럼 진행 방법에 집중했다면, 객체 지향은 "누가 무엇을 할지"에 집중한다.
객체 지향의 4가지 핵심 특징
1. 캡슐화(Encapsulation)
캡슐화는 관련된 데이터와 메서드를 하나의 단위로 묶고, 외부에서의 접근을 제한하는 것이다. 객체 내부의 구현을 숨기고 필요한 부분만 외부에 노출함으로써 객체의 응집도를 높이고 결합도를 낮출 수 있다.
2. 상속(Inheritance)
상속은 기존 클래스의 특성을 새로운 클래스가 물려받는 것이다. 상속을 통해 코드 재사용성을 높이고 계층 구조를 표현할 수 있다.
// 부모 클래스
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + "가 먹이를 먹는다.");
}
public void sleep() {
System.out.println(name + "가 잠을 잔다.");
}
}
// 자식 클래스 - 강아지
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age); // 부모 클래스의 생성자 호출
this.breed = breed;
}
public void bark() {
System.out.println(name + "멍멍");
}
// 부모 메서드를 오버라이드
@Override
public void eat() {
System.out.println(breed + " 품종의 " + name + "가 사료를 먹는다.");
}
}
Dog 클래스는 Animal 클래스를 상속받아 name, age 속성과 eat(), sleep() 메서드를 물려받았고, 자신만의 특수한 기능과 특성을 추가로 구현했다.
3. 다형성(Polymorphism)
다형성은 객체 지향의 가장 강력한 특징으로, 동일한 메서드 호출이 객체의 타입에 따라 다른 동작을 할 수 있게 한다. 인터페이스를 통해 여러 구현체를 유연하게 사용할 수 있는 것이 다형성의 핵심이다.
// 동물 인터페이스
public interface Animal {
void makeSound();
}
// 고양이 구현
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("야옹");
}
}
// 강아지 구현
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍");
}
}
// 다형성 활용
public class Farm {
public static void main(String[] args) {
// 동물 목록 만들기
List<Animal> animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
// 모든 동물에게 같은 메시지 전달
for (Animal animal : animals) {
animal.makeSound(); // 각 동물 타입에 맞게 다른 소리가 출력됨
}
}
}
위 코드에서 makeSound()를 호출할 때 실제 객체 타입에 따라 "야옹"이나 "멍멍"이 출력된다.
4. 추상화(Abstraction)
추상화는 복잡한 것을 단순하게 만드는 과정이다.
TV 리모컨에는 전원, 볼륨, 채널 버튼만 있지만, 실제로 TV 안에는 엄청 복잡한 전자회로와 부품들이 있다. 리모컨은 그 모든 복잡한 기술을 '전원 켜기', '볼륨 올리기' 같은 간단한 기능으로 추상화한 것.
즉, 불필요한 세부 사항은 숨기고 중요한 부분만 노출해서 복잡성을 관리하는 것이다.
// 사용자에게 보이는 간단한 인터페이스
public interface TvRemote {
void powerOn();
void powerOff();
void volumeUp();
void volumeDown();
}
// 실제 내부는 훨씬 복잡함
public class SamsungTV implements TvRemote {
@Override
public void powerOn() {
// 실제론 엄청 복잡한 전자회로 작동
System.out.println("TV 켜짐");
}
@Override
public void powerOff() {
System.out.println("TV 꺼짐");
}
@Override
public void volumeUp() {
System.out.println("볼륨 올림");
}
@Override
public void volumeDown() {
System.out.println("볼륨 내림");
}
}
// 사용하는 측에서는 복잡한 내부를 몰라도 됨
public class Person {
public static void main(String[] args) {
TvRemote remote = new SamsungTV();
// TV가 어떻게 작동하는지 몰라도 사용 가능
remote.powerOn();
remote.volumeUp();
}
}
다른 예시
- 자동차 운전할 때: 엔진 작동 원리는 몰라도 핸들/브레이크/엑셀만 알면 됨
- 커피머신 사용할 때: 내부 부품 구조 몰라도 버튼만 누르면 됨
- 스마트폰 쓸 때: 반도체가 어떻게 작동하는지 몰라도 앱 아이콘만 터치하면 됨
객체 지향 프로그래밍의 장점
- 모듈화: 프로그램을 독립적인 객체 단위로 나눌 수 있어서 개발, 테스트, 유지보수가 쉬움
- 재사용성: 이미 만들어진 클래스를 상속이나 조합을 통해 재사용 가능
- 유지보수성: 캡슐화를 통해 내부 구현을 숨기고 인터페이스를 통해 통신하니까, 한 부분의 변경이 다른 부분에 미치는 영향을 최소화
- 확장성: 다형성을 통해 새로운 클래스를 추가하는 방식으로 시스템을 확장 가능
'프로그래밍 기초' 카테고리의 다른 글
객체 지향 프로그래밍의 5가지 설계 원칙 - SOLID (0) | 2025.03.24 |
---|