콘텐츠로 이동

객체지향 프로그래밍 (OOP)

1. 핵심 개념 4가지

캡슐화 (Encapsulation)

데이터와 메서드를 하나의 단위로 묶고, 접근 제어자로 정보를 은닉합니다.

class BankAccount {
    private double balance;  // 외부에서 직접 접근 불가

    public void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    public double getBalance() {
        return balance;
    }
}
  • 장점: 유지보수 용이, 데이터 무결성 보호

상속 (Inheritance)

부모 클래스의 속성과 메서드를 자식 클래스가 물려받습니다. IS-A 관계입니다.

class Animal {
    void eat() { System.out.println("먹는다"); }
}

class Dog extends Animal {
    void bark() { System.out.println("멍멍"); }
}

Dog d = new Dog();
d.eat();   // 부모 메서드 사용 가능
d.bark();
  • 장점: 코드 재사용, 계층적 구조 표현

다형성 (Polymorphism)

같은 메서드가 다른 객체에서 다르게 동작합니다.

오버로딩 (Overloading) — 컴파일 시간 결정 (정적 바인딩) - 같은 이름, 다른 매개변수

void print(int x) { }
void print(String s) { }
void print(int x, int y) { }

오버라이딩 (Overriding) — 실행 시간 결정 (동적 바인딩) - 부모 메서드를 자식이 재정의

class Animal {
    void sound() { System.out.println("..."); }
}

class Dog extends Animal {
    @Override
    void sound() { System.out.println("멍멍"); }
}

class Cat extends Animal {
    @Override
    void sound() { System.out.println("야옹"); }
}

Animal a = new Dog();
a.sound();  // "멍멍" 출력 (동적 바인딩)

추상화 (Abstraction)

복잡한 내부를 숨기고 필요한 기능만 노출합니다.

abstract class Shape {
    abstract double getArea();  // 구현 강제
}

class Circle extends Shape {
    double r;
    @Override
    double getArea() { return 3.14 * r * r; }
}

2. 추상 클래스 vs 인터페이스

구분 추상 클래스 인터페이스
목적 IS-A 관계 CAN-DO 관계
상속 단일 상속만 다중 구현 가능
메서드 일부 구현 가능 기본적으로 추상 (Java 8+ default 허용)
생성자 가능 불가
접근 제어자 모두 가능 public만
변수 모든 타입 static final (상수)
// 추상 클래스 - IS-A 관계
abstract class Vehicle {
    abstract void drive();
    void refuel() { System.out.println("주유"); }  // 구현 포함 가능
}

// 인터페이스 - CAN-DO 관계
interface Flyable { void fly(); }
interface Swimmable { void swim(); }

// 다중 구현
class FlyingFish extends Animal implements Flyable, Swimmable {
    public void fly() { }
    public void swim() { }
}

3. SOLID 원칙

S — 단일 책임 원칙 (SRP)

클래스는 하나의 책임만 가져야 합니다.

// Bad: 하나의 클래스가 급여 계산 + 보고서 생성을 모두 담당
class Employee {
    void calculatePay() { }
    void generateReport() { }
}

// Good: 분리
class PayCalculator { void calculatePay() { } }
class ReportGenerator { void generateReport() { } }

O — 개방-폐쇄 원칙 (OCP)

확장에는 열려있고, 수정에는 닫혀있어야 합니다.

// Bad: 새 도형 추가 시 기존 코드 수정 필요
class AreaCalculator {
    double calculate(Object shape) {
        if (shape instanceof Circle) { ... }
        else if (shape instanceof Square) { ... }  // 수정 필요
    }
}

// Good: 새 도형은 Shape를 구현만 하면 됨
interface Shape { double getArea(); }
class Circle implements Shape { ... }
class Square implements Shape { ... }

L — 리스코프 치환 원칙 (LSP)

자식 클래스는 부모 클래스를 대체할 수 있어야 합니다.

// 부모 타입 변수에 자식 객체 할당 가능
Animal animal = new Dog();
animal.sound();  // 정상 동작해야 함

I — 인터페이스 분리 원칙 (ISP)

클라이언트가 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리합니다.

// Bad: 모든 기능이 하나의 인터페이스
interface Worker { void work(); void eat(); }

// Good: 역할에 따라 분리
interface Workable { void work(); }
interface Eatable { void eat(); }

D — 의존성 역전 원칙 (DIP)

구체적인 클래스가 아닌 추상화(인터페이스)에 의존합니다.

// Bad: 구체적인 클래스에 의존
class OrderService {
    MySQLDatabase db = new MySQLDatabase();
}

// Good: 추상화에 의존 (의존성 주입)
class OrderService {
    private Database db;  // 인터페이스
    OrderService(Database db) { this.db = db; }
}

4. 클래스 관계

관계 키워드 설명 예시
IS-A 상속 자식이 부모의 일종 Dog IS-A Animal
HAS-A (합성) 포함 전체가 부분을 소유 (강함) Car HAS-A Engine
HAS-A (집합) 포함 전체가 부분을 참조 (약함) Team HAS-A Player
USES-A 의존 일시적으로 사용 Driver USES-A Car

시험 포인트

  • 오버로딩 vs 오버라이딩: 매개변수 다름 vs 메서드 재정의
  • 정적 바인딩 vs 동적 바인딩: 오버로딩 vs 오버라이딩
  • 추상 클래스 vs 인터페이스: 단일상속/일부구현 vs 다중구현/전체추상
  • SOLID 원칙 5가지 각각의 의미와 예시
  • 합성 vs 집합: 부분이 전체에 종속 vs 독립적으로 존재 가능