[CS] Java의 객체 지향 프로그래밍(OOP)

khj·2025년 1월 30일

Computer Science

목록 보기
18/25
post-thumbnail

1. 객체 지향 프로그래밍(OOP)란?

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 객체(Object) 를 중심으로 프로그램을 설계하고 개발하는 방식입니다.
자바(Java)는 대표적인 객체 지향 언어로, 현실 세계의 개념을 코드로 쉽게 표현할 수 있도록 설계되었습니다.


2. 객체(Object)와 클래스(Class)

2.1. 객체(Object)란?

  • 속성(데이터)과 동작(메서드)을 포함하는 독립적인 개체
  • 현실 세계의 사물을 소프트웨어로 모델링한 개념

2.2. 클래스(Class)란?

  • 객체를 생성하기 위한 청사진(설계도)
  • 클래스 내에서 필드(멤버 변수) 와 메서드(함수) 를 정의하여 객체의 속성과 동작을 결정
// 클래스 정의
public class Car {
    private String brand;
    private int speed;

    public Car(String brand, int speed) {
        this.brand = brand;
        this.speed = speed;
    }

    public void accelerate() {
        speed += 10;
        System.out.println(brand + "가 가속하여 속도: " + speed);
    }
}

// 객체 생성
public class Main {
    public static void main(String[] args) {
        Car myCar = new Car("Toyota", 0);
        myCar.accelerate(); // Toyota가 가속하여 속도: 10
    }
}

3. Java의 객체 지향 4대 특징

객체 지향 프로그래밍(OOP)은 다음 네 가지 핵심 개념을 기반으로 합니다.

특징 설명
캡슐화(Encapsulation) 데이터 보호를 위해 필드를 숨기고, 접근을 제한
상속(Inheritance) 기존 클래스를 재사용하여 새로운 클래스를 생성
다형성(Polymorphism) 동일한 코드로 다양한 객체를 다룰 수 있도록 함
추상화(Abstraction) 복잡한 내부 구현을 숨기고, 필요한 기능만 노출

3.1. 캡슐화 (Encapsulation)

  • private 키워드를 사용하여 데이터를 외부에서 직접 수정하지 못하도록 보호
  • getter와 setter 메서드를 통해 접근 제어
public class Person {
    private String name;

    public String getName() { // Getter
        return name;
    }

    public void setName(String name) { // Setter
        this.name = name;
    }
}

3.2. 상속 (Inheritance)

  • 부모 클래스(상위 클래스)의 속성과 메서드를 자식 클래스(하위 클래스)가 물려받음
  • 코드의 재사용성 증가 및 유지보수 용이
// 부모 클래스
public class Animal {
    public void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스
public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("멍멍!");
    }
}

// 실행 코드
public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.sound(); // 멍멍!
    }
}

3.3. 다형성 (Polymorphism)

  • 부모 클래스를 통해 다양한 자식 클래스를 다룰 수 있음
  • 오버로딩(Overloading) 과 오버라이딩(Overriding) 으로 구현 가능
// 부모 클래스
public class Animal {
    public void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스
public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹!");
    }
}

// 실행 코드
public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Cat(); // 부모 타입으로 참조
        myAnimal.makeSound(); // 야옹!
    }
}

3.4. 추상화 (Abstraction)

  • 복잡한 구현을 숨기고, 필요한 기능만 노출
  • abstract class 와 interface 를 활용
// 추상 클래스
abstract class Shape {
    abstract void draw(); // 추상 메서드
}

// 구체 클래스
class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("원을 그립니다.");
    }
}

4. 객체 지향 설계 원칙 (SOLID 원칙)

SOLID 원칙은 객체 지향 프로그래밍에서 유지보수성과 확장성을 높이기 위해 따라야 하는 5가지 중요한 설계 원칙입니다.

원칙 설명
S 단일 책임 원칙(Single Responsibility Principle, SRP)
O 개방-폐쇄 원칙(Open-Closed Principle, OCP)
L 리스코프 치환 원칙(Liskov Substitution Principle, LSP)
I 인터페이스 분리 원칙(Interface Segregation Principle, ISP)
D 의존 역전 원칙(Dependency Inversion Principle, DIP)

4.1. 단일 책임 원칙 (SRP: Single Responsibility Principle)

  • 하나의 클래스는 하나의 책임만 가져야 한다.
  • 변경 사항이 있을 때 한 가지 이유로만 수정되어야 함
// 잘못된 설계: User 클래스가 데이터 처리까지 담당
public class User {
    public void saveToDatabase() {
        System.out.println("DB에 저장");
    }
}

// 개선된 설계: User 클래스와 UserRepository 분리
public class User {
    private String name;
}

public class UserRepository {
    public void save(User user) {
        System.out.println("DB에 저장");
    }
}

4.2. 개방-폐쇄 원칙 (OCP: Open-Closed Principle)

  • 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
  • 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있어야 함
// 확장 가능하도록 인터페이스 활용
interface Payment {
    void pay();
}

class CreditCardPayment implements Payment {
    public void pay() {
        System.out.println("신용카드 결제");
    }
}

class PayPalPayment implements Payment {
    public void pay() {
        System.out.println("PayPal 결제");
    }
}

4.3. 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)

  • 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
class Rectangle {
    int width, height;

    void setWidth(int width) { this.width = width; }
    void setHeight(int height) { this.height = height; }
}

class Square extends Rectangle {
    void setWidth(int width) { 
        super.setWidth(width);
        super.setHeight(width);
    }
}

위반 사례: 정사각형(Square)은 가로와 세로가 항상 같아야 하지만, Rectangle의 setWidth()와 setHeight()를 따르지 않아 LSP를 위반함.


4.4. 인터페이스 분리 원칙 (ISP: Interface Segregation Principle)

  • 필요한 기능만 인터페이스로 제공해야 한다.
interface Animal {
    void eat();
    void fly();
}

class Dog implements Animal { // 불필요한 fly() 메서드 강제됨
    public void eat() {}
    public void fly() {} // 개는 날지 못하지만 구현해야 함
}

잘못된 설계 → 동물마다 필요한 기능을 분리해야 함

interface Eatable { void eat(); }
interface Flyable { void fly(); }

class Bird implements Eatable, Flyable { public void eat() {} public void fly() {} }
class Dog implements Eatable { public void eat() {} }

4.5. 의존 역전 원칙 (DIP: Dependency Inversion Principle)

  • 구체적인 클래스가 아니라 인터페이스나 추상 클래스에 의존해야 한다.
class PaymentService {
    private Payment payment; // 인터페이스를 의존

    public PaymentService(Payment payment) {
        this.payment = payment;
    }

    public void processPayment() {
        payment.pay();
    }
}
profile
Spring, Django 개발 블로그

0개의 댓글