본문 바로가기

객체지향

SOLID 원칙

시작하기에 앞서,

SOLID원칙이란, 로버트 마틴이 2000년대 초반에 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙이다.

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

이 원칙에 대해서 하나씩 자세히 알아보자.


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

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 응집도는 높게, 결합도를 낮게 만든다.
  • 변경시 파급 효과가 적다.

이를 예시를 통해 살펴보자.

이전 객체지향의 핵심에서 살펴봤던 예제를 다시 한번 더 사용해보려한다.

우리는 게임을 하고 있는데 전사와 마법사를 선택하여 게임을 진행할 수 있다.

이 경우에 다음 그림과 같이 직업이라는 한 객체가 전사와 마법사의 역할을 모두 수행하게 만들 수 있다.

public class Character {
    String position;

    public Character(String position) {
        this.position = position;
    }

    public String attack() {
        if (position == "전사") {
            return "전사의 묵직한 한방!";
        } else {
            return "마법사의 묵직한 한방!";
        }
    }
}

이 클래스는 하나의 전사와 마법사의 책임을 수행해야하고 이 말인 즉슨 두 가지의 책임을 가지고 있다는 것이다.

이 클래스를 보면 두 개의 책임(직업)을 맡고 있어 응집도가 낮은 것을 볼 수 있고 다른 책임(직업)과 position이라는 변수를 공유하고 있는 것처럼 의존, 연관이 존재하여 결합도가 높다는 것을 볼 수 있다.

그렇다면 새로운 직업이 계속 생길 때, 이 클래스는 변경과 삭제가 수시로 발생할 것이다.

그렇다면 다음과 같이 책임을 분리해서 구현해보자.

public abstract class Character {
    public abstract String attack();
}

public class Warrior extends Character {
    public String attack() {
        return "전사의 묵직한 한방!";
    }
}

public class Wizard extends Character {
    public String attack() {
        return "마법사의 묵직한 한방!";
    }
}

이 클래스에서는 전사와 마법사의 책임을 분리했다.

각 클래스를 보면 단 하나의 책임(직업)만 맡고 있어 응집도는 높은 것을 볼 수 있고 다른 책임(직업)과의 의존, 연관이 존재하지 않아 결합도가 낮다는 것을 볼 수 있다.

이렇다면 새로운 직업이 계속 생길 때, 이 각각의 클래스에는 변동이 없고 새로운 클래스를 정의하면 될 것이다.

다음과 같이 하나의 클래스는 하나의 책임을 갖도록 구성한다는 것이 SRP이다.


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

  • 소프트웨어 요소는 확장에는 열려 있고 변경에는 닫혀 있어야 한다.
  • 다형성을 적극 활용하자.

이 말을 쉽게 풀어보면 기능을 확장하는데 있어서 코드를 전혀 손대지 않아도 된다는 것이다.

사실 위의 예제에서 바로 볼 수 있다.

우리는 새로운 직업이 생긴다해도 다음과 같이 다른 클래스의 변경 없이 단순히 새로운 직업에 대한 클래스를 추가하기만 하면 된다.

public class Archer extends Character{
    @Override
    public String attack() {
        return "궁수의 묵직한 한방!";
    }
}

다음과 같이 기능을 확장하는데 있어 변경에 닫혀있고 확장에는 열려있는 원칙이 OCP이다.


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

  • 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.
  • 인터페이스 규약을 지켜야한다는 것이다.

예를 들면, 여기서 공격을 하는데 다른 새로운 직업은 이것을 스킬로 구현하면 안된다는 것이다.

우리는 공격을 단순히 기본공격이라고 규약을 맺어놨는데 다른 직업만 따로 스킬을 구현한다면 이것은 리스코프 치환 원칙에 어긋나는 것이다.


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

  • 클라이언트가 자신이 이용하지 않는 메소드에 의존하지 않아야 한다.
  • 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킨다.

우리가 만약 자동차라는 인터페이스가 존재한다면,

운전 인터페이스와 정비 인터페이스로 분리가 가능할 것이다.

이에 따라서 운전자 클라이언트가 운전만 한다면 사용하지 않는 정비에 대한 인터페이스에 의존하지 않을 것이며

마찬가지로 정비사 클라이언트가 정비만 한다면 사용하지 않는 운전에 대한 인터페이스에 의존하지 않을 것이다.

따라서, 최대한 자세하게 쪼갤 수 있으면 쪼개자.


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

  • 상위 계층이 하위 계층의 구현으로부터 독립시킨다.
  • 구현 클래스에 의존하지말고 추상 클래스에 의존한다.

만약 우리가 운전을 한다고 했을 때, 운전 면허가 있다면 어떤 자동차이던지 운전이 가능할 것이다.

그 차가 외제차이든 국산차이든 무슨 차이든 간에 자동차라는 역할에 충실하면 된다.

만약 외제차일 때와 국산차 일때 클라이언트에서 차이가 발생한다면 이는 의존 역전 원칙을 지키고 있지 않은 것이다.

따라서, 구현 클래스(구체적인 것)에 의존하지말고 추상 클래스(포괄적인 것)에 의존하자.


마무리

다음과 같이 SOLID의 원칙에 대해 알아보았다.

우리는 유지 보수가 쉽고 확장성이 좋은 소프트웨어를 만들기 위해 이러한 원칙들을 숙지하고

이러한 원칙들을 적용하는 능력을 키워나가야 할 것이다.

'객체지향' 카테고리의 다른 글

객체지향이란?  (1) 2021.09.13