Chapter01/Java

[ java ] 객체지향 개발 SOLID 5대 원리 + 예시코드

EmmaDev_v 2024. 3. 13. 23:27

객체지향

: 문제에 대해 데이터의 관점에서 이 프로그램을 바라보며 데이터를 가지고 있는 객체들 간의 관계를 정의해서 프로그램을 만든다. (cf.C는 절차지향적 언어임)

 

 

SRP 단일책임의 원칙 ,OCP 개방폐쇄의 원칙, LSP 리스코브 치환의 원칙 , ISP 인터페이스 분리의 원칙, DIP 의존성역전의 원칙 

다섯가지 원칙의 초성을 따서 SOLID원칙 라고부는데

 

이 원칙에 걸맞는 코드는 결합도가 느슨하고 응집력이 높은 코드가 됩니다 !

 

 

 

 

소프트웨어 공학에서 모듈화는 응고결저 라고 암기하죠?

결합도가 높을수록 응집도가 낮을수록 재사용성과 유지보수성이 높아집니다!

 

 

 

모든 상황에 이 원칙들을 적용하는데는 무리가 있을 수 있으나

가독성좋고 재사용성이 높은 코드를 짜기 위해서 알아야하는 SOLID 원칙에 대해 알아볼게여

 

 

 

https://www.youtube.com/watch?v=wGWrOpRdu40 를 참고하여 개념 정리 후 예제 코드를 만들어보았습니다.

 

 

 

이 코드는 이전 오버로딩 글에서 간단하게 만든 예시 코드인데요.

public class Car {

    // 이 메서드는 주어진 색상과 유형에 따라 자동차의 기본 가격을 반환
    private Integer car(String color, String type) {
        int price;
        
        switch (type.toLowerCase()) {
            case "suv":
                price = 30000;
                break;
            case "sedan":
                price = 20000;
                break;
            case "coupe":
                price = 25000;
                break;
            default:
                price = 15000;
                break;
        }

        // 색상에 따라 추가 비용이 발생
        switch (color.toLowerCase()) {
            case "red":
                price += 1000;
                break;
            case "black":
                price += 500;
                break;
            // 다른 색상에는 추가 비용이 없음
        }

        return price;
    }

    // 이 메서드는 색상만 주어질 경우 기본 유형인 "sedan"의 가격을 반환
    private Integer car(String color) {
        return car(color, "sedan");
    }

    public static void main(String[] args) {
        Car myCar = new Car();
        
        // SUV 타입의 빨간색 자동차 가격을 계산
        Integer suvPrice = myCar.car("red", "suv");
        System.out.println("SUV의 빨간색 자동차 가격: " + suvPrice);
        
        // 기본 유형인 sedan의 검은색 자동차 가격을 계산
        Integer sedanPrice = myCar.car("black");
        System.out.println("Sedan의 검은색 자동차 가격: " + sedanPrice);
    }
}

https://dev-emma-dev.tistory.com/28

 

[ Java ] Overloading 개념과 예시

: 메소드 이름을 같게하고, 파라미터(타입, 순서, 개수)를 달리해 여러 메소드를 정의하는 방법  제가 예시 코드를 만들어보았어요 car라는 이름의 메소드는 파라미터를 색상만 갖는 경우, 색상

dev-emma-dev.tistory.com

 

 

이 코드에 SOLID 원칙을 적용해서 수정해보도록할게요!

 

 

// SRP: Car 클래스는 자동차에 관련된 속성과 메서드만 담당합니다.
public class Car {
    private String color;
    private String type;
    private int basePrice;

    public Car(String color, String type, int basePrice) {
        this.color = color;
        this.type = type;
        this.basePrice = basePrice;
    }

    public String getColor() {
        return color;
    }

    public String getType() {
        return type;
    }

    public int getBasePrice() {
        return basePrice;
    }
}

// OCP: 확장을 위해 CarPriceCalculator 인터페이스를 사용합니다.
interface CarPriceCalculator {
    int calculatePrice(Car car);
}

// 구체적인 가격 계산 로직을 위한 기본 구현체
class BasicCarPriceCalculator implements CarPriceCalculator {
    @Override
    public int calculatePrice(Car car) {
        int price = car.getBasePrice();
        
        switch (car.getColor().toLowerCase()) {
            case "red":
                price += 1000;
                break;
            case "black":
                price += 500;
                break;
        }
        return price;
    }
}

// LSP: SpecialCarPriceCalculator도 CarPriceCalculator의 일종입니다.
class SpecialCarPriceCalculator extends BasicCarPriceCalculator {
    @Override
    public int calculatePrice(Car car) {
        int price = super.calculatePrice(car);

        // 추가적인 가격 계산 로직
        if ("special".equalsIgnoreCase(car.getType())) {
            price += 2000;
        }
        return price;
    }
}

// ISP: CarService는 필요한 메서드만 포함합니다.
interface CarService {
    int getCarPrice(Car car);
}

class CarPriceService implements CarService {
    private CarPriceCalculator calculator;

