티스토리 뷰

📺 시리즈

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 - 리스코프 치환 원칙

2023.10.16 - [Go/디자인 패턴] - [Go] SOLID in Go - 인터페이스 분리 원칙


🌌 의존 관계 역전 원칙 (Dependency Inversion Principle)

 

상위 계층이 하위 계층에 의존하는 전통적인 의존 관계를 역전시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.

  • 원칙 1: 상위 모듈은 하위 모듈에 의존해서는 안 된다. 둘 다 추상 모듈에 의존해야 한다.
  • 원칙 2: 추상 모듈은 구체화된 모듈에 의존해서는 안 된다. 구체화된 모듈은 추상 모듈에 의존해야 한다.

 

전통적인 의존 관계

 하이랄의 용사, 링크는 마스터소드라는 검을 가지고 있습니다. 구체화된 모듈이 구체화된 모듈에 의존하고 있는 것을 이런 식으로 표현해 보았습니다.

type Link struct {
	Weapon MasterSword
}

func (l *Link) Attack() int {
	return l.Weapon.AttackPower
}

type MasterSword struct {
	AttackPower int
}

 그런데 어느 날, 불의의 사고로 마스터소드가 박살이나 수리가 필요한 상황이 발생했습니다. 하이랄에는 이미 마물들의 마수가 곳곳으로 퍼지고 있습니다. 링크는 하이랄의 용사이기 때문에 마스터소드의 수리가 완료될 때까지 마냥 손을 놓고만 있을 수는 없는 노릇이죠. 그래서 활을 사용해서 마물들을 제거하기로 했습니다.

type Link struct {
	Weapon Bow
	Ammo   Arrow
}

func (l *Link) Attack() int {
	return l.Weapon.AttackPower + l.Ammo.AttackPower
}

type Bow struct {
	AttackPower int
}

type Arrow struct {
	AttackPower int
}

 그런데 링크는 기존에 마스터소드에 의존하고 있었습니다. 마스터소드는 한 손으로 휘둘러서 사용할 수 있었지만, 활은 화살도 필요하고 양손을 사용한 활시위를 당기는 동작이 수반되어야 합니다. 따라서 링크의 Attack 메서드 구현을 변경해야 하며, 이는 '개방-폐쇄 원칙'을 위배하는 것이기도 합니다. 문제는 링크가 무기로 마스터소드와 활만 사용할 수 있는 것이 아니라는 점에 있습니다. 만약 양손검이나 도끼 등의 무기를 사용한다고 했을 때 이를 위해 매번 구현을 변경해야 되는 문제가 발생하게 됩니다.

 

의존성 역전

 이 경우에는 다음과 같이 링크라는 구체화된 모듈이 Weapon이라는 추상 모듈에 의존하게 함으로써 해결할 수 있습니다. 이제 링크는 마스터소드 외에도 Weapon 인터페이스를 구현한 무기라면 어떤 것이든 사용할 수 있게 됩니다. 이렇게 의존 관계를 구체화된 모듈에서 추상 모듈로 역전시킴으로써 프로그램의 유연성을 향상할 수 있습니다.

type Weapon interface {
	Attack() int
}

type MasterSword struct {
	AttackPower int
}

func (m *MasterSword) Attack() int {
	return m.AttackPower
}

type Bow struct {
	AttackPower int
}

func (b *Bow) Attack() int {
	return b.AttackPower
}

type Arrow struct {
	AttackPower int
}

func (a *Arrow) Attack() int {
	return a.AttackPower
}

var _ Weapon = (*MasterSword)(nil)
var _ Weapon = (*Bow)(nil)
var _ Weapon = (*Arrow)(nil)
type Link struct {
	LeftHand  Weapon
	RightHand Weapon
}

func (l *Link) Attack() int {
	return l.LeftHand.Attack() + l.RightHand.Attack()
}

 여기서 링크도 Hero라는 인터페이스를 구현하게 함으로써 추상 모듈이 추상 모듈에 의존하는 구조로 나타낼 수도 있습니다. 무기를 갈아낄 수 있는 것과 유사하게 무기를 착용하는 착용자도 유일하지는 않을 것이기 때문이죠. 예를 들어, 시간의 오카리나 링크가 야숨 링크의 마스터소드를 착용할 수도 있다는 것입니다. (진짜 가능한지는 모름)

type Hero interface {
	SetLeftHand(Weapon)
	SetRightHand(Weapon)
	Attack() int
}

type Link struct {
	LeftHand  Weapon
	RightHand Weapon
}

func (l *Link) SetLeftHand(w Weapon) {
	l.LeftHand = w
}

func (l *Link) SetRightHand(w Weapon) {
	l.RightHand = w
}

func (l *Link) Attack() int {
	return l.LeftHand.Attack() + l.RightHand.Attack()
}

var _ Hero = (*Link)(nil)

🎯 의존 관계 역전 원칙의 이점 및 사용 시 주의할 점

이점

  • 유연성 향상: 모듈 간의 결합도를 낮추어 새로운 모듈을 추가하거나 기존 모듈을 변경할 때 다른 부분에 영향이 덜 미치게 됩니다.
  • 테스트 용이: 의존성을 주입할 수 있으므로 mock 테스트가 가능합니다. 이로써 유닛 테스트 작성이 쉬워지고 코드의 신뢰성을 높일 수 있습니다.
  • 재사용성 증가: 고수준 모듈과 저수준 모듈 사이의 의존성이 인터페이스를 통해 정의되므로, 이 인터페이스를 다른 컨텍스트에서 재사용할 수 있습니다.

 

주의할 점

  • 적절한 추상화와 인터페이스 설계 필요: 잘못된 추상화나 너무 많은 인터페이스는 코드를 복잡하게 만들 수 있습니다.
  • 비용 및 성능 고려: DIP를 엄격하게 따르면 런타임에서 객체를 생성하고 주입하는 데 관련된 비용이 발생할 수 있으며, 이는 성능 문제를 야기할 수 있습니다. 성능 요구 사항에 따라 DIP를 유연하게 적용해야 합니다.

🙏 마치며

 이로써 SOLID 다섯 가지 원칙에 대해 Go로 작성된 예제를 통해 알아보았습니다. 아무래도 SOLID가 객체지향 진영에서 많이 언급되는 개념이다 보니 클래스와 상속에 초점을 맞추게 되는 것도 무리는 아니라고 봅니다. 그러나 이 시리즈를 작성하기 전에도 지금도 SOLID는 근본적으로 어떤 설계가 좋은 것인지에 대한 가이드라인의 일종이기 때문에 객체지향 언어가 아니라도 충분히 적용하고 사용할 수 있다고 생각합니다.

 

 의도적으로 좋은 설계에 대해 고민하는 것. 물론 설계에 매몰된다면 생산성을 놓치게되는 문제가 발생할 수도 있을 것입니다. 그렇기 때문에 부단히 연습하고 노력하는 것. 뻔한 것이 가장 뻔하지 않는 결과를 만들어내는 것이니까요.


📖 참고자료

 

[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
글 보관함