술(述)/풀이

LSP

쪼랩전사 2021. 12. 10. 20:03
728x90

정의

SOLID 중 L 에 해당한다.

리스코프 치환 원칙(Liskov Substitution Principle)은 Barbara Liskov(바바라 리스코프)가 처음 소개했다.

즉, 리스코프가 개발한 치환 원칙이다.

그렇다면 뭘 치환[각주:1]할까?

바로 상위 타입하위 타입으로 치환하는 것을 말한다.

이때 하위 타입으로 치환하더라도 치환하기 이전처럼 정상적으로 동작해야 한다는 것이 리스코프 치환 원칙이다.

위반사례

전통적으로 직사각형/정사각형 문제가 있다.

type Tetragon interface {
	SetHeight(height uint64)
	SetWidth(width uint64)
	Area() uint64
}

위 예시처럼 사각형(tetragon)이라는 인터페이스가 존재할 때, 직사각형과 정사각형을 구현하면 아래와 같아진다.

 

- 직사각형(rectangle)

type Rectangle struct {
	height, width uint64
}

func NewRectangle(height uint64, width uint64) *Rectangle {
	return &Rectangle{height: height, width: width}
}

func (r *Rectangle) SetHeight(height uint64) {
	r.height = height
}

func (r *Rectangle) SetWidth(width uint64) {
	r.width = width
}

func (r *Rectangle) Area() uint64 {
	return r.width * r.height
}

- 정사각형(square)

type Square struct {
	side uint64
}

func NewSquare(side uint64) *Square {
	return &Square{side: side}
}

func (s *Square) SetHeight(height uint64) {
	s.side = height
}

func (s *Square) SetWidth(width uint64) {
	s.side = width
}

func (s *Square) Area() uint64 {
	return s.side * s.side
}

위 구조체들의 테스트는 아래와 같다.

func TestTetragonal(t *testing.T) {
	var tetragons []Tetragon

	tetragons = append(tetragons, NewRectangle(1, 1))
	tetragons = append(tetragons, NewSquare(1))

	for i, tetragon := range tetragons {
		t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
			tetragon.SetWidth(5)
			tetragon.SetHeight(2)
			area := tetragon.Area()
			if area != 10 {
				t.Fatalf("%v is not 10", area)
			}
		})
	}
}

위의 코드는 너비를 5로, 높이를 2로 변경 후, 면적을 구할 때 어떻게 나오는지 테스트하는 것이다.

사각형의 테스트로서는 당연히 10이라는 값이 나와야 하지만, 정사각형은 높이와 너비가 같으므로 다른 값이 나와 테스트를 실패하게 된다.

 

더보기

위 예시와는 다르게, 다른 곳에서는 Square 구조체가 Rectangle 구조체를 상속받아서 테스트를 진행하는 것도 있으나, 이거나 그거나 똑같은 개념이다. 위의 예시는 Tetragon이라는 상위 타입이, 다른 곳의 예시는 Rectangle 자체가 상위 타입이 되는 것뿐이다.

삽질기

왜 이런 불상사가 생길까? 위와 같은 실수가 없을 것 같지만, 실제로는 저런 상황이 종종 발생한다.

필자가 생각하는 이유는 아래와 같다.

1. 성급한 추상화[각주:2]

  - 이렇게 하면 코드가 더 이뻐지지 않을까? 라는 식으로 추측하며, 구현하지도 않아본 것들을 미리 추상화시켜버리는 것이 문제이다. 솔직히, 여기까지는 괜찮다. 추상화를 빠르게 포기하고 구체클래스를 집어넣으면 된다. 그런데, 이를 포기 못하고, 이리저리 바꿔보며 시간 낭비만 하게 된다.

2. 너무 많은 추상화

  - 추상화에 너무 많은 함수를 넣어 버리는 것이다. 위의 예시도 사실 setHeight, setWidth 함수의 추상화를 포기하면 된다. 그러면 너무나도 간단해진다. 왠지 추상화를 쓰면 메소드를 어느 정도 넣어야 할 것 같은 압박감에 시달린다. 그냥 필요할 때, 필요한 만큼만 추가하면 된다.

마치며...

이 글을 쓰면서도, 코딩하면서도 느끼는 것이지만, 추상화를 너무 성급하게 하지 말자. DIP을 적용해야 하는 상황이 아니라면, 일단 구현을 해보고 비슷한 것만 추상화를 그때그때 필요에 따라 조금씩 하자.


  1. 네이버 사전: 바꾸어 놓다. [본문으로]
  2. Go 에서는 인터페이스 [본문으로]

'술(述) > 풀이' 카테고리의 다른 글

ISP  (0) 2021.12.29
SRP  (0) 2021.12.13
bash 설정 파일  (0) 2021.12.05
code bloat(코드 비대화)  (0) 2021.11.24
headless server(헤드리스 서버)  (0) 2021.11.22