CS/OOP

OOP - SOLID 알아보기

쪼멘탈 2022. 9. 8. 23:39
반응형

SOLID 알아보기

 

개발과정에서 SOLID를 왜 지켜야 할까?

SOLID 원칙을 지킨다면 개발과정에서 유지보수에 용이하며 확장에 쉽도록 프로그래밍할 수 있도록 도움을 주기 때문이다.

 

 

SOLID

 

  • S  - Single Responsibility Principle 단일 책임의 원칙
  • O  - Open Close Principle 개방 폐쇄의 원칙
  • L   - Liskov Substitutuin Principle 리스코프 치환의 법칙
  • I - Interface Segregation Principle 인터페이스 분리 원칙
  • D - Dependency Inversion Principle 의존관계 역전 원칙

 

 

단일 책임의 원칙 (SRP)

하나의 클래스는 하나의 책임만을(기능만을) 가지고 있어야 한다는 프로그래밍 원칙이다. A Class안에 여러 기능이 들어가 있는 상태로 개발이 진행되었다고 가정해 보자. 이 상태에서 문제가 발생했다는 가정을 들어보자 1. A Class에서 오류가 발생했을 때와 2. A Class안에 로직을 수정한 후 테스트를 돌려보다가 문제가 발생한다면 어떤 기능이 문제를 일으켰는지 한 번에 명확한 확인이 어렵다. 이런 문제를 줄이기 위해서 하나의 Class안에는 하나의 기능만을 가지고 있어야 한다. 즉, Class가 제공하는 서비스는 단 하나의 책임만을 가져야 한다.

 

 

개방 폐쇄의 원칙 (OCP)

클래스와 함수에 확장에 관해서는 열려 있지만 수정에 대해서는 닫혀 있어야 하는 프로그래밍 원칙이다. 이 말을 이해하기 위해선 코드를 직접 보는 것이 더 빠르다.(아래 코드 참고) 개방 폐쇄의 원칙은 코드를 수정할 때 기존의 코드를 수정하지 않고도 작동하던 기능을 오류 없이 수행이 가능해야 한다. 즉, 각 객체마다 결합성은 낮추고 응집성은 높인 것으로 코드 변경에 따른 사이드 임팩트를 최대한 줄이기 위한 프로그래밍 원칙이다. 객체지향에서는 다형성을 활용하는 방법이 있다. 아래 예제 코드를 보면서 확인해 보자

 

public class Cry {
    
    public void howCry(){
        String animal = whatAnimal("dog");

        switch (animal){
            case "dog":
                System.out.println("멍멍");
                break;
            case "cat":
                System.out.println("야옹");
        }

    }
    public String whatAnimal(String type){
        return type;
    }
}

위와 같은 코드에서 dog와 cat을 말고 다른 동물들을 추가할 경우 추가를 할 때마다 코드를 몇 번씩 수정해야 할 수도 있다.

(수정하지 않는다면 오류가 발생할 수 있다.) 이런 문제를 해결하기 위해 아래와 같은 코드로 작성하는 것이 더 좋다. 

 

public interface Cry {
    void cry();
}
public class Dog implements Cry{
    @Override
    public void cry() {
        System.out.println("멍멍");
    }
}
public class Cow implements Cry{
    @Override
    public void cry() {
        System.out.println("음메~");
    }
}

 

동물을 추가하고 싶을 때 동물 클래스를 추가로 만들어 주고 Cry 인터페이스를 implements 받아 해당 원하는 기능을 구현한다면 기존 다른 클래스에 구현되어 있는 코드를 수정하지 않고도 확장이 가능하다. 

 

리스코프 치환의 원칙 (LSP)

상위 타입의 객체를 사용하다가 상속받은 하위 타입의 객체로 치환해도 기능이 정상 작동할 수 있어야 하는 프로그래밍 원칙이다. 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다. 다형성의 특징을 유지하기 위해서 이 원칙이 사용된다. (인터페이스를 구현한 구현체를 믿고 사용하려고) 자동차 인터페이스에서 엑셀을 구현했을 경우 엑셀을 호출 시, 속력이 높아져야 하지 감소되면 안 된다. 하지만 속력을 높이지 않는 기능을 구현하여도 컴파일 오류는 발생하지 않는다. 이런 문제가 리스코프 치환의 원칙 위배이다.

 

 

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

인터페이스의 특징은 해당 인터페이스에 안에 선언되어 있는 모든 메소드를 확장하는 클래스 안에 구현해 주어야 하는데 이럴 때 하나의 인터페이스 안에 너무 많은 기능이 들어있다면 확장하는 클래스 안에 사용하지 않는 기능들 까지도 구현해야 하는 문제가 생길 수 있다. 이런 문제를 해결하기 위해서 인터페이스를 분리해주어야 한다. (특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스보다 낫다.) 

public interface Vehicle {
    void drive();
    void turnLeft();
    void turnRight();

    void steer();
    void steerLeft();
    void steerRight();

}

위와 같은 인터페이스를 만들었을 때 자동차와 보트로 나눠 기능을 구현한다면 사용하지 않는 기능까지 기능을 강제로 구현해야 한다. 이런 경우를 방지하기 위해 인터페이스를 너무 큰 단위로 만들지 않고 작은 단위로 분리한다.

 

public interface Car {
    void drive();
    void turnLeft();
    void turnRight();
}
public interface Boat {
    void steer();
    void steerLeft();
    void steerRight();
}

위와 같이 인터페이스를 분리해 준다면 인터페이스가 더 명확해지고, 대체 가능성이 높아진다.

 

의존 역전의 원칙 (DIP)

의존 관계를 맺을 때는 좀 더 상위 개념으로 하는 것이 좋다. 추상화에 의존해야 하며, 구체화에 의존하면 안 된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이며 쉽게 말하자면 구현 클래스에 의존하지 말고, 인터페이스에 의존해야 한다는 뜻이다. 인터페이스에 의존해야지 유연하게 구현체를 변경할 수 있다. (구현체에 의존할 경우 수정이 어렵다.)

 

 

정리

모든 설계에 역할과 구현을 분리하여 유연한 변경을 할 수 있도록 구현하는 것이 좋은 객체 지향 설계이다.
반응형