[OOP] 단일 책임 원칙(SRP: Single Responsiblity Principle) 개념 및 예제
객체 지향 설계 원칙(SOLID)
객체 지향 언어의 등장 이후 수많은 시행착오와 베스트 프랙티스 속에서 객체 지향 설계 5가지 원칙이 등장했는데, 바로 SOLID다. SOLID는 로버트 C. 마틴(Robert C. Martin)이 2000년대 초반 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙으로 제시한 것을 마이클 페더스(Michael Feathers)가 두문자어로 소개한 것이다.
SOLID는 다음 5가지 원칙의 앞 글자를 따서 부르는 이름이다.
- SRP(Single Responsiblity Principle) : 단일 책임 원칙
- OCP(Open Closed Principle) : 개방 폐쇄 원칙
- LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
- ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle) : 의존 역전 원칙
이 원칙들은 응집도는 높이고(High Cohesion), 결합도는 낮추라(Loose Coupling)는 고전 원칙을 객체지향의 관점에서 재정립한 것이다.
단일 책임 원칙(SRP: Single Responsiblity Principle)
“어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.”
- 로버트 C. 마틴 -
다음 그림과 같이 남자라고 하는 클래스와 남자 클래스에 의존하는 다양한 클래스가 있다고 생각해 보자.
- 이 경우 남자 클래스에는 수많은 역할과 책임을 지니게 된다.
- 남자 클래스는 누군가의 여자친구의 역할과 책임을 다하는 동시에, 아들, 사원, 소대원 역할과 책임까지 한 번에 지니게 된다.
- 이런 경우 역할과 책임을 분리하라는 것이 단일 책임 원칙이다.
다음은 남자라는 하나의 클래스가 역할과 책임에 따라 네 개의 클래스로 분리된 그림이다.
- 역할에 따라 클래스 이름이 정해져 이해하기 좋아졌다.
- 남자친구는 여자친구와 이별하더라도 어머니 - 아들 관계, 직장 상사 - 사원 관계, 소대장 - 소대원 관계는 아무런 영향도 받지 않게 된다.
단일 책임 원칙은 클래스 분할뿐만 아니라 속성, 메서드, 패키지, 모듈, 컴포넌트, 프레임워크 등에도 적용할 수 있는 개념이다.
속성이 SRP를 지키지 않은 경우
class Person {
String name;
String age;
String armyNumber;
// ...
}
- Person 클래스는 이름과 나이, 군번 등의 속성을 가지고 있다.
Person romeo = new Person();
Person juliet = new Person();
juliet.armyNumber = "1573042009"; // ?
- Person 클래스를 통해 로미오와 줄리엣 인스턴스를 생성한다고 가정할 때 줄리엣이 가진 armyNumber라는 속성에 값을 할당하거나 읽어오는 코드를 제제할 방법이 없다.
이 경우 Person 클래스를 Man과 Woman 클래스로 분할하고 Man 클래스에만 armyNumber 속성을 갖게 하는 식으로 리팩토링할 수 있다.
class Person {
String name;
String age;
// ...
}
class Man extends Person {
String armyNumber;
// ...
}
class Woman extends Person {
// ...
}
Man romeo = new Man();
Woman juliet = new Woman();
- 만약 Man 클래스와 Woman 클래스에 공통점이 없다면 Person 클래스는 제거해도 된다.
메서드가 SRP를 지키지 않은 경우
이번에는 메서드가 SRP를 지키지 못하는 경우로 Dog 클래스를 만들고 소변을 보는 행위인 toUrinate() 메서드를 구현했다고 가정하자.
public class Dog {
final static Boolean male = true;
final static Boolean female = false;
Boolean gender;
void toUrinate() {
if (this.gender == male) {
// 한쪽 다리를 들고 소변을 본다.
} else {
// 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
}
}
}
- 강아지가 수컷이냐 암컷이냐에 따라 toUrinate() 메서드에서 분기 처리가 진행된다.
- 강아지 클래스의 toUrinate() 메서드가 수컷 강아지의 행위와 암컷 강아지의 행위를 모두 구현하려고 하기에 단일 책임 원칙을 위배하고 있다.
- 메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 것이 분기 처리를 위한 if 문이다.
위 코드를 다음과 같이 리팩토링할 수 있다.
abstract class Dog {
abstract void toUrinate();
}
class MaleDog extends Dog {
@Override
void toUrinate() {
// 한쪽 다리를 들고 소변을 본다.
}
}
class FemaleDog extends Dog {
@Override
void toUrinate() {
// 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
}
}
단일 책임 원칙과 추상화
단일 책임 원칙과 가장 관계가 깊은 객체 지향 4대 특성은 추상화임을 알 수 있다. 애플리케이션의 경계를 정하고 추상화를 통해 클래스들을 선별하고 속성과 메서드를 설계할 때 반드시 단일 책임 원칙을 고려하는 습관을 들이자. 또한 리팩토링을 통해 코드를 개선할 때도 단일 책임 원칙을 적용할 수 있는 곳이 있는지 꼼꼼히 살피자.
참고 서적
https://link.coupang.com/a/9loXR
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."