프로그램을 여러 개의 독립적인 '객체(Object)'들의 모임으로 보고, 이 객체들이 서로 상호작용하도록 만드는 프로그래밍 방식이다.

현실 세계의 사물이나 개념(자동차, 사람, 주문 등)을 컴퓨터 속 객체로 모델링하는 방식이라고 생각하면 쉽다.
예를 들어 '자동차'라는 객체는 색상, 속도와 같은 데이터(속성, Property)와 달리다(), 멈추다()와 같은 기능(메서드, Method)을 가진다.
이렇게 프로그램을 객체 단위로 설계하면 코드를 재사용하기 쉽고, 유지보수가 용이하며, 대규모 프로젝트를 관리하기에 효율적이다.
응집도는 하나의 클래스(또는 모듈)가 가진 내부 요소들(메서드, 속성)이 얼마나 서로 밀접하게 관련되어 있는지를 나타내는 척도이다. 즉, 클래스가 단 하나의 명확한 책임과 목적을 가지고 있는가를 의미한다.
높은 응집도를 가진 클래스는 하나의 주제에 관련된 기능과 데이터만을 가집니다. 예를 들어 '사용자(User)' 클래스는 사용자의 이름, 이메일, 비밀번호와 같은 속성과 로그인, 로그아웃, 정보 수정과 같은 메서드들로 구성된다. 이처럼 관련된 것들이 한곳에 모여있어 클래스의 역할이 명확해진다.
// 주문 관련 책임만 가지는 Order 클래스
public class Order {
private List<MenuItem> items;
private double totalPrice;
public void addItem(MenuItem item) {
// 아이템 추가 로직
}
public void calculateTotalPrice() {
// 총액 계산 로직
}
}
// 주문 내역을 출력하는 책임만 가지는 OrderPrinter 클래스
public class OrderPrinter {
public void printOrder(Order order) {
// 주문 내역 출력 로직
}
}
결합도는 서로 다른 클래스(또는 모듈)들이 얼마나 강하게 연결되어 있는지, 즉 서로에게 얼마나 의존적인지를 나타내는 척도입니다.
조립할 때 특정한 클래스에 종속되지 않아야 한다.
종속되는 정도가 낮아야 한다
// 알림 기능을 위한 인터페이스
public interface Notifier {
void send(String message);
}
// 이메일 알림 구현체
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
// 이메일 발송 로직
}
}
// SMS 알림 구현체
public class SmsNotifier implements Notifier {
@Override
public void send(String message) {
// SMS 발송 로직
}
}
// OrderProcessor는 구체적인 Notifier 구현 클래스를 몰라도 됨
public class OrderProcessor {
private final Notifier notifier;
// 생성자를 통해 Notifier 인터페이스에 의존성을 주입받음
public OrderProcessor(Notifier notifier) {
this.notifier = notifier;
}
public void process() {
// 주문 처리 로직 ...
notifier.send("주문이 완료되었습니다.");
}
}
🔹 정의
복잡한 시스템에서 공통적이고 핵심적인 특징만 뽑아내어 단순화하는 것.
즉, 불필요한 세부사항은 숨기고, 필요한 기능만 외부에 노출.
🔹 목적
코드의 복잡성을 줄이고 유지보수를 쉽게 한다.
상위 개념(추상 클래스, 인터페이스)을 통해 설계 중심의 프로그래밍이 가능하다.
🔹 예시
// 추상 클래스
abstract class Animal {
abstract void makeSound(); // 구체적인 소리는 동물마다 다름
}
// 구체 클래스
class Dog extends Animal {
void makeSound() {
System.out.println("멍멍");
}
}
🔹 주의점 (예외 및 오해)
추상화는 “추상 클래스나 인터페이스를 쓰는 것”만 의미하지 않는다.
→ 클래스 설계 단계에서 “불필요한 세부사항을 감추는 행위” 자체가 추상화.
구체화(concretization)와는 반대 개념이지만, 둘은 상호보완 관계임.
🔹 정의
데이터(속성)와 메서드(행동)를 하나로 묶고, 외부에서 직접 접근하지 못하도록 보호하는 것.
🔹 목적
데이터의 무결성과 보안성 확보
객체 내부 구현을 숨기고, 필요한 정보만 getter/setter로 제공
🔹 예시
public class Person {
private String name; // 외부에서 직접 접근 불가
private int age;
public String getName() { // 접근 제어 메서드
return name;
}
public void setName(String name) {
this.name = name;
}
}
🔹 주의점 (예외 및 오해)
“private + getter/setter”만이 캡슐화는 아니다.
→ 진짜 캡슐화는 “외부에서 객체 내부 상태를 마음대로 바꿀 수 없게 설계하는 것”.
public 필드로 직접 노출하면 캡슐화가 깨진다.
패키지 간 접근 제한(protected, default)도 캡슐화 전략의 일부.
🔹 정의
기존 클래스(부모 클래스)의 속성과 기능을 자식 클래스가 물려받는 것.
“재사용성”과 “확장성”을 높이는 핵심 개념.
🔹 목적
코드 재사용 (공통 코드 중복 제거)
상위 개념으로부터 일관된 설계 가능
부모 타입으로 자식 객체를 다룰 수 있는 기반 제공
🔹 예시
class Animal {
void eat() { System.out.println("먹는다"); }
}
class Dog extends Animal {
void bark() { System.out.println("짖는다"); }
}
➡️ Dog는 Animal의 eat()을 그대로 사용 가능
🔹 주의점 (예외 및 오해)
자바는 단일 상속만 지원 (하나의 클래스만 extends 가능).
→ 대신 인터페이스 다중 구현으로 보완.
부모 클래스의 private 멤버는 상속되지 않는다.
상속은 “is-a 관계”일 때만 사용해야 한다.
→ 잘못 사용하면 결합도가 높아지고 유지보수성 저하
🔹 정의
하나의 객체가 여러 형태로 동작할 수 있는 성질.
즉, 하나의 인터페이스(또는 부모 타입) 로 여러 객체를 다르게 동작시키는 것.
🔹 목적
코드의 유연성, 확장성 확보
새로운 클래스 추가 시 기존 코드 변경 최소화
🔹 예시
class Animal {
void sound() { System.out.println("소리 없음"); }
}
class Dog extends Animal {
void sound() { System.out.println("멍멍"); }
}
class Cat extends Animal {
void sound() { System.out.println("야옹"); }
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.sound(); // 멍멍
a2.sound(); // 야옹
}
}
🔹 주의점 (예외 및 오해)
다형성은 “상속 + 메서드 오버라이딩” 을 전제로 한다.
정적 다형성(오버로딩) vs 동적 다형성(오버라이딩) 구분 필요
→ 자바에서는 주로 “오버라이딩 기반의 다형성”을 의미.
객체 타입이 아니라 실행 시점의 실제 객체 기준으로 메서드가 호출된다 (동적 바인딩).
| 개념 | 핵심 키워드 | 대표 문법 요소 | 주요 목적 |
|---|---|---|---|
| 추상화 (Abstraction) | 공통 속성 추출 | abstract class, interface | 설계 단순화 |
| 캡슐화 (Encapsulation) | 정보 은닉 | private, getter/setter | 보안, 데이터 보호 |
| 상속 (Inheritance) | 코드 재사용 | extends | 확장성, 일관성 |
| 다형성 (Polymorphism) | 여러 형태 | override, upcasting | 유연성 |