    // DIP: CarPriceService는 추상화된 CarPriceCalculator에 의존합니다.
    public CarPriceService(CarPriceCalculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int getCarPrice(Car car) {
        return calculator.calculatePrice(car);
    }
}

public class Main {
    public static void main(String[] args) {
        Car suv = new Car("red", "suv", 30000);
        Car sedan = new Car("black", "sedan", 20000);
        
        CarPriceCalculator basicCalculator = new BasicCarPriceCalculator();
        CarPriceCalculator specialCalculator = new SpecialCarPriceCalculator();
        
        CarService basicService = new CarPriceService(basicCalculator);
        CarService specialService = new CarPriceService(specialCalculator);
        
        System.out.println("SUV의 빨간색 자동차 가격: " + basicService.getCarPrice(suv));
        System.out.println("Sedan의 검은색 자동차 가격: " + specialService.getCarPrice(sedan));
    }
}

 

 

코드 설명과 함께 개념에 대해 적어보겠습니다

 

 

 

 

SRP 단일책임의 원칙 Single Responsibility Principle

 : 클래스는 하나의 책임만 가져야한다

 예를 들어 어떠한 물체가 움직임도 갖고 음성을 갖는다고 하면 
이를 별도의 기능으로 만들고 별도의 컴포넌트로 오브젝트에 부착한다
움직임만 수정하고싶을 때 움직임 부분의 코드만 수정하면 될 수 있도록!
이런 단위로 분리해 사용하면 코드 가독성이 높아지며, 상속을 받아 사용하면 확장성도 좋아진다. 그러니 모듈식으로 여러부분에서 재사용성이 좋아진다.

코드 적용

→ Car 클래스는 자동차의 속성만을 담당하며, 가격 계산은 CarPriceCalculator 인터페이스와 그 구현체들에 의해 처리됩니다.

 

 

OCP 개방폐쇄의 원칙 Open Close Principle

: 클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야한다.

 예를 들어 도형의 넓이를 구한다고 했을 때 도형별로 개별 함수가 존재해 매개변수를 각각 바꿔야 하는 대신 공용으로 쓸 수 있도록하여 계속 기능을 추가할수있도록 해야한다.
도형별로 개별함수가 존재해서 매개변수를 바꿔야하는데 이렇게 말고 공용으로 쓸 수 있게 설계해야한다.
사각형, 원이 아니라 모양이라는 추상 클래스로 만들고 오버라이드를 사용하면 된다. 
차이는 도형이 추가되도 기존코드는 수정되지않고 새로운 클래스만 추가해도된다.

코드 적용

→ CarPriceCalculator 인터페이스를 사용하여 새로운 가격 계산 로직을 추가할 때 기존 코드를 수정할 필요 없이 새로운 클래스를 추가할 수 있습니다.

 

 

LSP 리스코브 치환의 원칙 The Liskov Substitution Principle

: 리스코브라는 사람이 만들어서 이름을 따옴. 상속 할 때 지켜야 하는 원칙으로 파생 클래스가 기본 클래스를 대체할 수 있어야 한다.

 예를 들어 자동차,버스,기차 라는 교통수단이라는 클래스가 좌회전, 우회전 이라는 메소드이 있을 때
자동차나 버스 등은 다 이 기능이 적용되지만 기차의 경우는 오류가 발생한다.
좀 더 추상클래스를 간단하고 분류해서 만들어
상속하기보다 인터페이스를 이용해 '도로를 달리는 것', '레일을 달리는 것' 으로 나누는 것이다. 
하위클래스는 어떤 경우에도 부모클래스를 대체할 수 있어야한다.

코드 적용

→ SpecialCarPriceCalculator는 BasicCarPriceCalculator를 확장하여 특별한 가격 계산 로직을 추가합니다.

 

 

ISP 인터페이스 분리의 원칙 Interface Sergregation Princicle

: 인터페이스는 한 번에 크게 사용하지 말고 작은 단위로 나누어 사용해야한다.

 한 번에 모든 것들을 넣지말고 최대한 나눠,,, 이들을 조합해서 사용한다.
분리해서 쓸수록 코드간의 결합도가 낮아지고 수정이 용이해진다.

코드 적용

→ CarService 인터페이스는 가격 계산에 필요한 메서드만 포함합니다.

 

 

DIP 의존성역전의 원칙 Dependency Inversion Principle

: 고수준 모듈은 저소준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야한다.

 door 클래스에 open과 close 기능이 존재한다고 할 때, 
switch클래스에서 door 을 열고 닫게 만들고, 여기 toggle을 만들어서 열고 닫게 할수잇는데
switch 클래스는 door를 직접적으로 알고있어 door만 열고 닫을 수 있다는 단점이있다.
switch는 door만 열고 닫는건 아니고, lignt 등 이외의 것들도 확성화 비활성화 할수잇어야한다.
그러므로 switch를 인터페이스로만들고 이 인터페이스에 활성 비활성화 함수를 생성하여
door에 이 인터페이스를 쓰면 기존 switch 클래스를 door에 직접 연결하지않고 인터페이스를 통해 연결하고
toggle에서 인터페이스 메소드를 사용하면된다.

코드 적용

→ CarPriceService는 CarPriceCalculator 인터페이스에 의존하여 구체적인 구현체와 분리됩니다.

 

 

 

 

 

 

 

 

 

 

 

반응형

'Chapter01 > Java' 카테고리의 다른 글

[Java] Stack / Queue 사용예시  (2) 2025.03.07
[ Java ] 이중해시맵 + 예시코드  (0) 2024.06.17
[ java ] Stream_예시코드  (0) 2024.05.28
[ Java ] 클래스 정의  (0) 2024.03.13
[ Java ] Overloading 개념 + 예시코드  (1) 2023.11.07