객체지향의 5가지 설계 원칙이 있다.
SRP
OCP
LSP
ISP
DIP
SRP (Single-responsibility principle) 단일 책임 원칙
: 하나의 모듈은 하나의 액터에 대해서만 책임져야 한다.
: 클래스는 단 하나의 기능만 갖고, 제공하는 서비스는 그 기능을 수행한다.
응집성
: 단일 액터를 책임지는 코드를 함께 묶어주는 힘
응집성을 위반하는 징후 1: 우발적 중복
응집성을 위반하는 징후 2: 병합
SRP 예제 참고
퍼사드(Facade) 패턴
OCP (Open Compute Project) 개방-폐쇄 원칙
: 소프트웨어 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
기존 코드를 변경하지 않고 기능을 추가할 수 있어야 한다.
- 동작 방식: Controller 또는 Interactor > Presenter > View
- 기능이 how, why, when 발생하는지에 따라서 기능을 분리하고 분리한 기능을 컴포넌트의 계층구조로 조직화한다.
- 목표: 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.
처리 과정을 클래스 단위로 분할하고, 클래스는 컴포넌트 단위로 분리한다.
<I>: 인터페이스
<DS>: 데이터 구조
OCP 원칙 예제
- 카드 결제 시스템을 만들어야 한다.
- 현재 지원하는 카드는 신한 카드 하나뿐이다.
- 이제 우리 카드 결제가 추가되어 구현해야 한다.
- 앞으로도 카드는 지속해서 추가될 예정이다
LSP (Liskov Substitution Principle) 리스코프 치환 원칙
: 상위 타입의 객체를 하위 타입의 객체로 치환해도 코드가 문제없이 동작해야 한다는 원칙 (is-as)
LSP 위반 예제: 정사각형-직사각형 문제
- 정사각형이 직사각형을 상속받는 구조에서, with height 값을 바꾸면 정사각형 변이 같아야 하므로 이는 LSP에 위반된다.
class Rectangle {
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
public setWidth(width: number): void {
this.width = width;
}
public setHeight(height: number): void {
this.height = height;
}
public area(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
constructor(width: number, height: number) {
super(width, height);
}
public setWidth(width: number): void {
this.width = width;
this.height = width;
}
public setHeight(height: number): void {
this.width = height;
this.height = height;
}
public area(): number {
return this.width * this.height;
}
}
const jonyRectangle = new Rectangle(3, 5);
jonyRectangle.setWidth(5);
jonyRectangle.setHeight(3);
jonyRectangle.area() === 15; // true
const jewookRectangle = new Square(3, 3);
jewookRectangle.setWidth(5);
jewookRectangle.setHeight(3);
jewookRectangle.area() === 15; // false
LSP 지키려면 예제: 정사각형-직사각형 문제
- 정사각형과 직사각형은 서로 다른 동작을 하는 클래스로 구현해야한다.
interface Shape {
area(): number;
}
class Rectangle implements Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
public setWidth(width: number): void {
this.width = width;
}
public setHeight(height: number): void {
this.height = height;
}
public area(): number {
return this.width * this.height;
}
}
class Square implements Shape {
private side: number;
constructor(side: number) {
this.side = side;
}
public setSide(side: number): void {
this.side = side;
}
public area(): number {
return this.side * this.side;
}
}
const jonyShape: Shape = new Rectangle(2, 8);
jonyShape.area() === 16; // true
const jewookShape: Shape = new Square(5);
jewookShape.area() === 25; // true
참고
ISP (Interface Segregation Principle) 인터페이스 분리 원칙
: 자신이 사용하지 않는 인터페이스에는 의존하면 안된다.
: 인터페이스를 작은 단위로 분리시킴으로써 한 클래스가 다른 클래스에 종솔될 때 가능한 최소한의 인터페이스만을 사용한다.
오퍼레이션을 인터페이스 단위로 분리하기 (왼쪽 분리 전, 오른쪽 분리 후)
정적 타입 언어 (Java) 는 import, use, include 선언문을 사용이 필요하다.
-> 소스 코드의 의존성 발생 -> 재컴파일, 재배포 필요!
동적 타입 언어 (Ruby, Python) 는 import, use, include 선언문이 존재하지 않는다.
-> 재컴파일, 재배포 필요 X
ISP는 언어 종류에 따라 영향받는 정도가 다르다.
잘못된 아키텍처 예시
S라는 시스템 구축 시 F 프레임워크 도립을 원하며, D 데이터베이스를 반드시 사용하도록 만들경우
-> S는 F에 의존, F는 D에 의존
-> 의존하게 되면 문제가 많이 발생할 수 있다.
DIP (Dependency Inversion Principle) 의존성 역전 원칙
: 소스 코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템
ex) 자바 같은 정적 타입 언어에서 use, import, include 구분은 오직 인터페이스나 추상 클래스 같은 추상적인 선언만을 참조해야한다. (구체적인 대상에는 의존하면 안된다)
: 상위 계층이 하위 계층의 구현으로부터 독립되게 하는 원칙
- 소스코드 의존성은 제어흐름과는 반대 방향으로 역전된다.
- 유연성이 극대화된 시스템
- 아키텍저 다이어그램에서 가장 눈에 띄는 원칙
변하지 않는 것 vs 변하기 쉬운 것
- 인터페이스, 추상 클래스 -> 변하지 않는 것
- 구체 클래스 -> 변하기 쉬운 것 ex) 우리가 개발중이라 자주 변경될 수 밖에 없는 모듈들
소프트웨어 설계의 기본
- 인터페이스의 변동성을 낮추기 위해 노력한다.
- 인터페이스를 변경하지 않고도 구현체에 기능을 추가할 수 있는 방법을 찾기 위해 노력한다.
DIP 코딩 실천법
- 변동성이 큰 구체 클래스를 참조하지 않기 대신에 추상 인터페이스를 참조하기
- 변동성이 큰 구체 클래스로부터 파생하지 않기
- 구체 함수를 오버라이드 하지 않기
- 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 않기
의존성을 관리하기 위해 추상 팩토리 패턴을 사용한다.
DIP 위배를 모두 없앨 수는 없다.
But 위배하는 클래스들을 구체 컴포넌트 내부로 모으면, 시스템의 나머지 부분과 분리할 수 있다.
대다수의 시스템은 이러한 구체 컴포넌트 (ex. Main) 를 최소한 하나는 포함한다.
참고
클래스간의 관계 종류 화살표
'클린아키텍처' 카테고리의 다른 글
[클린아키텍처] 17장 경계: 선긋기, 18장 경계 해부학 (0) | 2022.03.10 |
---|---|
[클린아키텍처] 15장 아키텍처란?, 16장 독립성 (0) | 2022.03.06 |
[클린아키텍처] 12장~14장 컴포넌트 원칙 (0) | 2022.03.06 |
[클린아키텍처] 3장~6장 프로그래밍 패러다임 - 구조적 프로그래밍, 객체 지향 프로그래밍, 함수형 프로그래밍 (0) | 2022.02.27 |
[클린아키텍처] 1장 설계와 아키텍처란? 2장 두가지 가치에 대한 이야기 (0) | 2022.02.27 |
댓글