내일배움캠프 19일차 - C++ 문법: 객체지향적 설계, SOLID 원칙
객체지향적 프로그래밍이 중요한 이유:
1) 대부분 라이브러리 및 오픈소스는 객체지향적으로 설계되어 있다.
2) 좋은 설계로 구현된 코드는 개발 시간을 단축할 수 있다.
3) 좋은 설계로 구현된 코드는 기능 변경에 유연하게 대응할 수 있다.
응집도
응집도는 클래스 또는 모듈 내부의 구성요소들이 얼마나 밀접하게 관련되어 있는지를 나타낸다.
응집도가 낮은경우
서로 관련 없는 기능들이 하나의 클래스에 포함된 경우를 두고 '응집도가 낮다'고 한다.
응집도가 낮으면
- 하나의 목적이 아닌 따로 노는 느낌을 주게 된다.
- 하나의 class에 모든 기능이 집중되어 있어 유지 보수가 어렵다.
ex) 피자배달 클래스
1) 피자배달 기능
2) 웹사이트 디자인 기능
3) 회사 마케팅 기능
4) 창고 관리 기능
응집도가 높은 경우
서로 관련 있는 모듈들만 하나의 클래스에 포함되어 있으면 '응집도가 높다'고 한다.
일반적으로 응집도가 높을수록 좋은 설계로 평가된다.
응집도가 높으면
- 기능 변경이 필요할때 해당기능이 담긴 class만 수정하면 된다.
- 관련된 class끼리 정보를 공유하여 코드의 구조가 명확해진다.
ex) 피자배달 클래스
1) 피자 배달 경로 확인
2) 주문한 고객 대응
3) 배달 예상 시간 측정
결합도
모듈 또는 클래스 간의 의존성을 나타낸다.
일반적으로 결합도는 낮을수록 좋은 코드로 본다.
ex) 자동차의 엔진클래스
- 자동차 Class가 디젤 엔진 Class를 직접 포함하고 있다. 이 구조는 아래와 같은 문제점이 있다.
1) 새로운 엔진을 추가하면 자동차 Class도 수정해야한다.
2) 변경이 잦으면 수정 범위가 커지고 유지 보수가 어려워진다.
따라서 아래의 자동차 Class는 한 엔진 클래스에 대한 의존성이 높아 결합도가 높다. (좋지 않음)
- 해당 구조는 아래와 같은 장점이 있다.
1) 자동차 클래스는 엔진 인터페이스만 의존하므로, 새로운 엔진을 추가해도 자동차 코드를 수정하지 않아도 된다.
2) 확장성이 높아지며, 다양한 엔진을 유연하게 지원할 수 있다.
아래의 자동차 Class는 타 Class의 변경에 의한 수정이 필요 없으므로 결합도가 높다. (인터페이스 사용, 특정 클래스에 대한 의존도 낮음, 좋은 코드)
SOLID 원칙
SOLID 원칙이란, 객체지향 설계에서 유지 보수성과 확장성을 높이기 위한 5가지 원칙을 의미한다.
1) 유지보수성 및 확장성 향상
2) 변경에 유연한 설계 제공
1. 단일 책임 원칙 (SRP, Single Responsibility Principle)
각 클래스는 하나의 책임만을 가진다.
즉, 클래스는 변경될 이유가 하나뿐이고, 한 종류의 기능만을 수행해야한다.
ex1) 성적 계산기
- 학생의 이름을 받을 수 있어야 한다.
- 학생의 이름을 출력할 수 있어야 한다.
- 학생의 점수를 받고 성적을 계산할 수 있어야 한다.
적용되지 않은 사례
- Student 클래스는 Student 정보만 있어야 한다.
SRP가 적용된 사례
- Student는 학생 정보만 담고 있고, 성적 계산, 출력은 각각 다른 클래스가 맡고있다.
ex2) invoice(청구서류) 클래스 정의
- 인보이스를 추가/삭제할 수 있어야 한다.
- 이메일을 보낼 수 있어야 한다.
- 에러 로그를 띄울 수 있어야한다.
인보이스를 추가/삭제하는 AddInvoice(), DeleteInvoice()는 Invoice 클래스에,
이메일을 보내는 SendEmail()은 Email 클래스에,
에러 로그를 띄우는 LogError()는 Logger 클래스에 분류되었다.
2. 개방 폐쇄 원칙 (OCP, Open-Closed Principle)
확장에는 열려 있어야 하고, 수정에는 닫혀있어야 한다.
즉, 기존 코드를 최소한으로 변경하면서 새로운 기능을 추가할 수 있도록 설계해야 한다.
ex) 도형 타입을 매개변수로 받고 해당 도형을 그려주는 클래스 제작
적용되지 않은 사례
- 클래스 하나가 모든 도형을 전부 관리하고 있다.
- 어떤 도형이 추가되면 drawShape 함수의 코드가 수정된다.
- 해당 클래스는 수정할 때마다 영향을 받게 된다. (유지보수 어려움)
OCP가 적용된 사례
- ShapeManager는 도형들을 상속하는 Shape 인터페이스를 인자로 받는다.
- 도형의 출력은 Shape을 상속받은 특정 도형 클래스가 한다.
- 역할분담으로 인해 총 관리하는 ShapeManager는 수정할 필요가 없어진다.
3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)
파생(자식) 클래스는 기본(부모) 클래스에서 기대되는 행동을 보장해야 한다.
즉, 기본 클래스의 코드가 파생 클래스로 상속되더라도 정상적으로 동작해야 한다.
ex) Rectangle(직사각형)과 Square(정사각형) 클래스 설계
- 모든 도형은 넓이를 계산할 수 있어야 한다.
적용되지 않은 사례
- Rectangle 클래스를 Square 클래스가 상속받는다.
- 파생 클래스(Square)는 기본 클래스(Rectangle)의 너비(width)와 높이(height)를 따로 설정할 필요가 없다.
- Shape 인터페이스를 Rectangle, Square 클래스가 각각 상속받는다.
- 각 도형마다 필요한 변수를 할당하면 된다.
4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.즉, 하나의 거대한 인터페이스보다는 역할별로 세분화된 인터페이스를 만들어, 필요한 기능만 구현해야 한다.
ex) 프린트, 스캔을 하는 클래스
적용되지 않은 사례
- print, scan을 하나의 클래스에서 모두 구현하면, 파생 클래스는 필요가 없어도 이를 모두 구현해야 한다.
- 프린터, 스캔, 팩스 클래스를 따로 구현하게 되면 각각 필요한 클래스만 가져다 사용하면 된다.
5. 의존 역전 원칙 (DIP, Dependency Inversion Principle)
객체에서 다른 클래스의 서비스가 필요하다면, 그 클래스를 직접 참조하기 보다는 클래스의 상위요소(추상 클래스 or 인터페이스)를 참조해야 한다는 원칙이다.
즉, 추상성이 낮은(구체적으로 구현됨) 클래스보다는 추상성이 높은 클래스를 참조하여 결합도를 낮춰야 한다.
ex) 키보드와 모니터를 가진 컴퓨터
적용되지 않은 사례
- Computer 클래스는 Keyboard 클래스와 Monitor 클래스에 강하게 결합되어 있다.
- 다른 입출력장치나 여러종류의 Keyboard, Monitor를 요구하면 Computer의 변경량이 많아진다.
- Computer의 멤버변수를 인터페이스화하여 결합도가 낮아졌다.
- 이런 설계는 다른 입/출력장치로 쉽게 교체가 가능하다.
댓글
댓글 쓰기