객체 지향 설계 5대 원칙 중 하나인 SRP (Single Responsibility Principal)
에 대해서 정리해보고자 한다.
어떤 클래스를 변경해야 하는 이유는 오직 하나 뿐이어야 한다.
- 로버트 C. 마틴
SRP
는 단일 책임 원칙이라 불리우며, 말 그대로 하나의 책임을 가져야 한다는 원칙이다.
한 클래스는 하나의 책임만 가져야 한다.
- 위키백과, SOLID (객체 지향 설계)
모든 클래스는 하나의 책임만을 가지며, 클래스는 그 책임을 완전히 캡슐화 해야 한다.
- 위키백과, 단일 책임 원칙
간단하게 말하면, 역할에 맞는 한 가지 일만 똑바로 하자!
라고 생각하면 된다.
그렇다면, 왜 역할에 맞는 하나의 책임만을 가지게 하려고 하는 걸까?
이는 변화에 보다 유연한 대응을 할 수 있게 만들어주기 때문이다. 이를 반대로 말하면 해당 역할을 제외한 다른 변화에 대해서는 신경쓸 필요가 없다는 말과 같다고 볼 수 있기 때문이다. 즉, 변경해야 하는 이유를 제한함으로써 변화할 여지를 줄인다고 생각할 수 있다.
이해를 돕기 위해 적절한 예시를 들어보자(이해를 돕기 위한 예시이므로 오류가 있을 수 있다).
자, 여기에 연필이 있다. 연필은 기본적으로 글 쓰기
를 할 수 있다. 만약 여기서, 지우개가 달려있어 썼던 글자를 지울 수 있는 연필이라고 한다면 다음과 같을 것이다.
하지만 뭔가 이상하지 않은가? 정확하게 글자를 쓰는 것과 지우는 역할을 수행하는게 어디인지 생각해보자. 글자를 쓰는 건 연필
이 하는 일이다(엄밀히 따지면 연필에 달려있는 연필심
이 되겠지만). 그리고 글자를 지우는 건 연필에 달려있는 지우개
가 하는 일이다. 이는 각자의 본분에 충실하지 못한 것으로, 지우개의 기능에 변화가 생길 때, 연필도 같이 변경되는 결과를 얻게 된다. 그러니까 현재, 연필은 쓰기
와 지우기
라는 두 개의 역할에 대한 책임을 갖고 있는 것이다. 게다가 현재 연필은 연필이 할 수 있는 글자를 쓰는 기능
과 아무 상관도 없는 글자를 지우는
기능을 위해 지우개의 이름도 알아야 한다...
만약 이 상태에서 지우는 기능에 변화를 줘야한다고 가정해보자. 그러면 지우개
를 변경하는 것이 아닌 연필
이라는 엉뚱한 곳에서 변화가 생기게 된다. 글자를 지우는 책임
은 지우개한테 있는데 말이다!
이를 조금 더 극단적이게 확장해서, 지우개에 모터를 달아서(?)
엄청나게 고속으로 글자를 지울 수 있게 되었다고 해보자. 그러면 다음과 같이 변할 것이다.
분명 지우개에 모터를 달았을텐데, 연필에 모터가 달려있는 이상한 모양새가 되어버렸다(오류가 있을지 모르나 극단적인 예시를 들기위해 이렇게 표현했다). 이렇게 된건 순전히 연필이 지우개의 지우기
기능을 수행하는 역할까지 하고 있기 때문이다. 심지어, 원래 연필 객체를 만들기 위해서는 이름과 지우개만 필요했는데 이제는 모터까지 필요해졌다! 순수하게 연필의 쓰기 기능 자체에는 모터가 필요없는데도! 연필은 이제 본인과 아무 상관도 없는 지우기 기능을 위해 굳이 모터까지 장착해야 한다!
여기서는 간단히 main
메서드 내부에 빨간줄이 그어진 부분만 변경하면 되지만 실제로 이런 상황이 발생하면 변경해야하는 코드의 숫자는 기하급수적으로 많아질 것이다. 쓰기, 지우기 기능에 변화가 생길 때마다 연관되어 있는 것들은 싹 다 바꿔야 할테니까 말이다. 여기서 모터가 돌아가는 것까지 연필의 책임이 된다면 글쎄... 😅
연필은 연필의 역할이 있고 지우개는 지우개의 역할이 있다. 그리고 모터에게는 모터만의 역할이 있다. 그리고 우리는 이들을 구현할 때 각각의 역할에 맞게 책임을 분배해줘야만 한다. 그러니까, 지우개가 변경되는데 연필을 변경해선 안되고 연필이 변경되는데 지우개가 변경되서는 안된다는 뜻이다. 연필이 글을 쓰는데 굳이 지우개까지 동원할 필요는 없지 않은가?
따라서 책임을 적절히 분배하면 다음과 같은 모양새가 되지않을까 싶다.
우리는 바로 위 사진속의 연필에 주목해야 한다. 연필은 글자를 지운다.
라는 기능에 대한 책임에서 벗어남으로써 가장 처음과 똑같이 이름과 지우개만 있으면 된다. 이는 곧 글자를 지우는 기능
에 다양한 변화가 발생해도 연필이 연필로써의 역할을 수행하는데에 아무런 영향을 주지 않는 것을 의미한다. 게다가, 연필은 지우개가 글자를 지우는
행위를 어떻게 수행하는지 알 필요가 전혀 없어진다. 즉, 지우개와 연필은 각각의 책임을 캡슐화하게 된다.
이는 클래스 뿐만 아니라 메서드 등에도 모두 적용되어야 한다. 단순히 연필로 글자를 쓰는 기능에 샤샤샥하는 소리가 나는 기능을 추가한다고 생각해보자. 만약 write()
메서드에서 소리를 내는 기능까지 책임을 갖고 있다면? 당장에는 write()
메서드에 변화를 주면 될지 모르나, 추후엔 해당 메서드와 연관된 다른 기능들까지 전부 변경해야 할 것이다.
따라서, 역시 이 경우에도 역할에 맞게끔 책임을 분리해줘야 한다.
글자를 쓰는 기능은 write()
에서, 소리를 내는 기능은 makeSound()
에서 담당함으로써 서로가 서로에게 미치는 영향력이 줄어들게 될 것이며 변화에 유연하게 대응할 수 있을 것이다.
여기서는 객체에 대해서 이야기 했지만, 메서드에도 동일하게 적용할 수 있다. 하나의 메서드가 2가지 이상의 역할을 수행한다면, 2가지 이상의 이유로 수정될 수 있다는 것을 의미하는 것이다.
당장, 바로 위의 write()
메서드와 makeSound()
메서드의 기능을 합한 메서드를 만들고 수정을 시도해보자. 그러면 이 말의 의미를 이해하기 좀 더 쉬울 것이다.
역할에 맞는 적절한 책임의 분배는 변화에 유연하게 대응할 수 있게 해준다.