Post

TypeScript에서 SOLID 원칙 중 ISP 원칙 적용해보기

TypeScript에서 SOLID 원칙 중 ISP 원칙 적용해보기

타입스크립트에서 SOLID 원칙 중 ISP 원칙을 적용하는 방법을 예제와 함께 소개해보려고 한다!!!

ISP 원칙은, 인터페이스 분리 원칙 (Interface Segregation Principle)으로 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙이다.

ISP는 마치 SRP와 비슷하게 보이는데, SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다고 말할 수 있다.

다만 유의할 점은 인터페이스클래스와 다르게 추상화이기 때문에 여러개의 역할을 가지는데 있어 제약이 없다.

즉, SRP 원칙의 목표는 클래스 분리를 통하여 이루어진다면, ISP 원칙은 인터페이스 분리를 통하여 이루어 진다고 볼 수 있다.

또한 SRP 원칙의 클래스 책임의 범위에 대해 분리 기준이 다르듯이, 인터페이스를 분리하는 기준은 상황에 따라 다르다.

예제 코드를 함께 보면서 알아보자.

ISP를 적용하기 전 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// ISP 적용 전

interface HomeDevice {
  turnOn(): void;
  turnOff(): void;
  setTimer(hours: number): void;
  connectToWiFi(ssid: string, password: string): void;
  playMusic(): void;
}

class SmartTV implements HomeDevice {
  turnOn(): void {
    console.log("Smart TV: 전원 켬!");
  }

  turnOff(): void {
    console.log("Smart TV: 전원 끔!");
  }

  setTimer(hours: number): void {
    console.log(`Smart TV: ${hours} 시간 타이머 설정!`);
  }

  connectToWiFi(ssid: string, password: string): void {
    console.log(`Smart TV: ${ssid} 와이파이 연결 완료!`);
  }

  playMusic(): void {
    console.log("Smart TV: 음악 재생 중!");
  }
}

class AirCirculator implements HomeDevice {
  turnOn(): void {
    console.log("AirCirculator: 전원 켬!");
  }

  turnOff(): void {
    console.log("AirCirculator: 전원 끔!");
  }

  setTimer(hours: number): void {
    console.log(`AirCirculator: ${hours} 시간 타이머 설정!`);
  }

  connectToWiFi(ssid: string, password: string): void {
    console.log("지원하지 않는 기능입니다.");
  }

  playMusic(): void {
    console.log("지원하지 않는 기능입니다.");
  }
}

// 사용 예시
const myTV = new SmartTV();
myTV.turnOn();
myTV.playMusic();

const myCirculator = new AirCirculator();
AirCirculator.turnOn();
AirCirculator.setTimer(1);

ISP 적용 전 코드의 문제점

현재 코드에서 HomeDevice 인터페이스는 모든 가정용 전자기기에 필요한 기능을 포함하고 있다. 하지만 SmartTVAirCirculator 클래스는 모든 메서드를 구현해야 하며, 일부 메서드는 해당 기기에 적용되지 않는 경우가 있다. AirCirculator 클래스를 보면, 불필요한 connectToWiFI, playMusic 등의 기능을 포함하고 있다. 결국 필요하지도 않은 기능을 어쩔 수 없이 구현해야 하는 낭비가 발생한다.

이는 각각의 기능에 맞게 인터페이스를 잘게 분리하도록 구성하고, 잘게 분리된 인터페이스를 클래스가 지원되는 기능만을 선별하여 implements 하면 ISP 원칙이 지켜지게 된다.

ISP를 적용하여 해결하는 과정을 살펴보자.

ISP를 적용한 후의 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// ISP 적용 후
interface PowerControl {
  turnOn(): void;
  turnOff(): void;
}

interface Timer {
  setTimer(hours: number): void;
}

interface WiFiConnectable {
  connectToWiFi(ssid: string, password: string): void;
}

interface MusicPlayer {
  playMusic(): void;
}

class SmartTV implements PowerControl, Timer, WiFiConnectable, MusicPlayer {
  turnOn(): void {
    console.log("Smart TV: 전원 켬!");
  }

  turnOff(): void {
    console.log("Smart TV: 전원 끔!");
  }

  setTimer(hours: number): void {
    console.log(`Smart TV: ${hours} 시간 타이머 설정!`);
  }

  connectToWiFi(ssid: string, password: string): void {
    console.log(`Smart TV: ${ssid} 와이파이 연결 완료!`);
  }

  playMusic(): void {
    console.log("Smart TV: 음악 재생 중!");
  }
}

class AirCirculator implements PowerControl, Timer {
  turnOn(): void {
    console.log("AirCirculator: 전원 켬!");
  }

  turnOff(): void {
    console.log("AirCirculator: 전원 끔!");
  }

  setTimer(hours: number): void {
    console.log(`AirCirculator: ${hours} 시간 타이머 설정!`);
  }
}

// 사용 예시
const myTV = new SmartTV();
myTV.turnOn();
myTV.playMusic();

const myCirculator = new AirCirculator();
AirCirculator.turnOn();
AirCirculator.setTimer(1);

ISP 적용 후 개선점

ISP를 적용한 코드는, PowerControl, Timer ,WiFiConnectable, MusicPlayer, 인터페이스는 각각 구체적인 기능을 정의한다. 이를 통해 SmartTvAirCirculator 클래스는 각각 필요한 인터페이스만 구현하게 되어 불필요한 메서드 구현을 피할 수 있다.

적용 후 장점으로는,

  • 클래스의 의존성 감소: 각 클래스는 자신이 필요로 하는 인터페이스만 의존하게 되어, 코드 의존성이 줄어든다.
  • 유연성 증가: 새로운 기기나 기능을 추가할 때 필요한 인터페이스만 구현하면 되므로 유연하게 확장이 가능하다.
  • 인터페이스 명확화: 인터페이스가 좀 더 명확하게 분리되어, 각 기능의 역할이 명확해진다.
  • 유지보수 용이성: 인터페이스 변경이 해당 인터페이스를 구현하는 클래스에 미치는 영향이 최소화되어 유지보수가 용이해진다.

이렇게 ISP를 적용한다면 인터페이스와 클래스 간의 관계가 더욱 명확해지고, 코드의 재사용성과 유지보수성이 향상된다.

Reference

완벽하게 이해하는 ISP (인터페이스 분리 원칙)

This post is licensed under CC BY 4.0 by the author.
-->