-
[디자인 패턴 with TS] SOLID 원칙강의노트/디자인패턴 with TS 2025. 7. 10. 11:26

1. SOLID 원칙 개요
두문자 약어 개념 S SRP 단일 책임 원칙 (Single Responsibility Principle)
: 한 클래스는 하나의 책임만 가져야 한다.O OCP 개방 폐쇄 원칙 (Open/Closed Principle)
: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.L LSP 리스코프 치환 원칙 (Liskov Substitution Principle)
: 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.I ISP 인스턴스 분리 원칙 (Interface Segregation Principle)
: 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.D DIP 의존관계 역전 원칙 (Dependency Inversion Principle)
: 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다."2. 단일 책임 원칙: SRP
한 클래스는 하나의 책임만 가져야 한다.
함수를 변경해야 하는 이유가 해당 함수의 책임이다. 함수를 변경해야 하는 이유가 2가지 이상이라면 단일책임원칙 위반인 것이다.
단일책임원칙은 싱글턴에서 보았듯이, 실무에서 꽤나 많이 위반하는 원칙이다. 앞서 구현해봤던 간단한 싱글턴의 함수에서도 위반했는데 점점 더 복잡해질 수록 위반사항은 더 늘어만 갈 것이다. 그래서 추천하는 방법은, 처음부터 단일책임원칙을 지키려고 하지 말고 코드의 어느 부분이 위반인지 인지하고만 넘어가자. 이후에 단일책임원칙을 위반하는 다른 비슷한 케이스가 발생했을 때 두 함수를 같이 묶어서 리팩터링을 해주자. 예를들면 이미지를 생성하고 저장하는 함수가 있다면 처음부터 분리하지 말고, 동영상을 생성하고 저장하는 함수처럼 유사한 케이스가 있을 때 반복되는 저장과 생성을 담당하는 전용 함수를 만들어서 분리한다. 처음부터 단일책임원칙을 신경쓰다보면 코드의 진도가 나가지 않는다. 그러니 단일책임원칙은 알고만 있되, 너무 억매이지 말자.
3. 개방 폐쇄 원칙: OCP
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
다음에 배울 팩토리 메서드를 배우며 체감할 수 있는 원칙이다. 무언가를 추가하기 위해 기존 코드를 수정하는 것이 OCP 위반인 것이다. 대표적인 개방폐쇄원칙 위반은 아래 코드와 같은 형태이다.
// 변경 전 function main(type) { if (type === 'a') { doA(); } else if (type === 'b') { doB(); } else if (type === 'c') { doC(); } else { ... } } // 변경 후 function main(type) { if (type === 'a') { doA(); } else if (type === 'b') { doB(); } else if (type === 'c') { doC(); } else if (type === 'd') { // 조건 d 추가 doD(); } else if (type === 'e') { // 조건 e 추가 doE(); } else { ... } }그럼 어떻게 해야 할까?
코드를 아래와 같이 변경하면, 기존 코드는 건들지 않고 새로운 기능을 추가할 수 있다. 즉, 변경에는 닫혀 있면서 확장에는 열려있는 형태가 된다. 이렇듯 디자인 패턴을 활용하면 if문을 상당히 제거할 수 있게 된다.
interface Doable { do(): void; } function main (type: Doable) { type.do(); } const a = { do() {} }; const b = { do() {} }; const c = { do() {} }; const d = { do() {} }; // 조건 d 추가 const e = { do() {} }; // 조건 e 추가 main(e);
4. 리스코프 치환 원칙 (LSP)프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
용어가 생소하여 어렵게 느껴질 수 있지만 아주 간단한 원칙이다. 정의에 나오는 정확성은 타입이라고 쉽게 생각면 된다. 그래서 리스코프 치환 원칙을 다시 표현하면, "부모 객체를 자식 객체로 교체했을 때 타입에러가 발생하면 안된다" 라고 할 수 있다.
아래 코드를 통해서 리스코프 치환 원칙을 위배하는 예시를 살펴보자.
class Animal { isAnimal() { return true; } } class Bird extends Animal { fly() { return '파닥파닥'; // type: string } isBird() { return true; } } // 펭귄 추가 class Penguin extends Bird { override fly() { throw new Error('펭귄은 못날아'); // type: never } } console.log(new Animal().isAnimal()); // true console.log(new Bird().fly()); // 파닥파닥 console.log(new Penguin().fly()); // throw error; // 리스코프 치환 원칙 검증 console.log(new Bird().isAnimal()); // true console.log(new Bird().fly().at(1)); // 닥 console.log(new Penguin().fly().at(1)); // throw error위 코드에서와 같이 부모의 타입을 자식이 다르게 정의하는 경우에는 리스코프 치환 원칙에 위반된다. 해당 원칙을 기반으로 TS 타입 에러도 발생한다. 이를 쉽게 확인할 수 있는 방법은 사용하고 있는 부모 객체를 자식 객체로 치환해 보면 된다. 정상적으로 잘 동작하는 지가 아닌, 타입 에러가 발생하지 않는 것을 확인하면 된다. 부모 클래스(객체)를 자식 클래스(객체)로 갈아 끼웠을 때 타입 에러가 발생한다면 리스코프 치환 원칙 위반인 것이다.
5. 인터페이스 분리 원칙 (ISP)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
이러한 현상이 발생한 원인은 인터페이스 분리 원칙을 따르지 않은 인터페이스 정의에 있다. 위에서 인용어구의 의미는, 인터페이스를 잘게 쪼개라는 의미이다. 범용 인터페이스 하나가 있다면, 덩치가 너무 크기 때문에 implements 하는 클래스에서는 필요로 하지도 않는 무의미한 구현을 해야 하는 가능성이 생긴다. 이러한 현상을 막자는 것이 인터페이스 분리 원칙이다.
아래 코드는 펭귄 객체를 새로 만들 경우, 앞서 살펴 보았듯이 fly를 재정의하게 되면서 리스코프 치환 원칙을 다시 위반하게 될 것이다.
class Animal { isAnimal() { return true; } } interface IBird { fly(): string; quack(): string; } class Bird extends Animal implements Bird { fly() { return '파닥파닥'; } quack() { return '짹짹'; } isBird() { return true; } }인터페이스는 객체가 무엇을 할 수 있는지, 객체를 구현하기 이전에 정의해주는 역할을 한다. 그래서 인터페이스는 보통 '-able' (~ 할 수 있는)을 붙여서 정의한다. 기존 코드는 인터페이스에 필요 이상의 속성들을 포함하고 있었다. 아래와 같이 인터페이스 분리 원칙을 따르는 코드로 수정해주면, 새(Bird)가 비행이 필수가 아니고, 지저귀는 것이 필수라면 quackable만 붙여준다.
인터페이스의 장점이자 추상 클래스와 차이점은 동시에 인터페이스를 implements 할 수 있다. JS/TS는 단일 상속 언어이기 때문에 객체의 상속(extends)은 하나만 가능하다. 이러한 단일 상속 언어의 단점을 인터페이스로 극복할 수 있며, 불필요한 메서드 구현을 피하기 위해서는 잘게 나눈 인터페이스를 implements 해서 사용해야 한다. 인터페이스에 아무런 생각 없이 메서드를 우겨 넣게 되면, implements 하는 객체에서는 불필요한 메서드를 모두 구현을 해줘야 함과 동시에 리스코프 치환 원칙을 위반할 가능성이 높아진다. 그래서 인터페이스는 최소한으로만 정의해두고 다중 implements 기능을 활용하자.
class Animal { isAnimal() { return true; } } interface Quackable { quack(): string; } interface Flyable { fly(): string; } class Bird extends Animal implements Quackable { quack() { return '짹짹'; } isBird() { return true; } } class Panguin extends Bird implements Quackable {} class Sparrow extends Bird implements Flyable { fly() { return '파닥파닥'; } }6. 의존관계 역전 원칙 (DIP)
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
추상화에 의존하라는 의미는 인터페이스나 추상 클래스에 의존하라는 것이다. 의존성 관계 역전 원칙 (Dependency Inversion Principle) 과 의존성 주입 (Dependency Injection) 을 헷갈려 하는 사람이 많다. 의존성 주입은 DIP을 구현하는 방법(패턴) 중에 하나이다. 이 둘은 같은 개념이 아니며 종속관계를 가진다.
의존관계 역전 원칙은 강결합이 아닌 유연성을 주는 원칙이다. 의존성 주입을 통해서 함수를 호출하는 쪽에서 필요로 하는 것을 알아서 바꿔서 사용할 수 있도록 해주는 것이다. 잘 알아두어야 할 것은 아래 코드와 같이 매개변수나, 생성자를 통해서 주입 받는 것은 의존관계 역전 원칙을 구현하는 방법 중에 하나이다. 이 방법 외에서 서비스 로케이터 패턴과 같은 다른 DIP 구현 방법도 존재한다.
DIP를 간단하게 외우고 지킨다면 (1) 매개변수나 생성자로 외부 객체들을 주입하고, (2) 외부 객체를 가져올 때는 타입을 인터페이스나 추상 클래스 둘 만 사용한다고 생각하면 된다.
import AGrimpan from './grimpan.js'; function main(grimpan: AGrimpan) { grimpan.initialize(); } main(Grimpan.getInstance()); main(IEGrimpan.getInstance()); // 다른 인스턴스 주입 main(ChromeGrimpan.getInstance()); // 다른 인스턴스 주입주입을 받는 방법은 크게 생성자와 세터(setter)를 통해 받는 방법이 있다.
interface IObj {} class Obj implements IObj {} class A { constructor(obj?: IObj) {} setObj(obj: IObj) {} } new A(new Obj()); // 생성자를 이용한 주입 방식 new A().setObj(new Obj()); // 세터를 이용한 주입 방식앞으로 배울 디자인 패턴을 보면 SOLID 원칙이 저절로 지켜지는 경우가 많다. SOLID 원칙을 기키는데 용이하기 때문에 하나의 패턴으로 굳어진 것 같다. 앞서 단일 책임 원칙에서도 언급했지만, 실무에서 단일 책임 원칙을 못지키는 경우가 많다고 했다. 디자인 패턴임에도 불구하고 유일하게 단일 책임 원칙 못지키는 경우가 있다. 그래서 SOLID 원칙을 다 지키기 보다는, 단일 책임 원칙을 제외한 나머지 4가지를 만족하는 코드를 작성하도록 노력하는 것이 좋을 것이다.
출처 1. TS/JS 디자인 패턴 with Canvas
출처 2. 위키백과: SOLID
TS/JS 디자인 패턴 with Canvas: 제로초에게 제대로 배우기 강의 | 제로초(조현영) - 인프런
제로초(조현영) | 타입스크립트/자바스크립트로 그림판을 만들어보며 다양한 디자인 패턴의 쓰임과 장단점을 알아봅니다. canvas api를 배울 수 있는 것은 보너스!, 디자인 패턴 배워서 저한테 도
www.inflearn.com
SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. 컴퓨터 프로그래밍에서 SOLID란 로버트 C. 마틴[1][2]이 2000년대 초반[3]에 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자
ko.wikipedia.org
'강의노트 > 디자인패턴 with TS' 카테고리의 다른 글
[디자인 패턴 with TS] 팩토리 메서드 패턴으로 if문 정리하기 (4) 2025.07.12 [디자인 패턴 with TS] 싱글턴 (Singleton) (0) 2025.07.07 [디자인 패턴 with TS] 강의 내용 (1) 2025.07.07