Post

[Spring] 0. 객체지향 프로그래밍

객체지향 프로그래밍 (Object-Oriented Programming)


Spring 프레임워크를 깊이 있게 이해하려면, 그 뿌리가 되는 객체지향 프로그래밍 개념을 먼저 알아야 한다.


객체지향 프로그래밍이란?

  • 프로그램을 객체 단위로 구성하여 코드의 재사용성유지보수성을 높이는 방법론
  • 객체는 데이터(속성)기능(메서드)을 하나로 묶은 단위이다.
  • 객체지향 프로그래밍에서는 객체들이 서로 협력하며 문제를 해결한다.


객체지향 설계의 장점

  • 재사용성 : 상속과 모듈화를 통해 코드를 재사용 가능.
  • 유지보수성 : 캡슐화로 내부 구현 변경이 외부에 영향을 미치지 않음.
  • 확장성 : 다형성과 추상화를 통해 새로운 기능을 쉽게 추가 가능.


객체지향의 4대 핵심 원칙

  • 캡슐화(Encapsulation) : 데이터와 해당 데이터를 처리하는 메서드를 하나의 객체 안에 묶고, 외부에서 직접 접근하지 못하도록 은닉한다.
  • 상속(Inheritance) : 한 클래스가 다른 클래스의 속성과 메서드를 물려받아 코드 재사용성을 높인다.
  • 다형성(Polymorphism) : 같은 이름의 메서드가 상황에 따라 다른 동작을 하도록 한다.
  • 추상화(Abstraction) : 복잡한 세부사항을 숨기고, 필요한 기능만 노출하여 단순화시킨다.



자바에서 객체지향 구현하기


클래스와 객체

  • 클래스 : 객체를 생성하기 위한 설계도
  • 객체 : 클래스의 인스턴스, 실제 메모리에 할당되어 동작한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Car {
    // 속성 (필드)
    private String brand;
    private int speed;

    // 생성자
    public Car(String brand, int speed) {
        this.brand = brand;
        this.speed = speed;
    }

    // 메서드
    public void drive() {
        System.out.println(brand + "가 " + speed + "km/h로 주행 중입니다.");
    }
}

// 객체 생성
Car myCar = new Car("Tesla", 100);
myCar.drive(); // 출력: Tesla 100km/h로 주행 중입니다.


캡슐화

  • 데이터를 private 로 선언하고, gettersetter 메서드를 통해 접근을 제어한다.


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
public class Person {
    private String name;
    private int age;

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

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

    // Getter
    public int getAge() {
        return age;
    }

    // Setter
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
}


상속

  • extends 키워드를 사용해 부모 클래스의 속성과 메서드를 자식 클래스가 물려받는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Animal {
    public void eat() {
        System.out.println("동물이 밥을 먹습니다.");
    }
}

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

// 사용
Dog dog = new Dog();
dog.eat();  // 출력: 동물이 밥을 먹습니다.
dog.bark(); // 출력: 멍멍!


다형성

  • 메서드 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현된다.
  • 오버로딩 : 같은 이름의 메서드에 매개변수를 다르게 정의
  • 오버라이딩 : 부모 클래스의 메서드를 자식 클래스에서 재정의


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Animal {
    public void makeSound() {
        System.out.println("소리를 냅니다.");
    }
}

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

// 사용
Animal cat = new Cat();
cat.makeSound(); // 출력: 야옹!


추상화

  • abstract 클래스나 interface 를 사용해 구현해야 할 메서드의 ‘규칙’을 정의한다.


1
2
3
4
5
6
7
8
9
10
public interface Flyable {
    void fly();
}

public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("새가 날아갑니다!");
    }
}



SOLID : 좋은 객체지향 설계를 위한 5대 원칙


SOLID 는 객체지향의 4대 원칙을 기반으로, 더욱 유연하고 유지보수하기 쉬우며, 이해하기 편한 소프트웨어를 만들기 위한 5가지 설계 원칙이다.

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

    “하나의 클래스는 단 하나의 책임만 가져야 한다.”

    클래스를 변경해야 할 이유가 오직 하나여야 한다는 뜻이다.
    예를 들어, User 클래스가 사용자 정보 관리와 이메일 발송 기능을 모두 갖는 것은 SRP 위반이다.
    UserServiceEmailService 로 책임을 분리해야 한다.


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

    “소프트웨어 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하지만, 변경에는 닫혀 있어야 한다.”

    새로운 기능을 추가하더라도 기존 코드를 수정해서는 안 된다는 원칙이다
    다형성추상화를 통해, 새로운 결제 수단을 추가할 때 PaymentService 코드를 바꾸는 대신 KakaoPay 클래스를 새로 추가하는 것만으로 기능이 확장되도록 설계해야 한다.


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

    “프로그램의 정확성을 깨뜨리지 않으면서, 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있어야 한다.”

    상속 관계에서 자식 클래스가 부모의 기능을 오작동하게 만들면 안 된다는 뜻이다.
    예를 들어, Bird 를 상속받은 Penguinfly() 메서드를 제대로 수행하지 못한다면 LSP 위반이다.


  4. ISP: 인터페이스 분리 원칙(Interface Segregation Principle) “클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.”

    너무 큰 인터페이스 하나보다, 역할에 맞게 잘게 쪼개진 여러 개의 인터페이스가 낫다는 원칙입니다.
    예를 들어, SmartMachine 인터페이스에 print(), scan(), fax() 가 모두 있다면, scan 기능만 필요한 클라이언트도 print, fax 에 불필요하게 의존하게 된다. Printable, Scannable 등으로 인터페이스를 분리하는 것이 더 좋다.


  5. DIP: 의존관계 역전 원칙(Dependency Inversion Principle) “추상화에 의존해야지, 구체화에 의존하면 안 된다.”

    세부적인 구현 클래스가 아니라, 추상적인 인터페이스나 상위 클래스에 의존해야 한다는 원칙이다. 스프링의 의존성 주입(DI)이 바로 이 원칙을 구현하는 대표적인 기술이다.
    Controller 가 구체적인 MyService 클래스에 의존하는 게 아니라, Service 라는 인터페이스에 의존하게 만들어, 나중에 다른 구현체로 쉽게 교체할 수 있도록 설계해야 한다.

This post is licensed under CC BY 4.0 by the author.