객체지향 프로그래맹 설계 5원칙 SOLID

» java, OOP

객체지향 프로그래맹 설계 5원칙 SOLID

SOLID란 객체 지향 프로그래밍의 5대 원칙으로 각 앞글자를 따서 만들어졌다. SOLID 원칙을 철저히 지키면 시간이 지나도 변경이 용이하고, 유지보수와 확장이 쉬운 소프트웨어를 개발하는데 도움이 되는 것으로 알려져있다.

  1. SRP (Single Responsibility Principle) : 단일 책임 원칙
  2. OCP (Open/Closed Principle) : 개방/폐쇄 원칙
  3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
  4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
  5. DIP (Dependacny Inversion Principle) : 의존관계 역전 원칙

SRP (Single Responsibility Principle, 단일 책임 원칙)

하나의 클래스는 하나의 책임만을 가진다. 이 원칙을 지킴으로서 높은 응집도낮은 결합도를 가질 수 있다.

class Animal {
    public void fly();
    public void run();
}

Animal 클래스를 확인해보자 포유류와 조류의 기능을 모두 하나의 클래스에 설계를 할 경우 조류의 인스턴스가 수정 시 모든 Animal 클래스를 사용한 인스턴스들이 수정이 되어야 한다.
이는 하나의 책임을 가지고 있지 않다.

class Bird {
    public void fly();
}

class Mammal {
    public void run();
}

단 하나의 책임을 가질 수 있도록 Bird와 Mammal 클래스로 구분하였다. 이렇게 하나의 클래스에 하나의 책임만을 가지면 조류의 역할이 수정이 될 때, 포유류는 변경되지 않아도 된다.

또한, 가독성 향상, 유지보수 용이라는 이점까지 누릴 수 있으며 다른 원칙들의 기초가 된다.

OCP (Open/Closed Principle, 개방/폐쇄 원칙)

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

요구사항의 변경이나 추가사항의 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야하며 쉽게 확장이 가능하여 재사용할 수 있어야 한다는 뜻이다.

객체 지향의 특징인 추상화다형성을 이용해 OCP를 가능하게 한다.

interface Car {
	void accel();
	void brake();
}

class Bus implements Car {
	void accel() { /* 속도 10 증가 */ };
	void brake() { /* 속도 5 감소 */ };
}

class Truck implements Car {
	void accel() { /* 속도 5 증가 */ };
	void brake() { /* 속도 3 감소 */ };
}

Bus와 Truck 클래스가 변경이 되어도 Car 클래스에는 영향을 받지 않는다. 반대로 Bus, Trunk외 다른 차량 종류를 인터페이스를 통해서 확장할 수 있다. 이렇게 확장에는 열려있고 변경에는 닫히게 된다.

LSP (Liskov Substitution Principle, 리스코프 치환 원칙)

하위 타입이 상위 타입이 지정한 제약조건들을 지키고, 상위 타입에서 하위 타입으로 변동이 일어나도 상위 타입의 역할을 문제없이 제공해야 한다.
하위 클래스는 상속받은 상위 클래스의 모든 기능이 정상적으로 작동해야 한다.

class Car {
    int speed;

    public void drive() {
        this.speed += 10;
    }
}

class Bus extends Car {
    int speed;
    int km;

    @Override
    public void drive() {
        this.speed += 10; // 부모 기능 그대로 수행
        this.km += 1; // 기능 추가
    }
}

Bus는 Car의 기존의 기능을 그대로 수행하며 LSP 원칙을 지키고 있는 것이다. 리스코프 치환 원칙을 지키기 위해서는 상위 클래스를 상속 하지만 무분별한 오버라이딩을 줄이는 것이다.
상속을 할 때 오버라이드가 필요하다면 기존 상위 클래스의 메소드가 하던 역할을 충실히 수행하고 기능의 추가만 신중하게 수행하면 된다.

ISP (Interface Sergregation Principle, 인터페이스 분리 원칙)

SRP (단일 책임 원칙)가 클래스에 대해서 단일 책임을 갖도록 하는 원칙이었다면, ISP (인터페이스 분리 원칙) 인터페이스를 최대한 변경을 하지 않도록 구체적으로 구분하는 원칙이다.

자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원칙입니다. 다시 말하면, 하나의 큰 인터페이스를 상속 받기 보다는 인터페이스를 구체적이고 작은 단위들로 분리시켜 꼭 필요한 인터페이스만 상속하자는 의미

인터페이스 분리 전

interface Animal {
    public void fly();
    public void run();
}

인터페이스 분리 후

interface Bird {
    public void fly();
}

interface Mammal {
    public void run();
}

이렇게 설계를 하면 조류를 나타내는 클래스를 구현 시 Mammal 인터페이스는 구현하지 않아도 된다.

DIP (Dependancy Inversion Principle, 의존관계 역전 원칙)

구현체보다 인터페이스나 추상 클래스에 의존하는 것이 좋다는 원칙이다.
클래스 사이에는 의존관계가 존재하는데 구체적인 클래스에 의존하지 말고 최대한 추상화한 클래스에 의존하라는 뜻이다.

interface Car {
    public void drive();
    public void radio();
}

class Bus implements Car {
    public void drive() {/* ... */}
    public void radio() {/* ... */}
}

class Truck implements Car {
    public void drive() {/* ... */}
    public void radio() {/* ... */}
}
Car bus = new Bus();
bus.drive();

추상화된 인터페이스를 의존하면 여러가지 구현체를 별다른 코드의 변경없이 사용이 가능하다.
DIP를 통해서 추상화한 인터페이스에 의존하기 때문에 추후 제 3자(ex. 스프링 컨테이너)가 런타임 시에 어떤 구현체를 사용할지 결정해주는 DI를 통해 보다 외부 변동에 유연하게 대체할 수 있는 코드를 작성하는 것이 가능하다.

SOLID 외 다른 원칙

DRY (Don’t Repeat Yourself)

같거나 비슷한 코드를 반복하지 말라는 원칙이다. 코드의 중복을 최소화하여 유지보수성을 향상 시키고, 변경이 필요할 떄 한 곳만 수정하도록 유도한다.

KISS (Keep It Simple, Stupid)

코드를 간단하게 유지하라는 원칙이다. 복잡한 설계보다는 간단하고 이해하기 쉬운 설계를 선호하는 원칙이다.

Law of Demeter (디미터의 법칙)

한 객체는 직접적으로 관련이 있는 객체와만 상호작용해야 한다는 원칙이다.
즉, 객체는 자신이 가지고 있는 메서드를 통해서만 다른 객체와 상호작용해야 한다.

디미터의 법칙은 다른 객체가 어떠한 자료를 갖고 있는지 속사정을 몰라야 한다.
디미터의 법칙은 결합도와 관련된 것이며, 객체의 내부 구조가 외부로 노출되는지에 대한 것이다.