Hierarchy of Pattern Knowledge
1. 패턴의 지식체계
1) 설계 패턴 : 알고리즘이 변경되어도 영향 받지 않고 진화할 수 있도록 도와주는 패턴
2) OO 설계 원칙
- 변경되는 것을 enpcapsule
- 상속보다는 composition
- interface에 맞춰서 프로그래밍
3) OO 설계 기본 개념
- Abstraction
- Encapsulation
- Polymorphism
- Inheritance
2. Design Smells (설계 악취) : 잘못된 설계에 대한 증상
이름 | 증상 | 원인 | 해결 |
Rigidity (경직성) | 시스템 의존성으로 인해 수정 어려움 (한 모듈의 수정이 다른 모듈의 수정을 야기) |
1. 테스트, 빌드 시간 오래 걸림 2. 작은 변화에도 전체 빌드 필요 |
SOLID 원칙 지키기 Test 병렬 실행 |
Fragility (취약성) | 한 모듈의 수정이 다른 모듈에 영향 (ex. 오작동) |
모듈 간 강한 의존성 | 모듈 간 의존성 제거 |
Immobility (부동성) | 재사용할 수 있는 컴포넌트를 추출하기 어려운 경우 (ex. 로그인 모듈이 특정 DB, 특정 UI 사용하면 다른 시스템에서 재사용 불가) |
해당 컴포넌트와 다른 컴포넌트 간의 강한 의존성 |
결합도 낮추기 |
Viscosity (점착성) | 코드 추가 및 수정시, 기존 설계를 지키는 코드는 그렇지 않은 코드보다 구현 어려움 | 여러 레이어를 거쳐 강한 의존성 개발 환경이 비효율적 |
Dependency는 유지한채로 decoupling |
Needless Complexity (불필요한 복잡성) |
미래의 확장을 고려하여 설계하여 불필요하게 시스템이 복잡해짐 |
현재와 관련 없는 모듈끼리 미래를 위해 불필요한 coupling | 현재 요구사항에 집중 |
Needless Repetition (불필요한 반복) |
복붙을 많이하여 불필요한 코드 반복 많음 |
개발자 게으름 | |
Opacity (불투명성) | 이해하기 어려운 코드 |
3. Dependency Management
- Design Smells은 잘 관리되지 않은 dependency(spaghetti code)로 인해 발생
- dependency 관리를 위해 OO 언어는 다양한 tools 제공
- interface
- Polymorphism
Object-Oriented Design Principles
1. R.C. Martin's Software design principles (SOLID)
SOLID : 로버트 마틴이 명명한 객체 지향 프로그래밍 및 설계의 5가지 기본 원칙
- The Single-Responsibility Principle (SRP) : 단일 책임 원칙
- The Open-Closed Principle (OCP) : 개방-폐쇄 원칙
- The Liskov Substitution Principle (LSP) : 리스코프 치환 원칙
- The Interface Segregation Principle (ISP) : 인터페이스 분리 원칙
- The Dependency Inversion Principle (DIP) : 의존관계 역전 원칙
2. SRP
클래스는 단 한 가지의 변경 이유(책임)만 가지고 있어야 한다.
※ 책임 (Responsibility) : 클래스의 의무, 변경해야할 이유
- 책임이 많을 수록 변경 가능성이 높음
- 더 많은 변경이 있으면 버그 가능성 높음
- 변경은 다른 부분에도 영향을 줄 수 있어 변경하지 않는 것이 좋음
책임이 여러개라면 클래스를 분리해라
- Cohesion : 측정 척도
위반 예제 1
- 정렬을 위해 Comparable을 Implements함
- Worst : Student는 이름과 SSN을 알아야할 의무 외에 정렬 방법도 추가해야함. 정렬로 인해 다른 클래스에 대한 dependency가 생김
- 개선 : SortStudentBySSN 클래스를 별도로 만듦
- 위반예제 2
- Computational Geometry Application은 draw()쓰지 않고 area만 사용.
Graphical Application은 area()쓰지 않고 draw()만 사용
- 1차 개선 : Graphic Rectangle 클래스 분리
- 아직 개선 필요. 처음 모습과 동일하게 Geometric Rectangle이 변경되면 Graphic Rectangle도 변경되면서 Graphical Application도 영향 받음
- 2차 개선 : 각 클래스 분리
각 클래스의 영향도가 Rectangle에 영향을 미치지 않음
- 책임을 식별하자!
- 책임이 동종의 것인지 아예 다른 것(분리)인지 확인해야함
- 파악 방안 : application이 앞으로 어떻게 변화할지 확인 (변화 방향)
- 예시
- Computational Geometry Application은 draw()쓰지 않고 area만 사용.
- 하지만 SRP 무분별하게 적용하면 복잡해지고 관리 cost가 높아지니 주의!
3. OCP
- Software entity(class, module, function 등)이 확장성있게 만들어져야 하지만, 변경에 대해서는 최소화
- 확장성있게 만들되, 수정이 과도하게 일어나서는 안 된다!
- 위반시, Rigidity(경직성) Design Smells 발생
- 위반 예제 1
- HRMgr는 모든 임직원의 임금을 올려주는 함수 존재
- 문제
- 만약 여기서 Engineer라는 emptype이 추가되면 else if문 추가되고 해당 type의 incEngineerSalary 함수 추가되어야함
- 만약 다른 클래스에서 해당 함수를 재사용하고 싶은데 거기서는 Faculty, Staff 타입만 존재한다면?
- 개선 : Polymorphism 이용
- Engineer 추가하면 클래스를 추가하면 됨
- 추상화가 핵심!
- Abstract base class : 고정 (Employee)
- All the possible derived classes : 앞으로 추가될 수 있는 행위 (Staff, Secretary...)
- implementation(concrete class)이 아닌 interface(or abstract class)로 프로그래밍해라
- interface로 모델링하면 변화가 client까지 파급되지 않음
- interface로 모델링하면 변화가 client까지 파급되지 않음
- Polymorphism으로 보통 해결하기 때문에 런타임 cost 지불하고 복잡성이 증가되므로 주의!
- 미리 어떠한 변화가 생길지 모든 case를 예상해서 모든 장치를 만들 필요가 없음.
- 한 번은 희생.. 변화하지 않을 거라 생각하고 개발
- 만약 변화가 생겨서 영향도를 파악하고 여러 번 반복되면 추상화 추가 (TDD)
- 미리 어떠한 변화가 생길지 모든 case를 예상해서 모든 장치를 만들 필요가 없음.
3. LSP
Object는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 변경할 수 있어야 한다.
- Subtypes(Derived classes)은 그들의 base type(base classes)을 대체할 수 있어야 한다
- P의 subtype의 C가 있다면, P타입 객체가 쓸 곳에 C 객체로 대체해도 아무런 영향이 없어야 한다.
- 상속을 쓸지 말지 판단할 수 있는 근거
- Subtyping vs. Implementation Inheritance
- Subtyping(interface inheritance) : IS_A 관계
- Implementation Inheritance(code inheritance) : 타입에는 영향 X
- 대부분 언어에서는 extends하면 위 경우가 동시에 일어남
- 위반 예제 1
- Queue 클래스 구현할 때 기존에 만들었던 List 클래스가 있어서 재사용하기 위해 상속
- 문제 : 다른 개발자가 List 사용하는 곳에 Queue 넣어도 잘 동작하겠네? 라고 생각하지만 실제로 Queue는 List기능보다 더 제한적 (결국, Queue가 List를 대체할 개념이 아님. Queue가 List의 서브타입이 될 수 없기 때문!)
- 해결 : Object Composition(결합)
- 상속관계가 아니라서 LSP를 어기고 있지 않음
- 위반 예제 2
- sql.Date, sql.Time이 util.Date의 일부를 사용하기 위해서 상속함 (단지, 편의를 위해서 활용한 경우)
- 문제 : util.Date 타입의 변수를 sql.Time으로 레퍼런스하고 해당 변수에서 getDate()를 하면 에러 발생(sql.Time에서 deprecated시킴)
- LSP 위반은 다른 Solid 법칙 위반을 이끌 수 있음
- C type은 P type의 서브 타입은 아니지만 일부를 reuse를 하기 위해 단순 상속을 함. C type에는 f method가 없어서 C 타입 객체에서 f method call시 에러 발생 > 개발자가 f method 맨 위에 object 타입 식별을 하여 코드 작성(더 설계가 안 좋아짐)
- f method는 Ptype이 아닌 Ctype에도 종속적이게 되고, 또 다른 타입이 생겼을 때 또 추가해야함 (OCP 만족 X)
4. DIP
추상화에 의존해야지, 구체화에 의존하면 안 된다.
- High-level module은 low-level module에 의존해서는 안 되고, 둘다 abstraction에 의존해야한다.
- Abstractions은 details에 대해 의존하면 안 되지만 details은 abstraction에 의존해야한다.
- dependency 방향의 역전
- High-level resourse : Interface, abstract class
Low-level resourse : concrete class - 반대되는 SASD 구조
- High-level resourse : Interface, abstract class
- Ownership의 역전
- 서비스의 interface는 구현되어 있는 곳이 아닌 client와 같은 package에 있어야함 (client가 ownership을 가져야함)
- 위반 예제
- 3가지 레이어로 구성된 시스템. Policy Layer가 Mechanism Layer을 사용하고..
- 문제 : 하위 수준인 Utility Layer의 변화가 Policy, Mechanism Layer의 변화를 가져옴
- 개선 : interface와 implementation을 분리하고, interface는 사용하는 패키지 쪽으로 ownership을 옮겨줌 (간접적으로 영향을 미치도록)
5. ISP
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
- 클라이언트는 자신이 사용하지 않는 method에 대해 의존성을 강제하면 안 됨
- 잘게 만들어서 꼭 dependency를 가져야하는 Method만 가지도록 해야함
- 클라이언트가 많아지면 interface가 커지게 되어 주의 > cohesive한 그룹끼리 interface를 만들어야함
- 위반 예제
- Roaster Application은 getName, getSSN만 사용하고 Account Application은 getInvoice, postPayment만 사용
- 문제 : Student Enrollment 가 수정되면 두 application에 영향을 미침
- 개선
- Application별로 interface를 분리함
반응형
'💻 개발IT > Design Patterns' 카테고리의 다른 글
3. Behavior 패턴 - State Pattern (0) | 2022.03.01 |
---|---|
3. Behavior 패턴 - Iterator Pattern (0) | 2022.02.28 |
3. Behavior 패턴 - Template Method Pattern (0) | 2022.02.28 |
3. Behavior 패턴 - Observer Pattern (0) | 2022.02.27 |
3. Behavior 패턴 - Strategy Pattern (0) | 2022.02.27 |
2. 객체지향 개념과 설계원칙 - GRASP Principle (0) | 2022.02.24 |
2. 객체지향 개념과 설계 원칙 - 객체지향 패러다임 (0) | 2022.02.07 |
1. Design Patterns (0) | 2022.02.06 |