solid pricipal img generated by AI
S.O.L.I.D 원칙 타입스크립트 코드 예제로 이해하기
깨끗하고 유지보수하기 쉬운 코드를 작성하는 것은 단순히 동작하는 코드를 작성하는 것만큼 중요합니다.
S.O.L.I.D 원칙은 시간이 지나도 쉽게 조정, 확장, 유지보수할 수 있는 코드를 작성할 수 있도록 합니다.
이 원칙은 2000년대 초반 Robert C. Martin(일명 Uncle Bob)에 의해 소개되었습니다.
이 글에서는 5가지 원칙을 실제 코드 예제와 함께 살펴보겠습니다.
S: 단일 책임 원칙(Single Responsibility Principle, SRP)
클래스는 하나의 역할만 가져야 하며, 변경해야 하는 이유도 하나여야 합니다.
즉, 한 클래스가 여러 가지 역할을 담당해서는 안 되며, 단 하나의 책임만을 수행해야 합니다.
잘못된 예제:
class UserManager {
authenticate(username: string, password: string) {
// 인증 로직
}
updateUserProfile(user: any) {
// 프로필 변경 로직
}
sendNotification(email: string, message: string) {
// 이메일 전송 로직
}
}
이 클래스는 인증, 프로필 관리, 이메일 전송이라는 세 가지 책임을 가지므로 SRP를 위반합니다.
개선된 코드:
class AuthenticationService {
authenticate(username: string, password: string) {
// 인증 로직
}
}
class UserProfileService {
updateUserProfile(user: any) {
// 프로필 변경 로직
}
}
class NotificationService {
sendNotification(email: string, message: string) {
// 이메일 전송 로직
}
}
각 클래스가 하나의 역할만 담당하므로 유지보수가 쉬워집니다.
O: 개방-폐쇄 원칙(Open/Closed Principle, OCP)
소프트웨어 구성 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 합니다.
즉, 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있도록 설계해야 합니다.
잘못된 예제:
class AreaCalculator {
calculate(shape: any) {
if (shape.type === 'rectangle') {
return shape.width * shape.height;
} else if (shape.type === 'circle') {
return Math.PI * shape.radius ** 2;
}
}
}
새로운 도형(예: 삼각형)을 추가하려면 기존 코드를 수정해야 하므로 OCP를 위반합니다.
개선된 코드:
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea() { return this.width * this.height; }
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea() { return Math.PI * this.radius ** 2; }
}
새로운 도형을 추가할 때 기존 코드를 변경할 필요가 없습니다.
L: 리스코프 치환 원칙(Liskov Substitution Principle, LSP)
부모 클래스의 객체를 자식 클래스의 객체로 대체해도 프로그램이 정상적으로 동작해야 합니다.
잘못된 예제:
class EngineVehicle {
startEngine() {
console.log('Starting engine');
}
}
class Car extends EngineVehicle {}
class Bicycle extends EngineVehicle {}
자전거는 엔진이 없는데 startEngine() 메서드를 가지므로 LSP를 위반합니다.
개선된 코드:
class Vehicle {
move() {
console.log('Moving forward');
}
}
class Car extends Vehicle {
move() { console.log('Starting engine and driving'); }
}
class Bicycle extends Vehicle {
move() { console.log('Pedaling to move'); }
}
이제 자동차와 자전거가 move()를 각각 적절히 구현하므로 LSP를 준수합니다.
I: 인터페이스 분리 원칙(Interface Segregation Principle, ISP)
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다.
잘못된 예제:
interface UniversalMediaPlayer {
playAudio(): void;
playVideo(): void;
}
class AudioPlayer implements UniversalMediaPlayer {
playAudio() { console.log('Playing audio'); }
playVideo() { throw new Error('Unsupported feature'); }
}
개선된 코드:
interface AudioPlayable {
playAudio(): void;
}
interface VideoPlayable {
playVideo(): void;
}
class AudioPlayer implements AudioPlayable {
playAudio() { console.log('Playing audio'); }
}
이제 각 인터페이스가 특정 역할만 하므로 ISP를 준수합니다.
D: 의존성 역전 원칙(Dependency Inversion Principle, DIP)
고수준 모듈은 저수준 모듈에 의존하지 않고, 둘 다 추상화에 의존해야 합니다.
잘못된 예제:
class EmailService {
private provider = new GmailProvider();
send(email: string, content: string) {
this.provider.sendEmail(email, content);
}
}
이메일 서비스가 특정 이메일 제공업체(Gmail)에 의존하므로 DIP를 위반합니다.
개선된 코드:
interface EmailProvider {
sendEmail(email: string, content: string): void;
}
class GmailProvider implements EmailProvider {
sendEmail(email: string, content: string) {
console.log(`Sending email via Gmail: ${content}`);
}
}
class EmailService {
constructor(private provider: EmailProvider) {}
send(email: string, content: string) {
this.provider.sendEmail(email, content);
}
}
이제 이메일 서비스는 특정 구현체가 아닌 추상 인터페이스에 의존하므로 DIP를 준수합니다.
같이보면 좋은글 ->
[JavaScript] 프론트엔드 개발자를 위한 함수형 프로그래밍(functional programming)
[JavaScript] 프론트엔드 개발자를 위한 함수형 프로그래밍(functional programming)
JavaScript 개발자를 위한 함수형 프로그래밍JavaScript는 다양한 스타일로 코드를 작성할 수 있는 유연한 언어입니다. 명령형(imperative), 객체 지향(object-oriented), 그리고 함수형(functional) 프로그래밍
intelloper.tistory.com