객체지향 설계(Object Oriented Design, OOD)의 5원칙인 SOLID에 대해서 알아보겠습니다.
SOLID
SOLID는 로버트 C.마틴이 2000년대 초반 객체 지향 프로그래밍 및 설계의 다섯가지 기본원칙으로 제시한 것을 마이클 페더스(Michael Feathers)가 두문자어로 소개한 것이다.
- SRP(Single Responsibility Principle) : 단일 책임 원칙
- OCP(Open Closed Principle) : 개방 폐쇄 원칙
- LSP(Liskov Substitution Principle) : 리스코프 치원 원칙
- ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle) : 의존 역전 원칙
이 원칙들은 응집도는 높이고, 결합도는 낮추라는 고전 원칙을 객체지향의 관점에서 재정립한 것이라고 할 수 있다.
SRP - 단일 책임 원칙
어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C.마틴
단일 책임 원칙은 하나의 책임만 가지도록 하는 것이다.
속성, 메서드, 패키지, 모듈, 프레임워크 등에도 적용할 수 있는 개념이다.
- 클래스의 경우 역할에 따라 분리하여야 한다.
- 하나의 속성이 여러 의미를 가지면 안된다.
- 클래스를 분리시켜서 속성을 사용하는 클래스에만 해당 속성을 갖게한다.
- 메서드의 경우 if문을 통해 분기 처리를 하기보다는 역할을 나누고 각각의 클래스에서 구현하도록 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class 강아지 {
final static Boolean 수컷 = true;
final static Boolean 암컷 = true;
Boolean 성별;
void 소변보다() {
if(this.성별 == 수컷) {
// 한쪽 다리를 들고 소변을 본다.
} else {
// 뒷다리 두개를 굽혀 앉은 자세로 소변을 본다.
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class 강아지 {
abstract void 소변보다()
}
class 수컷강아지 extends 강아지 {
void 소변보다() {
// 한쪽 다리를 들고 소변을 본다.
}
}
class 암컷강아지 extends 강아지 {
void 소변보다() {
// 뒷다리 두개를 굽혀 앉은 자세로 소변을 본다.
}
}
단일 책임원칙을 준수하지 않으면 한 곳에서 오류가 발생했을 때 그 영향이 준수하고난 후보다 크게 미치게 된다. 또한 라인 수가 늘어나게되면서 결함률이 증가하게 된다.
객체지향 4대특성(캡상추다)와 가장 관계가 깊은 것은 모델링 과정을 담당하는 추상화임을 알 수 있다. 애플리케이션의 경계를 정하고, 추상화를 통해 클래스들을 선별하고 속성과 메서드를 설계할 때 단일 책임 원칙을 고려하도록 하자.
OCP - 개방 폐쇄 원칙
소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하지만 변경에 대해서는 닫혀 있어야 한다. - 로버트 C.마틴
위 문장에서 ‘자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀있어야 한다.’ 라는 것을 이끌어낼 수 있다.
상위 클래스 또는 인터페이스를 중간에 두어서 주변이 변화(기능 추가 및 변경)되더라도 사용자는 큰 영향을 받지 않도록 한다. (== 확장성이 높아지도록 한다.)
인터페이스를 이용하면 기능이 추가되더라도 다른 클래스의 변경없이 만들 수 있게 된다.
예시
JDBC
- JDBC를 사용하는 클라이언트는 데이터베이스가 오라클에서 MySQL로 바뀌더라도 Connection을 설정하는 부분 외에는 따로 수정할 필요가 없다.
- 자바 애플리케이션은 JDBC 인터페이스로 인해 변화에 영향을 받지 않는다. 즉, 데이터베이스라는 주변의 변화에 닫혀있는 것이다.
- 데이터베이스를 교체한다는 것은 데이터베이스가 자신의 확장에는 열려있다는 것이다.
자바
- 자바에서는 각 운영체제별 JVM과 목적파일이 있기 때문에 구동되는 운영체제가 바뀌더라도 본인 개발 PC에 설치된 JVM에서 구동하는 코드만 작성하면 된다.
- 개발자가 작성한 소스코드는 운영체제의 변화에 닫혀있고, 각 운영체제별 JVM은 확장에 열려있는 것이다.
개방 폐쇄 원칙을 무시하고 프로그램을 작성하면 유연성, 재사용성, 유지보수성 등 객체 지향 프로그램의 장점을 얻을 수 없다.
LSP - 리스코프 치환 원칙
서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다. - 로버트 C.마틴
1
2
인터페이스 변수1 = new 구현클래스();
상위클래스 변수2 = new 하위클래스();
이러한 상황에서 변수2로 하위클래스의 메서드를 호출할 경우 override된 메소드를 사용할 수 있지만, override되지 않은 하위클래스의 멤버변수와 멤버메소드에 접근할 수 없다. 👉자신의 기반타입으로 교체될 수 없다.
객체 지향의 상속 조건대로 구현된 프로그램이라면 리스코프 치환 원칙을 잘 지키고 있다고 할 수 있다.
객체 지향 상속 조건
- 하위 클래스 is a kind of 상위 클래스 : 하위 분류는 상위 분류의 한 종류이다
- 구현 클래스 is able to 인터페이스 : 구현 분류는 인터페이스 할 수 있어야 한다.
상속이 조직도나 계층도 구조로 구현되면 원칙을 위배한 경우이다.
위에서 설명한 예시처럼 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 한다.
인터페이스는 상속관계에서의 위와같은 문제가 발생할 일이 없어서 가급적이면 인터페이스를 사용해서 구현하는 것이 좋겠다.
ISP - 인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다. - 로버트 C.마틴
ISP는 SRP에서 역할별로 하나의 클래스로 나눠놓은 것들을 인터페이스로 만들고, 이것들을 필요할 때 가져다가 하나의 클래스에서 구현하도록 한다.
SRP와 같은 문제에 대한 또다른 해결책인데, 특별한 경우가 아니라면 SRP가 더 좋은 해결책이다.
빈약한/풍성한 상위클래스
가급적 공통 기능은 상위로 보내는 것이 좋다.
인터페이스 최소주의 원칙
인터페이스는 그 역할에 충실한 최소한의 기능만 공개하라는 것을 명심해야 한다.
DIP - 의존 역전 원칙
고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
자주 변경되는 구체(Concrete) 클래스에 의존하지 마라- 로버트 C.마틴
인터페이스나 상위 클래스에 의존성을 걸어두고 이를 구현하는 하위클래스가 바뀌더라도 영향받지 않게 하는 것이다.
- 상위클래스일수록, 인터페이스일수록, 추상클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상클래스를 통해 의존하라는 것이다.
- 대표적인 사례는 OCP에서 설명했던 JDBC이다.
SoC (Separation of Concerns) - 관심사의 분리
- 관심이 같은 것끼리는 하나의 객체 안으로, 또는 친한 객체로 모으고 관심이 다른 것은 가능한 한 따로 떨어져 서로 영향을 주지 않도록 분리하라는 것이다.
- SoC를 적용하면 자연스럽게 SRP, ISP, OCP에 도달하게 된다.
출처
- 스프링 입문을 위한 자바 객체 지향의 원리와 이해