| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- 객체복제
- linux 권한
- SQL문법
- 어댑터패턴
- mcp client
- 매직메서드
- 소프트웨어설계
- 인덱스
- docker network
- 객체지향
- dhgrp
- PHP객체지향
- docker
- OOP
- LLM
- 데코레이터패턴
- 데이터베이스
- mcp server
- MCP
- PHP
- SQL
- MySQL
- 디자인패턴
- ai 에이전트
- Tool Calling
- Agent Loop
- SQL기초
- Laravel
- 라라벨
- ai agent
- Today
- Total
개발블로그
[객체지향] 클래스를 쓰면 객체지향일까? 객체지향이 필요한 진짜 이유 본문
코드는 왜 점점 관리하기 어려워질까?
처음 프로그램을 만들 때는 코드가 단순하다.
변수 몇 개를 만들고, 함수를 호출하고, 조건문과 반복문으로 흐름을 제어하면 된다.
하지만 기능이 조금씩 추가되면 문제가 생긴다.
문제는 데이터와 규칙이 코드 곳곳에 흩어지기 시직함
- 특정 데이터가 어디서 변경되는지 찾기 어렵다
- 같은 규칙이 여러 곳에 중복된다
- 한 곳을 수정했는데 예상하지 못한 다른 기능이 깨진다
- 새로운 기능을 추가할수록 기존 코드를 건드리는 일이 많아진다
결국 코드가 어려워지는 이유는 단순히 줄 수가 많아져서가 아니라
데이터와 그 데이터를 다루는 규칙이 흩어지고, 변경의 영향 범위를 예측하기 어려워지기 때문
객체지향은 이 문제를 어떻게 바라볼까?
관련된 데이터와 동작을 하나의 객체 안에 모으면 어떨까?
관련된 데이터와 동작을 하나로 묶으면 다음과 같은 장점이 생김
- 관련된 규칙을 한곳에서 관리할 수 있다
- 외부 코드가 내부 상태를 함부로 바꾸지 못하게 만들 수 있다.
- 기능이 바뀌어도 수정 범위를 줄일 수 있다.
- 코드가 현실의 개념과 비슷한 단위로 나뉘어 이해하기 쉬워진다
객체지향은 관련 있는 상태와 행동을 객체로 묶고, 객체들이 각자의 책임을 수행하며 협력하도록 만드는 방식
클래스와 객체
| 구분 | 의미 |
| 클래스 | 객체가 어떤 데이터와 동작을 가질지 정의한 것 |
| 객체 | 클래스를 바탕으로 실제 메모리에 만들어진 대상 |
| 인스턴스 | 특정 클래스로부터 만들어진 객체 |
객체의 상태와 행동
| 구분 | 의미 | 예시 |
| 상태 | 객체가 가지고 있는 데이터 | 잔액, 계좌 상태, 소유자 |
| 행동 | 객체가 수행할 수 있는 동작 | 입금, 출금, 잔액 조회 |
객체 안에 상태와 행동을 함께 두면, 객체가 자신의 데이터를 직접 관리할 수 있음.
주요 특징
| 특징 | 핵심 의미 | 목적 |
| 캡슐화 | 상태와 행동을 하나로 묶고 외부 접근을 제한한다. | 객체의 상태를 안전하게 관리한다. |
| 추상화 | 복잡한 내부 구현 대신 필요한 기능만 드러낸다. | 사용하는 쪽의 복잡도를 줄인다. |
| 상속 | 기존 클래스의 공통 속성과 동작을 물려받는다. | 중복을 줄이고 공통 구조를 재사용한다. |
| 다형성 | 같은 요청에 대해 객체마다 다르게 동작한다. | 변경에 유연한 구조를 만든다. |
캡슐화 : 상태를 안전하게 관리하기
개념
객체의 상태와 그 상태를 다루는 행동을 하나로 묶고, 외부에서 상태를 직접 변경하지 못하도록 제한
객체가 자신의 상태를 올바른 규칙 안에서 관리하도록 만드는 것
추상화 : 필요한 기능만 드러내기
개념
복잡한 내부 구현 중에서 사용하는 쪽에 필요한 부분만 드러내는 것 (불필요한 복잡성을 줄이기)
ex) 출금 기능
내부적으로 : 계좌 상태 확인 → 출금 금액 검증 → 잔액 확인 → 잔액 차감 → 거래 내역 저장
=> 하지만 외부 코드가 이 모든 과정을 직접 알 필요는 없고.
"출금한다"는 기능만 알면 됨
상속 : 공통 구조 재사용하기
개념
기존 클래스의 속성과 동작을 물려받아 새로운 클래스를 만드는 방식
=> 공통 기능을 여러 클래스에서 반복해서 작성하지 않도록 도와줌
ex) 일반 계좌와 적금 계좌가 모두 계좌라는 공통 특성을 가진다고 할 때,
둘 다 소유자, 잔액, 입금 기능을 가질 수 있음
=> 공통 기능 재사용
다형성 : 같은 요청, 다른 동작
개념
같은 요청을 보내도 객체의 종류에 따라 다르게 동작할 수 있는 성질.
=> 같은 방식으로 요청하되, 실제 동작은 객체가 결정하게 만드는 것
ex) 여러 결제 수단에서
class CardPayment:
def pay(self, amount):
return "카드로 결제"
class KakaoPayPayment:
def pay(self, amount):
return "카카오페이로 결제"
두 객체는 모두 pay()라는 같은 동작을 제공함.
외부 코드는 결제 수단이 카드인지 카카오페이인지에 따라 코드를 매번 다르게 작성할 필요가 없음.
def checkout(payment, amount):
return payment.pay(amount)
=> 이 구조에서는 새로운 결제 수단이 추가되어도 checkout() 함수의 흐름을 크게 바꾸지 않아도 된다.
객체마다 pay()를 자기 방식대로 구현하면 됨
객체지향의 장점과 한계
장점
| 장점 | 설명 |
| 책임 분리 | 객체별로 맡은 역할이 나뉘어 코드 구조를 이해하기 쉬워진다. |
| 변경 범위 축소 | 특정 규칙이 바뀌었을 때 수정해야 할 위치를 예측하기 쉽다. |
| 재사용성 | 공통 동작을 여러 객체에서 재사용할 수 있다. |
| 유연성 | 다형성을 활용하면 새로운 종류의 객체를 추가하기 쉽다. |
한계
| 상황 | 이유 |
| 매우 작은 프로그램 | 클래스 구조가 오히려 코드를 길게 만들 수 있다. |
| 단순한 데이터 변환 | 함수 중심 흐름이 더 직접적일 수 있다. |
| 일회성 스크립트 | 객체 구조를 설계하는 비용이 더 클 수 있다. |
객체지향 적용 시 주의점
| 기준 | 질문 |
| 필요성 | 이 문제에 객체 구조가 필요한가? |
| 책임 | 이 객체는 어떤 일을 맡는가? |
| 경계 | 이 책임은 다른 객체와 분리될 이유가 있는가? |
| 관계 | 상속이 자연스러운 관계인가? |
| 행동 | 객체가 단순 데이터 보관함이 아니라 의미 있는 행동을 가지는가? |
객체 지향이 항상 필요한 것은 아니다
단순한 계산이나 일회성 데이터 변환처럼 흐름이 짧고 규칙이 복잡하지 않은 경우에는 함수 중심 코드가 더 읽기 쉬울 수 있음.
ex)
| 상황 | 더 단순할 수 있는 방식 |
| 숫자 계산 | 함수 |
| 문자열 포맷 변경 | 함수 |
| CSV 파일 한 번 변환 | 스크립트 |
| 간단한 유틸리티 로직 | 함수 모음 |
클래스가 많다고 좋은 설계는 아니다
클래스가 많아진다고 책임이 명확해지는 것은 아니다.
오히려 기준 없이 잘게 나눈 클래스는 전체 흐름을 따라가기 어렵게 만든다.
[ 기준 ]
- 이 객체는 어떤 책임을 가지고 있는가?
- 이 책임은 다른 객체와 분리될 이유가 있는가?
상속은 신중하게 사용해야 한다
자식 클래스는 부모 클래스의 구조와 변경에 영향을 받는다.
=> 단순히 코드 중복을 줄이기 위해 상속을 선택하면, 구조가 불필요하게 강하게 묶일 수 있음
[ 고려사항 ]
| 질문 | 의미 |
| A는 B의 한 종류인가? | 자연스러운 상속 관계인지 확인한다. |
| 부모 변경이 자식에게 안전한가? | 변경 영향 범위를 확인한다. |
| 공통 기능만 필요한가? | 조합이 더 적합할 수 있다. (필요한 객체를 내부에 포함해서 기능을 사용하는 방식) |
getter와 setter만 있는 객체는 조심해야 함
객체지향에서 캡슐화는 상태를 숨기는 것만 의미하지 않음.
객체가 자신의 상태를 올바른 방식으로 관리하도록 만드는 것이 중요.
객체가 getter, setter만 가지고 있다면, 사실상 데이터 보관함처럼 사용될 수 있음.
외부 코드가 값을 꺼내고, 판단하고, 다시 넣는다 (규칙이 외부에 있음)
객체지향에서 중요한 것을 필드를 숨겼는지가 아니라,
상태를 변경하는 규칙이 적절한 객체 안에 있는지다.
ex) 출금 가능 여부 판단과 잔액 변경을 계좌 객체 내부에 두기
SOLID와 디자인 패턴
SOLID = 객체의 책임을 다루는 원칙
| 원칙 | 핵심 질문 | 간단한 예시 |
| SRP | 이 클래스가 바뀌는 이유가 하나인가? | 결제 처리, 결제 내역 저장, 알림 발송을 하나의 클래스가 모두 담당하면 책임이 많다. |
| OCP | 기능을 추가할 때 기존 코드를 계속 수정해야 하는가? | 결제 수단이 추가될 때마다 if card, if point 조건문을 수정한다면 확장에 약하다. |
| LSP | 자식 클래스를 부모 타입처럼 사용해도 문제가 없는가? | Bird를 상속한 Penguin이 fly()를 제대로 수행할 수 없다면 상속 관계가 어색하다. |
| ISP | 사용하지 않는 기능까지 의존하고 있지는 않은가? | 프린터가 print(), scan(), fax()를 모두 강제받으면 단순 프린터에는 불필요한 기능이 생긴다. |
| DIP | 상위 정책이 구체적인 구현에 직접 의존하고 있지는 않은가? | 주문 서비스가 CardPayment에 직접 의존하면 다른 결제 수단으로 바꾸기 어렵다. |
디자인 패턴 = 자주 반복되는 설계 문제와 해결 구조를 정리
| 패턴 | 핵심 아이디어 | 사용 상황 |
| 전략 패턴 | 실행할 방식을 객체로 분리하고 필요할 때 교체한다. | 결제 방식, 할인 정책, 정렬 방식처럼 여러 방식 중 하나를 선택해야 할 때 |
| 팩토리 패턴 | 객체 생성 과정을 별도의 역할로 분리한다. | 조건에 따라 생성해야 하는 객체가 달라질 때 |
| 싱글톤 패턴 | 하나의 인스턴스만 사용하도록 제한한다. | 설정 객체, 공용 자원처럼 애플리케이션 전체에서 하나만 필요한 경우 |
| 어댑터 패턴 | 서로 다른 인터페이스를 맞춰 사용할 수 있게 변환한다. | 외부 API나 기존 코드를 현재 코드 구조에 맞춰 연결할 때 |
| 옵저버 패턴 | 어떤 객체의 상태 변화가 발생했을 때 관련 객체들에게 알린다. | 이벤트 처리, 알림, UI 상태 업데이트처럼 변화에 반응해야 할 때 |
| 데코레이터 패턴 | 기존 객체를 감싸서 기능을 추가한다. | 원래 코드를 크게 바꾸지 않고 로깅, 권한 확인, 캐싱 같은 기능을 덧붙일 때 |
'STUDY > CS' 카테고리의 다른 글
| [디자인패턴] 팩토리 패턴 (1) | 2026.09.04 |
|---|---|
| [디자인 패턴] 전략패턴 (0) | 2026.09.04 |
| [디자인 패턴] 디자인 패턴이란? 반복되는 설계 문제를 해결하는 방법 (0) | 2026.09.03 |
| [객체지향] SOLID 원칙 : 좋은 객체지향 설계를 판단하는 5가지 기준 (0) | 2026.09.03 |
| REST API (1) | 2026.08.21 |