객체지향 5대 원칙 SOLID, OOP 5대 원칙
각 원칙의 맨 앞 글자를 따서 SOLID 원칙이라고 불립니다.
SOLID는'견고한'이라는 뜻을 가지고 있습니다.
이 다섯 가지 원칙을 지키며 개발을 진행하면 유지보수에 용이한 견고한 코드를 작성할 수 있다는 것을 나타내기도 한다고 합니다.
- SRP (단일책임의 원칙 : Single Responsibility Principle)
- OCP (개방폐쇄의 원칙 : Open Close Principle)
- LSP (리스코프 치환의 원칙 : The Liskov Subsitution Principle)
- ISP (인터페이스 분리의 원칙 : Interface Segregation Principle)
- DIP (의존성 역전의 원칙 : Dependency Inversion Principle)
1. SPR (단일책임의 원칙 : Single Resonsibility Principle)
모든 클래스는 하나의 기능만 가지며 하나의 책임을 수행하는데 집중되어야 하는 것이 SRP가 의미하는 원칙입니다.
쉽게 말해 하나의 클래스로 너무 많은 일을 하지 말고 딱 한 가지 책임만 수행하라는 뜻을 가집니다.
여기서 말하는 한가지 책임의 기준이란?
SOLID 원칙의 창시자 Robert는 단일 책임 원칙에 대해 같이 수정해야될 것들은 묶고 따로 수정해야될 것들은 분리하는 것으로 그 기준을 정했습니다.
책임이 많으면 코드끼리의 결합도를 높일 가능성이 높아집니다.
낮은 결합도(Low Coupling)와 높은 응집도(High Cohesion)를 유지하기 위해
너무 많은 분할로 인해 책임의 파편화가 심할 경우
다시 응집력을 높이는 수정이 필요합니다.(Shoutgun Surgery : 산탄총 수술)
단일 책임 원칙이 적용된 코드라면, 기능 별 코드가 응집되어 있기 때문에 유지보수 시 수정이 매우 효율적입니다.
- 변경에 유연하다.
- 코드가 직관적이므로 이해하기 쉽다.
- 재사용에 용이한 컴포넌트의 기반이다.
산탄총 수술(Shoutgun Surgery) : Move Field와 Move Method를 통해 책임을 기존의 어떤 클래스로 모으거나, 모을 클래스가 없다면 새로운 클래스를 만들어 해결한다. 즉 산탄총처럼 산발적으로 여러 곳에 분산된 책임들을 한 곳으로 모으며 설계를 한다.
즉 응집성을 높이는 작업입니다.
2. OCP(개방 폐쇄의 원칙 : Open Close Principle)
소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 열려있고, 수정에는 닫혀있어야 한다는 원리입니다.
'확장에 열려있다'는 것은 프로그램의 기본 기능을 확장할 수 있다는 것이고,
'수정에 닫혀있다'는 것은 한 번 작성한 코드를 바꾸지 않아도 된다는 것입니다.
이 말은 어떤 클래스 코드를 수정하지 않아도 기존 기능을 확장할 수 있어야 한다는 것입니다.
SOLID 원칙의 창시자 Robert는 OCP를 가능케 하는 중요 메커니즘은 추상화와 다형성이라고 설명하고 있습니다.
적용 방법
- 변경(확장)될 것과 변하지 않을 것을 엄격히 구분한다.
- 두 모듈이 만나는 지점에 인터페이스를 정의한다.
- 구현에 의존하기다 정의한 인터페이스에 의존하도록 코드를 작성한다.
- 확장되는 부분이 인터페이스에 의존하기 때문에 내부 코드를 수정하지 않아도 되게 된다.
3. LSP(리스코프 치환의 원칙 : The Liskov Substitution Principle)
"서브 타입(자식클래스)은 언제나 기반 타입(부모클래스)으로 교체할 수 있어야 한다"
이 원칙의 의미는 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다는 것입니다.
리스코프 치환 원칙에 따르면 자식 클래스의 인스턴스가 부모 클래스의 인스턴스의 행동범위 안에서 행동해야 합니다.
부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 뜻입니다.
여기서 말하는 행동 규약 위반이란?
자식 클래스가 오버라이딩을 할 때 발생할 수 있습니다.
크게 두 가지로 나뉘는데,
첫째는 자식 클래스가 부모 클래스의 변수 타입을 바꾸거나, 메소드의 파라미터 혹은 리턴 값의 타입, 개수를 바꾸는 경우입니다.
둘째는 자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우입니다.
4. ISP(인터페이스 분리의 원칙 : Interface Sergregation Principle)
"자신이 사용하지 않은 인터페이스는 구현하지 말아야 한다."
인터페이스는 추상 클래스 중에서 추상 메소드만 있고 일반 메소드는 없는 것을 말합니다.
인터페이스 분리 원칙의 정의는 클래스가 사용하지 않을 메소드에 의존할 것을 강요하면 안 된다입니다.
여기서 의존이라는 말은 메소드를 가진다는 것으로 보면 됩니다.
클래스가 나중에 사용하지도 않을 메소드를 가지도록 강제하지 말라는 뜻입니다.
추상 클래스를 상속받으면 자식 클래스는 추상 메소드들을 반드시 오버라이딩 해야 합니다.
이때 추상 메소드를 반드시 오버라이딩 해야 하는 것이 바로 메소드를 가지도록 강제하는 것입니다.
'하나의 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다' 라도고 정의할 수 있습니다.
SPR (단일 책임의 원칙)이 클래스의 단일 책임을 강조한다면,
ISP (인터페이스 분리의 원칙)는 인터페이스의 단일 책임을 강조합니다.
SRP의 목포는 클래스 분리를 통해 이뤄지고, ISP는 인터페이스 분리를 통해 이뤄집니다.
결론적으로 인터페이스 분리 원칙은 지나치게 많은 추상 메소드를 가진 거대한 인터페이스 하나를,
관련된 추상 메소드들만 모여있도록 작은 크기의 인터페이스로 분리하라는 뜻입니다.
지나치게 큰 인터페이스는 이를 상속받을 자식 클래스가 필요하지 않은 메소드들을 강제로 오버라이딩하도록 만들기 때문입니다.
서로 관련성이 높은, 적절한 개수의 추상 메소드를 포함하게 될 때 역할 인터페이스라고 불립니다.
역할 인터페이스를 만들어 본인 역할에 맞는 인터페이스를 상속받는 것이 더 효율적이고, 각 클래스의 기능을 쉽게 파악할 수 있는 이점을 얻습니다.
인터페이스 설계에 따라 유연한 구조와 결합도가 낮아지며, 불필요한 컴파일과 재배포를 막을 수 있습니다.
5. DIP(의존성 역전의 원칙 : Dependency Inversion Principle)
"고수준(상위) 모듈은 저수준(하위) 모듈에 의존해선 안된다. 둘 다 추상화에 의존해야 한다."
"추상화는 세부 사항에 의존해선 안된다. 세부 사항은 추상화에 따라 달라진다."
각각의 클래스 또는 모듈 간의 의존성을 끊고 상위 레벨에서 정의한 추상을 하위 레벨 모듈이 구현하게 하는 원칙으로
외부에서 의존성을 주입받아 Low Coupling을 만들게 하는 게 목표입니다.
정리하자면 상위 모듈이 하위 모듈을 사용할 때 직접 인스턴스를 가져다 쓰지 말라는 뜻입니다.
이 경우 하위 모듈의 구체적인 내용에 상위 모듈이 의존하게 되어 하위 모듈에 변화가 있을 때마다
상위 모듈의 코드를 수정해야 하기 때문입니다.
이 문제의 해결 방법은 추상 클래스입니다. 상위 모듈과 하위 모듈 사이에 추상화 레이어를 만드는 것과 같습니다.
추상 클래스 사용 시 상위 모듈에는 추상 클래스의 자식 클래스 인스턴스를 사용한다는 가정 하에, 그 하위 모듈을 사용하는 코드를 작성해두면 되고, 하위 모듈은 추상 클래스의 추상 메소드들을 오버라이딩만 하면 됩니다.
이 경우 상위 모듈은 새로운 하위 모듈이 생겨도 기존 코드를 수정할 필요 없이 새로운 하위 모듈을 자유롭게 가져다 쓸 수 있습니다.
유지보수 측면에서도 용이합니다.
해당 부분은 클래스의 기존 기능을 확장하면서 기존 코드를 수정하지 않아도 되는 상태인 개방 폐쇄 원칙(OCP : Open Close Principle)입니다.
DIP는 클래스의 기존 기능을 확장하면서 기존 코드를 수정하지 않아도 되는 상태인 개방 폐쇄 원칙인 OCP를 지키는 하나의 방법입니다.
이와 같이 SOLID 원칙은 별도의 것이 아니라 서로 긴밀한 관계를 맺습니다.
참고 내용
아래 포스팅에는 각 원칙 별 예제 소스코드도 있기 때문에 참고 시 더욱 이해도를 높일 수 있습니다.
작성 글은 아래의 글과 다른 자료를 참고하여 작성하였습니다.