티스토리 뷰

📺 시리즈

2023.10.02 - [Go/디자인 패턴] - [Go] SOLID in Go - SOLID란?

2023.10.03 - [Go/디자인 패턴] - [Go] SOLID in Go - 구조체와 메서드

2023.10.04 - [Go/디자인 패턴] - [Go] SOLID in Go - 인터페이스

2023.10.09 - [Go/디자인 패턴] - [Go] SOLID in Go - 컴포지션

2023.10.10 - [Go/디자인 패턴] - [Go] SOLID in Go - 패키지

2023.10.11 - [Go/디자인 패턴] - [Go] SOLID in Go - 단일 책임 원칙

2023.10.12 - [Go/디자인 패턴] - [Go] SOLID in Go - 개방 폐쇄 원칙

2023.10.13 - [Go/디자인 패턴] - [Go] SOLID in Go - 리스코프 치환 원칙


⚒ 인터페이스 분리 원칙 (Interface Segregation Principle)

 

클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.

 

 인터페이스를 정의할 때 '이 기능도 필요할 것 같고 저 기능도 필요할 것 같아'라고 생각해서 처음부터 너무 많은 메서드를 인터페이스에 포함시킬 수 있습니다. 그러다 보면 인터페이스를 구현할 때 불필요한 기능을 억지로 구현해야 하는 문제가 발생할 수 있습니다. 인터페이스 분리 원칙은 이를 방지하기 위한 원칙으로, 인터페이스를 잘게 분리하여 클라이언트의 용도에 맞는 인터페이스만을 제공하는 것을 의미합니다.

 

너무 많은 기능을 가진 인터페이스

 Machine 인터페이스는 문서 출력, 팩스 그리고 스캔까지 총 세 개의 메서드를 가지고 있습니다. 흔하게 볼 수 있는 복합기를 추상화한 것이라고 볼 수 있습니다.

type Machine interface {
	Print(d Document)
	Fax(d Document)
	Scan(d Document)
}

 그런데 문서 출력만 가능한 오래된 프린터가 있습니다. 90년대 윈도 os를 그대로 사용하는 연구실도 있으니 놀라운 일은 아니죠. 이 프린터로 Machine 인터페이스를 구현하는 과정에서 팩스와 스캔은 기능 구현이 불가능하다 보니 이 메서드들을 호출하게 되면 시스템에 문제(패닉)가 발생하게 됩니다.

type OldFashionedPrinter struct{}

func (o OldFashionedPrinter) Print(d Document) {
	fmt.Printf("Print: %s\n", d.Content)
}

func (o OldFashionedPrinter) Fax(d Document) {
	panic("operation not supported")
}

func (o OldFashionedPrinter) Scan(d Document) {
	panic("operation not supported")
}

var _ Machine = (*OldFashionedPrinter)(nil)
package main

import (
	"solid/isp"
)

func main() {
	p := isp.OldFashionedPrinter{}
	d := isp.Document{Content: "This is a document"}
	p.Scan(d)
}
$ go run ./main.go
panic: operation not supported

 이처럼 클라이언트가 자신이 사용하지 않는 메서드에 의존하게 됨으로 인해 불필요한 문제 상황을 야기할 수 있습니다.

 

인터페이스 분리

 Machine 인터페이스를 분리하여 다음과 같이 세 개의 인터페이스를 정의했습니다. 이제 각 인터페이스는 하나의 메서드만을 가지고 있습니다.

type Printer interface {
	Print(d Document)
}

type Scanner interface {
	Scan(d Document)
}

type Faxer interface {
	Fax(d Document)
}

 클라이언트는 세 개의 인터페이스 중 자신이 필요한 인터페이스만을 선택하여 구현할 수 있습니다. 앞서 구현하지도 못하는 메서드에 억지로의존하던 상황과는 큰 차이를 보여줍니다.

type CustomPrinter struct{}

func (c CustomPrinter) Print(d Document) {
	fmt.Printf("Print: %s\n", d.Content)
}

func (c CustomPrinter) Scan(d Document) {
	fmt.Printf("Scan: %s\n", d.Content)
}

var _ Printer = (*CustomPrinter)(nil)
var _ Scanner = (*CustomPrinter)(nil)

 작게 쪼개진 인터페이스들은 필요에 따라 다른 인터페이스에 임베딩하여 사용할 수도 있습니다.

type CustomMachine interface {
	Printer
	Scanner
}

var _ CustomMachine = (*CustomPrinter)(nil)

🎯 인터페이스 분리 원칙의 이점 및 사용 시 주의할 점

이점

  • 더 작은 인터페이스: 인터페이스를 더 작고 관련성 높은 부분으로 분할함으로써 각 인터페이스의 목적과 책임이 명확해집니다.
  • 느슨한 결합: 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않게 됨으로써 변경이 쉬워지고 유지보수가 용이해집니다.
  • 유연성 및 확장성: 시스템을 확장하거나 변경할 때 필요한 인터페이스를 선택적으로 구현할 수 있으므로 새로운 기능 출가 또는 기존 기능의 수정을 최소화하고 코드 재사용성을 높일 수 있습니다.

 

주의할 점

  • 과도한 분리: 인터페이스를 지나치게 분리하게 되면 비슷한 기능을 가진 여러 개의 작은 인터페이스가 만들어질 수 있습니다.
  • 복잡성 증가: 너무 많은 작은 인터페이스로 인해 오히려 코드와 시스템 구조를 이해하는 것이 어려워질 수 있습니다.

🙏 마치며

 인터페이스 분리 원칙에 대해 알아보았습니다. 하나의 인터페이스에 너무 많은 기능을 집어넣는다거나 인터페이스를 너무 잘게 쪼개서 문제 상황과 마주치게 될 수 있다는 것. 즉, 과유불급이다! 그렇다고 정말 필요한 기능만 딱 정의를 한다는 것이 생각만큼 쉽지는 않을 것 같습니다. 많은 연습이 필요할 것 같네요.


📖 참고자료

 

[Must Have] Tucker의 Go 언어 프로그래밍(세종도서 선정작) - 골든래빗

게임 회사 서버 전문가가 알려주는 Go 언어를 내 것으로 만드는 비법! 구글이 개발한 Go는 고성능 비동기 프로그래밍에 유용한 언어입니다. 이 책은 Go 언어로 ‘나만의 프로그램’을 만들 수 있

goldenrabbit.co.kr

https://www.udemy.com/course/design-patterns-go/

글에서 수정이 필요한 부분이나 설명이 부족한 부분이 있다면 댓글로 남겨주세요!
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
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
Total
Today
Yesterday
글 보관함