[JAVA] 좋은 객체지향 설계의 5가지 원칙(SOLID)

728x90

좋은 객체지향 설계를 위해 사용되는 다섯 가지 원칙을 SOLID라고 합니다. SOLID는 각 원칙의 첫 글자를 따서 만들어진 약어입니다. 이 원칙들은 소프트웨어의 유지보수성, 재사용성, 확장성을 향상시키는 데 도움이 되는 지침을 제공합니다.

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

하나의 클래스는 하나의 책임만 가져야 합니다. 즉, 클래스는 변경의 이유가 하나여야 합니다. 이는 클래스가 특정한 기능이나 역할에 집중되도록 하여 코드의 응집성을 높이고, 변경 사항이 발생할 때 해당 클래스만 수정하면 되도록 합니다. 즉 변경사항이 발생할 때 파급효과가 적다면 단일책임원칙을 잘 따른것으로 볼 수 있습니다.

 

아래는 예시코드입니다.

 

 SRP 위반 예시)

 

class Report {
    void generateReport() {
        // 보고서 생성 코드
    }

    void saveToFile() {
        // 파일에 보고서 저장 코드
    }
}

 

SRP를 적용한 예시)

class Report {
    void generateReport() {
        // 보고서 생성 코드
    }
}

class ReportSaver {
    void saveToFile(Report report) {
        // 파일에 보고서 저장 코드
    }
}

 

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

소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정(변)에는 닫혀 있어야 합니다. 이는 새로운 기능을 추가할 때 기존의 코드를 변경하지 않고 확장할 수 있도록 하는 원칙입니다. 주로 추상화와 다형성을 통해 이를 구현합니다.

 

아래는 예시코드입니다.

 

OCP 위반 예시)

 

class Rectangle {
    int width;
    int height;
}

class AreaCalculator {
    double calculateArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }
}

 

OCP를 적용한 예시)

interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    int width;
    int height;

    public double calculateArea() {
        return width * height;
    }
}

class Circle implements Shape {
    int radius;

    public double calculateArea() {
        return 3.14 * radius * radius;
    }
}

 

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

상위 타입의 객체를 하위 타입의 객체로 대체해도 프로그램은 정상적으로 동작해야 합니다. 즉, 상속 관계에서는 하위 클래스가 상위 클래스를 완전히 대체할 수 있어야 합니다. 이를 통해 다형성을 보장하고, 코드의 신뢰성을 높입니다.

 

아래는 예시코드입니다.

 

LSP 위반 예시)

class Bird {
    void fly() {
        // 날다
    }
}

class Ostrich extends Bird {
    void fly() {
        throw new UnsupportedOperationException("타조는 날지 못해요");
    }
}

// 클라이언트 코드
void makeBirdFly(Bird bird) {
    bird.fly();
}

 

LSP를 적용한 예시)

abstract class Bird {
    abstract void move();
}

class Sparrow extends Bird {
    void move() {
        System.out.println("참새가 날아갑니다");
    }
}

class Ostrich extends Bird {
    void move() {
        System.out.println("타조가 달립니다");
    }
}

// 클라이언트 코드
void makeBirdMove(Bird bird) {
    bird.move();
}

 

 

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

클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다. 즉, 하나의 큰 인터페이스보다는 작은 여러 개의 인터페이스가 더 좋습니다. 이는 클래스가 자신이 필요로 하지 않는 기능에 의존하지 않도록 하여 결합도를 낮추고 응집도를 높입니다.

 

아래는 예시코드입니다.

 

ISP 위반 예시)

interface Worker {
    void work();
    void eat();
}

class Manager implements Worker {
    public void work() {
        // 일하다
    }

    public void eat() {
        // 식사하다
    }
}

 

ISP를 적용한 예시)

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Worker implements Workable, Eatable {
    public void work() {
        // 일하다
    }

    public void eat() {
        // 식사하다
    }
}

 

5. 의존 역전 원칙 (Dependency Inversion Principle - DIP)

고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 양쪽 모두 추상화에 의존해야 합니다. 또한, 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항은 추상화에 의존해야 합니다. 이는 추상화를 통해 모듈 간의 결합도를 낮추고, 유연성을 확보합니다.

 

아래는 예시코드입니다.

 

DIP 위반 예시)

class LightBulb {
    void turnOn() {
        // 전구를 켜다
    }

    void turnOff() {
        // 전구를 끄다
    }
}

class Switch {
    LightBulb bulb;

    Switch(LightBulb bulb) {
        this.bulb = bulb;
    }

    void operate() {
        // 스위치 동작 코드
        bulb.turnOn();
    }
}

 

DIP를 적용한 예시 (스프링을 사용한 의존성 주입)

 

스프링 프레임워크를 사용하면 런타임 시에 의존성을 주입할 수 있습니다. 이렇게 하면 Switch 클래스가 Switchable 인터페이스에 의존하면서 런타임에 실제 구현체를 주입받을 수 있습니다.

interface Switchable {
    void turnOn();
    void turnOff();
}

class LightBulb implements Switchable {
    public void turnOn() {
        // 전구를 켜다
    }

    public void turnOff() {
        // 전구를 끄다
    }
}

class Switch {
    Switchable device;

    Switch(Switchable device) {
        this.device = device;
    }

    void operate() {
        // 스위치 동작 코드
        device.turnOn();
    }
}

 

 

이러한 SOLID 원칙들은 객체지향 설계를 통해 유지보수성이 뛰어나고 확장이 용이한 소프트웨어를 개발하는 데 도움을 주는 기본적인 원리들입니다.

'JAVA Programming > Java' 카테고리의 다른 글

[JAVA] 키보드 입력 받기(Scanner)  (0) 2024.07.14
[JAVA] 메소드  (0) 2024.07.10
[JAVA] 제어문  (0) 2024.07.09
[JAVA] 타입 추론  (0) 2024.07.09
[JAVA] 배열  (0) 2024.07.09