SOLID 원칙

2020. 3. 15. 15:49Develop Study

SOLID 원칙

SOLID 원칙이란 객체지향 설계에서 지켜줘야 할 5개의 원칙을 말한다. 이러한 설계원칙을 알아야하는 이유는 시스템에 예쌍하지 못한 변경사항이 발생하더라도, 유연하게 대처하고 이후에 확장성(Scalable)한 시스템 구조를 설계하기 위해서이다. 좋은설계란, 시스템에 새로운 요구사항이나 변경사항이 있을 때, 영향을 받는 범위가 적은 구조를 말한다.

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

객체는 단 하나의 책임(기능)만을 가진다.
객체지향적(Object Oriented)으로 설계할 때는 응집도는 높게, 결합도는 낮게 설계하는 것이 좋다.
* 응집도
* 한 프로그램의 요소가 얼마나 뭉쳐있는지, 구성 요소들 사이의 응집력
* 결합도
* 프로그램 구성요소들 사이가 얼마나 의존적인가

흔히 함수는 하나의 기능만 수행하도록 구현되는 것은 알고들 있을 것이다. calculator()함수가 사칙연산을 모두 한다면 좋은 설계가 아니다. 마찬가지로 Calculator 객체가 있다면, 사칙연산만 하도록 구현해야하지 덧셈, 뺄셈, 곱셈, 나눗셈만 할 수 있어야 한다. 사칙연산에 대한 책임만 가져야할 것이다. 만약, 계산기에 알람을 추가한다고 해서, alarm()함수를 Calculator 기능으로 추가하는 것은 SRP에 위배된다.

다음 그림을 살펴보자.

SRP

하나의 객체에 책임이 많아질수록 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높아진다. 그러니까, 객체마다 책임을 제대로 나누지 않는다면 시스템은 매우 복잡. -> 객체의 하는일에 변경사항이 생기면 이 기능을 사용하는 부분의 코드를 모두 다시 테스트 해야 하기 때문이다.

여러 객체들이 하나의 책임만 갖도록 잘 분배한다면, 시스템에 변화가 생기더라도 그 영향을 최소화 할 수 있음

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

기존의 코드를 변경하지 않으면서(Closed), 기능을 추가할 수 있도록(Open) 설계가 되어야 한다.
확장에 대해서는 개방적이고, 수정에 대해서는 폐쇄적이어야 한다. 이를 만족하는 설계가 되려면, 캡슐화를 통해 여러 객체에서 사용하는 기능을 인터페이스에 정의하는 방법이 있다. 다음 그림을 살펴보자.

OCP

Animal 인터페이스를 구현한 각 클래스들은 울음소리 crying() 함수를 재정의한다. 울음소리를 호출하는 클라이언트는 다음과 같이 인터페이스에서 정의한 crying()만 호출하면 된다.

public class Client{
    public static void main(String args[]){
        Animal cat = new Cat();
        Animal dog = new Dog();

        cat.crying();
        dog.crying();
    }
}

이러한 식으로 encapsulate하면 동물이 추가되었을 때, crying() 함수를 호출하는 부분은 건드릴 필요가 없으면서, 쉽게 확장할 수 있다.

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

자식클래스는 최소한 자신의 부모클래스에서 가능한 행위는 수행할 수 있어야 한다.
즉, 자식클래스는 언제나 부모 클래스의 역할을 대체할 수 있어야 한다. 는 의미이고, 부모클래스와 자식 클래스의 행위가 일관된다는 뜻이다.

자식클래스가 부모클래스를 대체한다라는 말을 생각해보면, 부모의 기능에 대해 Override되지 않도록 하면 된다. 즉, 자식클래스는 부모클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행하도록 하면 된다.

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

자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
하나의 거대한 인터페이스보다는 여러 개의 구체적인 인터페이스가 낫다는 뜻인데... SRP가 객체의 단일책임이라면, ISP는 인터페이스의 단일책임을 의미한다. 백문이 불여일견이므로 예시를 살펴보자.

Phone Interface에는 call(), sms(), alarm(), calculator() 기능이 정의되어 있다. 3G폰과 smartphone은 Phone인터페이스의 기능을 모두 사용하므로, Phone() 인터페이스를 사용하려한다. 그런데 ISP를 만족하려면 Phone 인터페이스에 call(), sms(), alarm(), calculator() 함수를 모두 정의하는 것보다. Call, SmS, Alarm, Calculator 인터페이스를 각각 정의하여, 3G폰과 스마트폰 클래스에서 4개의 인터페이스를 구현하도록 설계해야한다. 그림으로 살펴보자.

ISP Example

이렇게 하면, 각 인터페이스의 메서드들이 서로 영향을 미치지 않게 된다. 다시말해, 자신이 사용하지 않은 메서드에 대해서 영향력이 줄어들게 된다.

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

객체들이 서로 정보를 주고받으면 어쩔수 없이 의존관계가 성립된다. 이 때, 객체들은 나름대로의 원칙을 가지고 정보를 주고받아야 한다. 여기서 나름대로의 원칙이란, 추상성이 낮은 클래스보다 추상성이 높은 클래스와 의존관계를 맺어야한다는 말이다. 일반적으로 Interface를 사용하면 준수할 수 있다. Python을 기준으로 Abstract Class를 활용해야 한다.

'Develop Study' 카테고리의 다른 글

SOLID 원칙 - SRP, OCP, ISP, LSP  (0) 2020.04.05
SOLID 원칙 - DIP(Dependency Inversion Principle)  (0) 2020.03.31
Python 개발 Tip  (0) 2020.03.04