객체 지향 프로그래밍
실세계의 객체처럼 모델링하고, 객체와의 상호작용을 토대로 프로그래밍하는 기법
객체 지향 언어의 특징
캡슐화(Encapsulation)
데이터와 그 데이터를 다루는 함수를 묶고, 외부의 개입과 오용을 막는 것
- 정보 은닉을 실현: 구현을 숨기고 안정적인 인터페이스를 제공함으로써, 디자인 변경에 대비함 (decoupling)
조합(Composition)
객체 내에 다른 객체의 인스턴스를 필드로 갖도록 하는 것 (HAS-A 관계)
- 다른 객체들로 이루어진 하나의 객체를 통해 새로운 인터페이스를 제공함
상속(Inheritance)
클래스들이 계층 구조를 갖고, 부모 디자인을 물려받는 것 (IS-A 관계)
- 기존 코드를 재사용할 수 있음
- 기존 코드와 독립적으로 확장할 수 있음
다형성(Polymophism)
하나의 인터페이스로 여러 종류의 함수나 클래스를 제공하는 것
- 코드 실행 시 어떤 개체에서 실행될지 신경쓸 필요 없도록(agnostic) 함
- 애드 혹(Ad hoc) 다형성: 함수에 대한 다형성 (오버로딩)
- 파라매트릭(Parametric) 다형성: 여러 타입을 다루는 함수와 클래스에 대한 다형성 (Generic)
- 서브타입(Subtype) 다형성: 클래스에 대한 다형성 (메소드 오버라이딩)
SOLID 원칙
소프트웨어 설계를 이해하기 쉽고, 유연하고, 유지 보수에 용이하도록 만들기 위한 원칙들
단일 책임 원칙(SRP; Single responsibility)
클래스를 수정하는 이유는 오직 한 가지여야만 한다.
- 모든 클래스와 함수는 한 가지 목적만을 가지며, 이를 캡슐화해야 한다.
- 여러 기능이 결합되도록 설계하면, 한 가지 기능을 수정할 때 다른 기능도 수정해야만 한다.
개방-폐쇄 원칙(OCP; Open-closed)
클래스와 함수는 확장에 열려있으나, 수정에는 닫혀있어야 한다.
- 다른 곳에서 사용되기 위해 수정에 닫혀있어야 한다. (잘 정의된 인터페이스를 통한 정보 은닉)
- 새로운 기능을 추가하기 위해 확장에 열려있어야 한다. (기존 코드를 바꾸지 않고 상속)
리스코브 치환 원칙(LSP; Liskov substitution)
기반 클래스에 대한 함수는 파생 클래스에 대해서도 작동해야 한다.
- 부모 클래스를 사용하는 코드에서, 부모 클래스를 자식 클래스로 치환해도 작동해야 한다.
- 즉, 부모 메소드의 Precondition을 강화하거나, Postcondition을 약화해선 안 된다.
- Precondition을 강화하면, 더 적은 범위의 인자만을 허용하게 된다.
- 기존에 허용됐던 인자를 사용하는 코드가 고장난다. (이거 넣어도 됐었는데?)
- Postcondtion을 약화하면, 더 넓은 범위의 반환 값을 허용하게 된다.
- 기존 범위의 반환 값에만 대응했던 코드가 고장난다. (이런 것만 나올 줄 알았는데?)
인터페이스 분리 원칙(ISP; Interface segregation)
범용적인 인터페이스 하나보다 구체적인 인터페이스 여러 개가 낫다.
- 부모 클래스에게 상속받은 메소드 중 사용하지 않는 것이 있으면 안 된다.
- 즉, 너무 큰 인터페이스를 작은 인터페이스 여러 개로 나누고, 클라이언트가 그들 중 하나에 의존하도록 해야 한다.
- 클라이언트 코드가 꼭 필요한 핵심에만 의존하도록 만든다. (decoupling)
- 시스템이 너무 결합(coupled)되어 있으면, 한 부분을 수정하기 위해 다른 많은 부분을 건드려야만 할 것이다.
의존 역전 원칙(DIP; Dependency inversion)
구현보다 추상적 개념에 의존하라.
- 상위 계층 모듈이 하위 계층 모듈에 직접 의존해선 안 된다. 둘 다 추상화된 개념(interface)에 의존해야 한다.
- 추상화된 개념이 구체적인 구현에 의존해선 안 된다. 그 반대로, 구현이 추상에 의존해야 한다.
- 즉, 하위 계층 모듈(
Object B
)이 상위 계층 모듈(Interface A
)을 의존하게 된다.
- 기존의 top-to-bottom에서 역전된 bottom-to-top 모습의 의존 관계를 보인다.
디자인 패턴
소프트웨어 디자인 시 자주 발생하는 문제를 위한 해결책
생성(Creational) 패턴
객체 생성을 위한 패턴
Abstract factory 패턴
객체를 생성하는 팩토리 클래스를 추상화
Builder 패턴
복잡한 객체의 생성 과정을 분리
Factory method 패턴
객체 생성을 캡슐화
Prototype 패턴
미리 만들어둔 뼈대 객체를 기반으로 새로운 객체 생성
Singleton 패턴
클래스가 오직 하나의 인스턴스만을 갖도록 함
- 생성 비용을 아끼고 데이터를 쉽게 공유할 수 있음
- 단점: 객체 지향적이지 않고, TDD가 어려워짐
- 클래스 상속이 불가능함
- 의존성을 주입받기 어려움
- 매 테스트마다 싱글톤 객체의 깨끗함을 보장할 수 없음
구조(Structural) 패턴
클래스 구조 설계를 위한 패턴
Composite 패턴
트리 구조처럼 부분-전체 계층 구조를 구성함 (e.g. 파일-디렉토리)
Decorator 패턴
객체를 감싸 동적으로 새로운 기능을 추가함
Proxy 패턴
객체를 감싸고 해당 클래스를 상속하여, 객체를 접근하는 메소드에 새로운 기능을 추가함
행동(Behavioural) 패턴
객체 간 상호작용을 위한 패턴
Command 패턴
클라이언트의 다양한 요청을 캡슐화
Observer(Publish/subscribe) 패턴
1:N 의존 관계로, 한 객체의 상태 변경을 다른 객체들에게 알림
Strategy 패턴
수행할 알고리즘을 캡슐화하고 서로 교체할 수 있도록 함
Template Method 패턴
수행할 알고리즘의 뼈대를 만들어두고, 자식 클래스가 특정 부분을 정의하도록 함