“캡슐화, 추상화, 상속, 다형성! 외우긴 했는데…”
??? : 객체지향의 4대 특징이 뭐야?
나 : 캡슐화, 추상화, 상속, 다형성?
??? : 오케이~ 그럼 너 프로젝트에서 어떻게 쓰고 있어?
나 : 어… DTO에서 상속 쓴 것 같고… (점점 작아짐)
이런 상황, 익숙하지 않나요...?
📌 문제는 개념과 실전이 연결되지 않는다는 것!
이번 기회에 객체지향 프로그래밍(OOP)의 핵심 4요소를 다시 정리해보고,
내가 진행 중인 실제 프로젝트에서 어떻게 적용했는지도 함께 살펴보려 한다.
데이터와 그 데이터를 다루는 로직을 하나로 묶고, 외부에서 직접 접근하지 못하도록 보호
클래스 내부 상태는 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;
}
}
}
불필요한 정보는 숨기고 핵심만 드러내는 것
인터페이스나 추상 클래스를 통해 동작의 틀만 제공
사용자는 내부 구현보다 ‘무엇을 할 수 있는가’에 집중
📌 예시
→ PaymentService 인터페이스만 보고도
→ 결제 기능이 있다는 걸 알 수 있음 (구현 방식은 몰라도 됨)
public interface PaymentService {
void pay(int amount);
}
기존 클래스를 확장하여 새로운 클래스를 만드는 것
중복 코드를 줄이고
공통된 기능을 묶어 상속 구조로 재사용
public class BaseResponse {
private int code;
private String message;
}
public class UserResponse extends BaseResponse {
private String username;
}
📌 BaseResponse를 상속하여 모든 응답 객체가 공통된 구조를 갖게 함
하나의 인터페이스(또는 상위 타입)로 여러 구현을 다룰 수 있는 것
런타임 시점에 어떤 객체가 들어와도 공통된 방식으로 처리 가능
인터페이스 기반 설계에서 매우 중요
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로 로깅 시스템 구축한 좌충우동 일지 공유할 예정 ㅎㅎ