•
SOLID는 로버트 C. 마틴이 2000년대 초반에 고안한 5가지 원칙을 지칭하는 말로, 5가지 원칙은 아래와 같이 구성되는데, 각 원칙의 앞 글자를 따면 SOLID라는 단어가 만들어지기 때문에 이를 통칭해서 SOLID 원칙이라 부른다.
◦
단일 책임 원칙(Single Responsibility Principle)
◦
개방 폐쇄 원칙(Open-Closed Principle)
◦
리스코프 치환 원칙(Liskov Substitution Principle)
◦
인터페이스 분리 원칙(Interface Segregation Principle)
◦
의존성 역전 원칙(Dependency Inversion Principle)
•
각 원칙은 객체지향 언어에서 좋은 설계를 얻기 위해 지켜야할 규범과 같은 것이며 목표는 소프트웨어의 유지보수성과 확장성을 높이는 것이다.
•
유지보수성은 어떻게하면 확보할 수 있을까? 사람이 유지보수를 하니 가독성을 확보하는 것이 우선일까? 그럴 수 있으나 설계 관점에서 코드의 유지보수성을 판단할 때, 사용 가능한 실무적인 맥락이 몇 가지 있다.
◦
영향 범위: 코드 변경으로 인한 영향 범위가 어떻게 되는가?
◦
의존성: 소프트웨어의 의존성 관리가 제대로 이뤄지고 있는가?
◦
확장성: 쉽게 확장 가능한가?
•
SOLID는 위 질문에 답을 알려주는 원칙이다. SOLID를 따르는 코드는 코드 변경으로 인한 영향 범위를 축소할 수 있고, 의존성을 제대로 관리하며 기능 확장이 쉽다.
1. SOLID 소개
1. 단일 책임 원칙
•
단일 책임 원칙은 클래스에 너무 많은 책임이 할당되어서는 안 되며, 단 하나의 책임만 있어야 한다고 말한다. 클래스는 하나의 책임만 있을때 변경이 쉬워진다.
•
과하게 집중된 책임을 가지는 클래스는 코드를 변경하려고 할 때 문제가 된다. 영향 범위를 알 수 없으니 코드 변경 자체가 어려워지는 것이다.
•
이러한 맥락에서 단일 책임 원칙을 따르라는 말은 클래스가 특정 역할을 달성하는 데만 집중할 수 있게 하라는 의미다. 클래스에 할당된 책임이 하나라면 코드를 이해하는 것도 쉬워진다.
•
즉, 단일 책임 원칙은 변경과 연결된다. 변경으로 인한 영향 범위를 최소화하는 것이 이 원칙의 목적이다.
◦
그래서 단일 책임 원칙을 소개할 때, 클래스는 하나의 책임만을 가져야 한다라는 말 대신 클래스를 변경해야 할 이유는 단 하나여야 한다로 소개되는 것이다.
•
소프트웨어는 복잡계이므로 번번히 들어오는 요구사항 변경을 효율적으로 처리하는 것이 중요하다. 따라서 외부의 변경 요청에도 소프트웨어의 항상성을 유지하는 것이 이 원칙의 가장 큰 목적이다.
◦
그렇다면 가볍게 언급하고 있는 책임이란 무엇일까? 우리는 책임이라는 말에 관해 조금 더 생각해 볼 필요가 있다. 왜냐면 책임이라는 단어의 의미는 사실 지나치게 추상적이기 때문이다.
•
책임이란 무엇이고 이를 어떻게 나눌지 기준이 필요하다. 이 같은 배경에서 SOLID의 창시자 로버트 C. 마틴은 단일 책임 원칙에 다음과 같이 첨언한다.
하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.
•
액터는 메시지를 전달하는 주체다. 그리고 단일 책임 원칙에서 말하는 책임은 액터에 대한 책임이다. 즉 메시지를 요청하는 주체가 누구냐에 따라 책임이 달라지는 것이다.
•
단일 책임 원칙을 이해하려면 시스템에 존재하는 액터를 먼저 이해해야 한다. 그리고 그러기 위한 문맥과 상황이 필요하다.
•
클래스를 변경할 이유는 유일한 액터의 요구사항이 변경될 때로 제한되어야 한다. 이는 최초로 이야기했던 클래스를 변경해야 할 이유는 단 하나여야 한다는 설명과도 이어진다.
단일 책임 원칙의 목표
클래스가 변경됐을 때, 영향을 받는 액터가 하나여야 한다.
클래스를 변경할 이유는 유일한 액터의 요구사항이 변경될 때로 제한되어야 한다.
2. 개방 폐쇄 원칙
•
개방 폐쇄 원칙은 주로 확장에 관한 이야기를 다룬다. 그래서 이 원칙은 확장에는 열려 있고 변경에는 닫혀 있어야 한다라는 말로 표현되기도 한다.
◦
이 원칙의 주된 목적은 기존 코드를 수정하지 않으면서도 확장이 가능한 시스템을 만드는 것이다.
•
기존 코드를 수정하지 않으면서도 확장이 가능한 시스템을 만들어야 하는 이유는 무엇일까? 간단하다. 시스템을 운영하면서 코드를 변경하는 것은 매우 위험한 일이기 때문이다.
•
따라서 코드를 확장하고자 할 때 취할 수 있는 최고의 전략은 기존 코드를 아예 건드리지 않는 것이다.
•
역할에 집중해 간접적으로 구현체를 사용하도록 하면 새로운 요구사항이 들어와도 기존 코드를 크게 변경하지 않고도 요구사항을 처리할 수 있다.
◦
심지어 이렇게 구현하면 요구사항 확장 요청에도 열려있는 코드가 된다.
•
OCP의 목표는 확장하기 쉬우면서 변경으로 인한 영향 범위를 최소화하는 것이다.
◦
이 목표는 소프트웨어 설계에 있어서 매우 중요한 가치로 OCP 원칙은 확장에는 열려 있고 변경에는 닫혀 있다라는 말로 명확하면서도 간결하게 표현된다.
◦
또한 이는 코드를 추상화된 역할에 의존하게 만듦으로써 달성할 수 있다.
3. 리스코프 치환 원칙
•
바바라 리스코프에 의해 고안되어 리스코프 치환 원칙이라 불리는 이 원칙은 한 문장으로 정의하면 기본 클래스의 계약을 파생 클래스가 제대로 치환할 수 있는지 확인하라는 원칙이다.
◦
리스코프 치환 원칙을 위반하는 대표적인 예는 직사각형과 정사각형이다.
•
파생 클래스가 기본 클래스를 대체하려면 기본 클래스에 할당된 의도와 계약이 무엇인지를 먼저 파악할 수 있어야 한다.
•
기본 클래스의 의도를 파악하려면 어떻게 해야할까? 바로 테스트 코드를 사용하는 것이다.
◦
초기 코드 작성자가 생각하는 모든 의도를 테스트 코드로 만들어 두면 파생 클래스를 작성하는 개발자들은 테스트를 기본 클래스의 의도를 파악할 수 있다.
•
인터페이스는 계약이며 테스트는 계약 명세이다.
4. 인터페이스 분리 원칙
•
인터페이스 분리 원칙은 클라이언트가 자신이 사용하지 않는 인터페이스에는 의존하지 않아야 한다는 원칙이다.
•
즉 어떤 클래스가 자신에게 필요하지 않은 인터페이스의 메소드를 구현하거나 의존하지 않아야 한다는 말이다.
◦
이를 통해 인터페이스의 크기를 작게 유지하고 클래스가 필요한 기능에만 집중할 수 있다.
•
이 원칙은 보통 개발자들이 하나의 인터페이스로 모든 것을 해결하려고 할 때 위배된다. 그러므로 이 원칙은 단일 책임 원칙과도 밀접한 관련이 있다.
•
통합된 인터페이스는 구현체에게 불필요한 구현을 강요할 수 있다. 따라서 범용성을 갖춘 하나의 인터페이스를 만드는 것보단 다수의 특화된 인터페이스를 여럿 만드는 편이 낫다.
⇒ 유지보수성 측면에서 그렇다. 무조건 낫다는 것은 아니다.
◦
통합된 인터페이스를 만드는 것이 좋지 않은 이유는 일반적으로 인터페이스가 통합되면 인터페이스의 역할이 두루뭉술해지기 때문이다.
•
여기서 저자가 말하는 비슷한 인터페이스를 하나로 통합해서 관리해서는 안 된다라고 말한다. 그런데 비슷한 인터페이스를 하나로 통합하겠다라는 말을 들었을때는 언뜻 부정적으로 인식되지 않는다.
◦
오히려 개발을 조금 더 공부해본 사람들이라면 인터페이스를 통합하여 응집도를 올리니 오히려 좋은 것이 아닌가라고 반문할 수 있다.
•
실제로 인터페이스를 통합하는 행위는 응집도를 추구하는 행위일 수 있다. 그러나 그것이 곧 응집력이 높아지는 결과로 이어지는 것은 아니다.
◦
응집도라는 개념은 유사한 코드를 한곳에 모은다에서 끝나는 것이 아니기 때문이다. 응집도의 종류는 다양하며 좀 더 세분화된 수준으로 다음과 같은 응집도들이 있다.
▪
기능적 응집도
•
모듈 내의 컴포넌트들이 같은 기능을 수행하도록 설계된 경우를 말한다.
◦
모듈이 어떤 목적을 가지고 있을 때, 컴포넌트들은 그 목적을 달성하기 위해 협력하며 오직 관련된 작업만 수행하는 경우다.
◦
모듈을 구성할 때, 주문이라는 모듈을 만들기보다는 주문 처리라는 모듈을 만들어 컴포넌트를 구성할 수 있을 것이다.
◦
이는 주문이라는 범용적인 도메인을 다루기보다 주문을 처리하는 것만을 목적으로 하는 모듈이다.
•
이럴 때, 기능적 응집도를 추구했다고 할 수 있다.
▪
순차적 응집도
•
모듈 내의 컴포넌트들이 특정한 작업을 수행하기 위해 순차적으로 연결된 경우를 의미한다.
◦
어떤 컴포넌트 출력이 다른 컴포넌트의 입력으로 사용되는 형태를 말한다.
◦
데이터베이스에서 데이터를 조회한 후, 조회 결과를 가공하는 모듈이 있다면 이는 순차적 응집도를 추구하는 것이다.
▪
통신적 응집도
•
모듈 내의 컴포넌트들이 같은 데이터나 정보를 공유하고 상호 작용할 때 이에 따라 모듈을 구성하는 경우를 의미한다.
•
모듈 내 컴포넌트들이 메시지를 주고받는 형태나 공유 데이터에 따라 구성되는 경우다.
•
이메일 전송 모듈을 구성하는 경우, 이메일은 통신에 사용되는 특수한 프로토콜이 있고 데이터희 형식도 발신자, 수신자, 제목, 본문 등으로 어느 정도 정해져있다. 이때 통신적 응집도를 추구했다고 한다.
▪
절차적 응집도
•
모듈 내의 요소들이 단계별 절차를 따라 동작하도록 설계된 경우를 나타낸다.
•
모듈의 요소들이 단계별로 연결되어 전체적인 기능을 수행하는 것이다. 계산기 모듈에서 입력을 받는 단계, 연산을 수행하는 단계, 결과를 출력하는 단계 등으로 구성하는 경우가 대표적이다.
▪
논리적 응집도
•
모듈 내의 요소들이 같은 목적을 달성하기 위해 논리적으로 연관된 경우를 말한다.
•
모듈의 요소들이 서로 관련된 동작을 수행하지만 특정한 순서나 데이터의 공유가 필요하지는 않다.
•
회원 관리 모듈에서 회원 등록, 회원 정보 업데이트, 회원 삭제 등의 작업을 수행하는 요소가 있다면 이는 논리적 응집도를 추구했다는 것을 알 수 있다.
•
일반적으로 기능적 응집도 > 순차적 응집도 > 통신적 응집도 > 절차적 응집도 > 논리적 응집도 순으로 응집도가 높다고 평가한다.
◦
그렇다고 모든 프로젝트에서 이 순서가 보장되는 것은 아니다. 상황에 따라 유기적으로 달라지므로 어떤 응집도가 있고 어떻게 추구할 수 있는지 정도만 알고 있으면 된다.
•
인터페이스 분리 원칙이 추구하는 것은 무엇일까? 클래스 분리 원칙이 아닌 인터페이스 분리 원칙이다. 그리고 인터페이스는 곧 역할이라고 부를 수 있다고 앞서 이야기했다.
•
인터페이스 분리 원칙은 역할과 책임을 분리하고 역할을 세세하게 구분하라는 의미다. 따라서 인터페이스를 분리하라는 말은 기능적 응집도를 추구하는 것으로 볼 수 있다.
5. 의존성 역전 원칙
•
의존성 역전 원칙이란 다음 두 조건을 따르는 것이다.
◦
상위 모듈은 하위 모듈에 의존해서는 안 된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
◦
추상화는 세부 사항에 의존해서는 안 된다. 세부 사항이 추상화에 의존해야 한다.
•
여기서 우리가 얻을 수 있는 포인트들은 다음과 같다.
◦
고수준 모듈은 추상화에 의존해야 한다.
◦
고수준 모듈이 저수준 모듈에 의존해서는 안 된다.
◦
저수준 모듈은 추상화를 구현해야 한다.
•
즉, 의존성 역전 원칙은 고/저수준 모듈 모두 추상화에 의존해야 한다는 원칙이다. 이 말이 와닿지 않을 수 있으니 먼저 의존성에 대해서 이야기 해보자.
2. 의존성
•
의존은 다른 객체나 함수를 사용하는 상태라고 정의할 수 있다.
◦
다시 말해 어떤 객체가 다른 코드를 사용하고 있기만 해도 이를 가리켜 의존하고 있다고 할 수 있다.
•
의존이라는 말을 들었을 때 우리는 통상적으로 두 클래스가 무언가 강하게 연결된 상태를 생각한다. 하지만 의존의 정의는 의외로 단순하고 명확하다. 사용하기만 해도 의존하고 있는 것이다.
◦
때문에 소프트웨어는 의존하는 객체들의 집합이라고 볼 수 있다. 객체지향에서 객체들은 필연적으로 협력하는 데 서로를 사용하기 때문이다. 의존은 소프트웨어 설계의 핵심이다.
•
컴퓨터 공학에는 의존을 표현하는 또 다른 용어가 있는데, 바로 결합이라는 용어다. 의존과 마찬가지로 어떤 객체나 코드를 사용하기만 해도 결합이 생긴다고 할 수 있다.
◦
결합은 결합이 어떻게 되어있느냐에 따라서 강결합으로 평가되기도 약결합으로 평가되기도 한다.
◦
이처럼 결합이 얼마나 강하게 되어있는지를 평가하는 지표가 있는데, 이를 결합도라고 한다. 마찬가지로 결합도는 의존성과 같은 말이다.
•
의존성 자체는 어려운 개념이 아니아. 약한 의존 상태로 만들어두고 유지하는 것은 어렵다. 따라서 우리는 어떻게 객체 간의 의존성을 약화시킬 지 고민해야 한다.
1. 의존성 주입
•
의존성을 약화시키는 기법으로 잘 알려진 의존성 주입은 스프링을 배울 때 특히나 자주 언급되는 개념이다. 의존성 주입은 단순히 말해서 필요한 의존성을 외부에서 넣어주는 것을 의미한다.
◦
의존성 주입에는 생성자 주입, 필드 주입, 세터 주입 등이 존재하며 각각의 장단점을 가진다.
•
의존성 주입이 왜 의존성을 약화시키는 기법일까? 의존하는 형태를 바꿔 의존성을 약화시키기 때문이다.
•
의존성 주입이 시스템에 불필요한 강한 의존을 피하게 해주는데 도움을 준다는 것을 설명에 앞서 다음과 같은 격언들 들어본적이 있을 것이다. new 연산자 사용을 자제하라
◦
왜냐면 new를 사용하는 것이 사실상 하드 코딩이고 강한 의존성을 만드는 대표적인 사례이기 때문이다.
◦
new를 사용하는 순간 구현에 집중할 수 밖에 없게 된다. 따라서 사실상 하드 코딩이 된다고 설명한 것이다.
2. 의존성 역전
•
의존의 방향이 바뀌는 것을 의존성 역전이라고 한다. 추상화를 사용하여 계층을 추가했을 뿐인데, 의존의 방향이 바뀌게 된다.
•
이는 의존성이 가지고 있는 의존성 전이라는 특징 때문에 그렇다. 즉 다시 얘기하면 의존성 역전은 화살표의 방향을 바꾸는 기법이다.
•
의존성 역전을 사용하면 어떻게 될까? 코드가 추상에 의존하는 형태로 바뀐다. 그래서 의존성 역전 원칙은 세부 사항에 의존하지 않고 정책에 의존하도록 코드를 작성하라는 말로 바꿔 작성할 수 있다.
•
이제 의존성 역전이 경계를 만든다라는 말도 이해할 수 있을 것이다. 앞선 개념에서 더 나아가 의존성 역전은 경계를 만드는 기법이며 모듈의 범위를 정하고 상하 관계를 표현하는 데 사용할 수 있는 수단이다.
•
의존성 역전을 통해 생긴 경계는 곧 모듈의 경계로 사용될 수 있다.
3. 의존성 역전과 스프링
•
스프링은 의존성 주입은 제공하지만 의존성 역전 원칙을 준수하지는 않는다.
•
예전에 진행했던 프로젝트의 계층형 구조를 떠올려보자 우리는 의존성 역전 원칙을 준수하며 개발을 진행했을까?
4. 의존성이 강조되는 이유
•
이번 장을 시작하면서 설계 관점에서 유지보수성을 판단할 때 크게 세 가지 맥락이 있다고 했다. 이를 다시 한번 복기해보자.
◦
영향 범위: 코드 변경으로 인한 영향 범위가 어떻게 되는가?
◦
의존성: 소프트웨어에서 의존성 관리가 제대로 이뤄지고 있는가?
◦
확장성: 쉽게 확장 가능한가?
•
지금까지 다룬 내용을 잘 따라왔다면 이제는 이 세 가지 맥락에 문제가 있다고 생각할 때, 적용해볼법한 방법들을 설명할 수 있을 것이다.
◦
영향 범위에 문제가 있다면 응집도를 높이기 위한 방법을 도입해 적절한 모듈화로 단일 책임 원칙을 준수하는 코드를 만든다.
◦
의존성에 문제가 있다면 의존성 주입과 의존성 역전 원칙을 적용해 의존성을 약화시킨다.
◦
확장성에 문제가 있다면 의존성 역전 원칙을 이용해 개방 폐쇄 원칙을 준수하는 코드로 만든다.
•
이런 내용들은 큰 틀에서 유지보수성이 좋은 소프트웨어 설계를 하고 싶다면 코드를 변경하거나 확장할 때 영향받는 범위를 최소화할 수 있어야 한다고 말하고 있다. 그리고 코드의 영향 범위를 최소화하려면 의존성을 잘 다뤄야한다.
◦
변경으로 인한 영향 범위를 축소하는 것이 목표이고, 의존성을 잘 관리하는 것은 이 목표를 달성하기 위한 방법이다.
•
의존성을 잘 다룬다는 것은 무슨 의미일까? 앞서 말했듯 다른 객체나 시스템을 사용한다는 의미고 이는 객체 간의 협력과 시스템 간의 협력이 곧 의존성이라는 뜻이다.
•
따라서 의존성 자체를 없애는 것은 불가능하다. 의존성을 잘 관리한다는 것은 불필요한 의존성을 줄이는 것은 목표로 하지만 없애는 것이 목표는 아니다. 그보다는 의존성을 끊는 것이 목표다.
•
그렇다면 의존성을 끊는다는 것은 뭘까? 이를 이해하려면 의존성이 가지고 있는 특징인 의존성 전이를 먼저 이해해야 한다.
◦
의존성은 컴포넌트 간의 상호작용을 표현하는 것이므로 한 컴포넌트가 변경되거나 영향을 받으면 관련된 다른 컴포넌트에도 영향이 갈 수 있다.
◦
그런데 이렇게 영향을 받은 컴포넌트는 연쇄적으로 또 다른 관련 컴포넌트에 영향을 주는데, 이를 가리켜 의존성이 전이된다고 표현한다.
◦
이때, A → B → C → D 순서와 같이 의존성이 표현된다고 할 때, C가 변경되면 B, A가 차례로 영향을 받는다. 즉 의존성은 화살표 방향의 역방향으로 전이된다.
•
의존성이 전이된다는 특징 때문에 소프트웨어 설계가 중요해진다. 코드 변경에서 자유로워지려면 연결을 최소화하고 어쩔 수 없는 연결마저도 약한 연결 상태로 만들어야 한다.
•
이때 의존성 역전을 사용하면 의존성 전이를 끊을 수 있다. 이는 변경으로 인한 영향 범위를 해당 변경사항이 발생한 컴포넌트로 한정한다.
•
따라서 의존성 역전이 경계를 만드는 기술인 것이다. 코드가 변경되더라도 경계 밖으로는 영향이 가지 않기 때문이다.
•
더불어 소프트웨어 개발과 관련된 격언 중, 의존성에 관한 격언이 하나 있는데, 순환 참조를 만들지 말라라는 격언이다.
◦
이는 의존성은 전이된다는 특징을 이해하면 이해할 수 있는데, 의존성 전이의 영향 범위를 확장시키는 주범이기 때문이다.
◦
순환 참조가 있다는 것은 순환하는 의존성을 갖고 있는 컴포넌트들이 같은 컴포넌트라는 선언과 다를 바 없다. 순환 의존하는 컴포넌트의 영향 범위가 같아지기 때문이다.
◦
순환 참조는 꼭 양방향 참조에서만 일어나는 일이 아니다. A → B → C → A와 같은 상황에서도 일어나는 일이다.
▪
같은 논리로 A, B, C는 사실상 같은 컴포넌트다.
•
순환 참조는 복잡한 의존성 그래프를 유도하고 의존성 전이 범위를 확장시킨다. 의존 그래프 사이에 사이클이 생겨선 안 된다.
•
순환 참조를 만들지 않으려면 양방향 참조를 끊어내고 단방향으로 만들어야 한다. 시스템에 존재하는 모든 의존 방향을 단방향으로 만들어 문제가 발생했을 때, 어느 것이 문제인지 추적 가능하게 해야 한다.
3. SOLID와 객체지향
•
SOLID한 코드는 객체지향적인 코드다라는 문장은 맞는 말일까? 얼추 맞지만 조금 다르다. 엄격하게 말해 SOLID와 객체지향은 추구하는 바가 약간 다르기 때문이다.
•
SOLID 원칙이 추구하는 것은 객체지향 설계다 따라서 SOLID가 추구하는 방향과 객체지향이 추구하는 방향은 조금 다르다.
•
객체지향의 핵심은 역할, 책임, 협력이다. 반면 SOLID는 객체지향 방법론 중 하나로 변경에 유연하고 확장할 수 있는 코드를 만드는 데 초점을 둔다.
◦
쉽게 말해 SOLID는 설계 원칙이고 설계 원칙은 응집도를 높이고 의존성을 낮추는 데 집중한다.
•
따라서 SOLID를 추구하는 것이 곧 객체지향으로 이어지는 것은 아니다. 따라서 SOLID를 무작정 따르기보다 객체지향의 본질인 역할, 책임, 협력을 제대로 이해하고 적절한 구현을 함께 고려해야 한다.
•
SOLID의 목표는 높은 응집도와 낮은 결합도였다. SOLID의 설명을 외우지말고 SOLID가 무엇을 해결하려 했는지 기억하자.
4. 디자인 패턴
•
디자인 패턴은 소프트웨어 설계를 하면서 자주 만나게 되는 문제 상황을 정의하여 이를 해결할 수 있는 모범 설계 사례를 모아놓은 것이다.
•
이 23가지 패턴은 크게 생성, 구조, 행동으로 분류할 수 있다.
◦
생성 패턴은 객체 생성을 유연하고 효율적으로 처리하는 방법을 소개한다.
◦
구조 패턴은 객체를 조합해서 더 큰 구조를 형성하는 방법을 소개한다.
◦
행동 패턴은 객체 간의 행위와 역할을 조정하는 방법을 다루는데, 이를 통해 객체 간의 상호작용르 개선하고 유연성을 높이는 것을 목표로 한다.
•
디자인 패턴이 어떻게 생겼고 예제를 외우려 하는 것은 좋은 공부 방법이 아니다. 어떤 상황에서 어떤 문제를 어떻게 해결하는지 이해하는 것이 좋은 공부 방법이다.
•
도식화된 패턴의 생김새를 외우기보다 의존 그래프를 파악하고 변경으로 인해 영향을 받는 범위가 어디까지인지 확인하며 디자인 패턴 학습에 임해보자.
•
패턴은 ‘문제 인식’, ‘해결 과정’, ‘해결 방법’을 정리한 것이다. 개발자의 업무는 문제를 해결하는 것이지 패턴을 도입하는 것이 아니다. 패턴은 도구일 뿐이다.