객체지향의 4가지 특징, 설명할 수 있나요?

Minseo Lim·2025년 4월 25일

“캡슐화, 추상화, 상속, 다형성! 외우긴 했는데…”


🎯 글을 쓰게 된 계기

??? : 객체지향의 4대 특징이 뭐야?
나 : 캡슐화, 추상화, 상속, 다형성?
??? : 오케이~ 그럼 너 프로젝트에서 어떻게 쓰고 있어?
나 : 어… DTO에서 상속 쓴 것 같고… (점점 작아짐)

이런 상황, 익숙하지 않나요...?

📌 문제는 개념과 실전이 연결되지 않는다는 것!

이번 기회에 객체지향 프로그래밍(OOP)의 핵심 4요소를 다시 정리해보고,
내가 진행 중인 실제 프로젝트에서 어떻게 적용했는지도 함께 살펴보려 한다.


1️⃣ 캡슐화 (Encapsulation)

데이터와 그 데이터를 다루는 로직을 하나로 묶고, 외부에서 직접 접근하지 못하도록 보호

클래스 내부 상태는 private으로 숨기고
getter, setter, 혹은 별도 메서드로만 접근 허용

📌 이유?
→ 외부에서 객체 상태를 함부로 바꾸지 못하게 함
→ 안정성과 유지보수성 향상

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void changeName(String newName) {
        if (newName != null && !newName.isBlank()) {
            this.name = newName;
        }
    }
}

2️⃣ 추상화 (Abstraction)

불필요한 정보는 숨기고 핵심만 드러내는 것

인터페이스나 추상 클래스를 통해 동작의 틀만 제공
사용자는 내부 구현보다 ‘무엇을 할 수 있는가’에 집중

📌 예시
→ PaymentService 인터페이스만 보고도
→ 결제 기능이 있다는 걸 알 수 있음 (구현 방식은 몰라도 됨)

public interface PaymentService {
    void pay(int amount);
}

3️⃣ 상속 (Inheritance)

기존 클래스를 확장하여 새로운 클래스를 만드는 것

중복 코드를 줄이고
공통된 기능을 묶어 상속 구조로 재사용
public class BaseResponse {
    private int code;
    private String message;
}

public class UserResponse extends BaseResponse {
    private String username;
}

📌 BaseResponse를 상속하여 모든 응답 객체가 공통된 구조를 갖게 함


4️⃣ 다형성 (Polymorphism)

하나의 인터페이스(또는 상위 타입)로 여러 구현을 다룰 수 있는 것

런타임 시점에 어떤 객체가 들어와도 공통된 방식으로 처리 가능
인터페이스 기반 설계에서 매우 중요
public class PaymentProcessor {
    public void process(PaymentService service) {
        service.pay(10000);
    }
}

📌 CreditCardPayment, KakaoPayPayment, NaverPayPayment 등 어떤 구현체가 들어와도 process()는 문제없이 동작함


🛠 실제 프로젝트 적용 예시

public interface DeviceAdapter {
    void connect();
}
@Component
public class PosDeviceAdapter implements DeviceAdapter {
    public void connect() {
        // POS 연결 로직
    }
}
@Component
public class PrinterDeviceAdapter implements DeviceAdapter {
    public void connect() {
        // 프린터 연결 로직
    }
}
@Service
public class DeviceService {
    private final List<DeviceAdapter> deviceAdapters;

    public DeviceService(List<DeviceAdapter> deviceAdapters) {
        this.deviceAdapters = deviceAdapters;
    }

    public void connectAllDevices() {
        for (DeviceAdapter adapter : deviceAdapters) {
            adapter.connect();
        }
    }
}

✅ 추상화: DeviceAdapter 인터페이스로 동작만 명시
✅ 다형성: 구현체가 여러 개여도 connect()만 호출하면 됨
✅ 캡슐화: 내부 연결 로직은 각 클래스 내부에서만 관리
✅ 상속은 필요 시 공통 로직 묶을 때 적용


🎯 객체지향 설계의 의의

단순히 “특징을 말할 수 있다”가 중요한 게 아니라
코드를 짤 때 그 개념을 인지하고 설계하는 게 진짜 객체지향이다.

예전엔 나도 “DTO에서 상속 써봤지~” 정도였는데, 지금은 왜 인터페이스를 만들고, 왜 구현체를 여러 개 두는지 명확한 기준이 생겼다. 프로젝트에 다시 적용해봐야징 ㅎㅎ


✍️ 마무리
• OOP 4대 특징은 서로 독립적이지 않다.
→ 함께 어우러져야 코드가 유연하고 견고해진다.
• 개념을 안다고 끝이 아니라,
→ 실제 설계와 구현에 어떻게 스며들게 할지가 핵심이다.

다음 글에서는 AOP로 로깅 시스템 구축한 좌충우동 일지 공유할 예정 ㅎㅎ

profile
프로덕트 엔지니어 임민서입니다.

0개의 댓글