✨ 오늘 공부한 것
- 알고리즘 예상 대진표 풀이 - 문법 특강 복습 - 개인 과제 구조 설계
지난 번 정리에 이어 오늘은 개방 폐쇄 원칙과 리스코프 치환 원칙, 인터페이스 분리 원칙에 대해 알아보았다.
사실 이전에 결합도가 낮은 클래스를 설명할 때, 개방 폐쇄의 원칙을 잘 지킨 클래스라고 했었다. 오늘은 이 원칙에 대해 조금 더 알아보려고 한다.
개방 폐쇄 원칙은 확장에 대해 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다는 원칙이다.
추상화에 의존하면 어렵지 않게 구현할 수 있다고 한다. 변하지 않는 부분만 남겨놓고, 변하는 부분은 생략하여 추상화 한다면 나중에 생략된 부분을 수정하는 방식으로 개방 폐쇄 원칙을 지킬 수 있다고 한다!
그럼 맥X날드의 햄버거로 예시를 들어보자.
위와 같은 경우는 개방 폐쇄 원칙을 과연 지켰을까? 결제 방식만 바꾸면 되는데 맥X날드 클래스까지 수정했기 때문에 원칙을 지키지 못했다고 할 수 있다. 그럼 어떻게 해야 됐을까?
카드로 “결제”하고, 토스 페이로 “결제”한다. 어떤 방식을 사용하던 “결제”를 한다는 것은 동일하므로, 우리는 Payment란 클래스 안에 pay라는 메소드를 일단 선언해놓고 보는 것이다.
abstract class Payment {
fun pay()
}
그 이후에 이 Payment 클래스를 구체화해서 카드 결제로 구현하던, 토스 페이로 구현하던 맥X날드 클래스에서는 Payment().pay()
를 사용하면 된다! 즉, 맥X날드 클래스의 수정 없이 기능 추가가 가능하다. 이 경우는 개방 폐쇄 원칙을 잘 지켰다고 할 수 있다.
핵심은 추상화를 통해 “변하지 않는 것들에 의존”해야 한다는 것임을 잊지 말자. 반대로 변하는 것들은 생략하면 된다!
하위 타입은 상위 타입을 대체할 수 있어야 한다는 원칙이다. 즉, 상속 관계에서 자식 타입은 부모 타입으로 교체할 수 있어야 한다는 뜻이다. 이 문장을 읽으면 바로 다형성이 생각날 것이다. 리스코프 치환 원칙은 다형성을 이용하기 위한 원칙이라고 한다.
일반 회원 가격과 멤버십 회원 가격이 다른 상품을 파는 주얼리 사이트로 예를 들어보자.
지금까지 작성된 클래스는 다음과 같다.
abstract class Product() {
var generalPrice: Int = 0
var membershipPrice: Int = 0
abstract fun setPrice(general: Int, membership: Int)
}
class Ring() : Product() {
override fun setPrice(general: Int, membership: Int) {
// general로 일반 회원과 멤버십 회원의 가격을 설정
super.generalPrice = general
super.membershipPrice = general
}
}
class Necklace() : Product() {
override fun setPrice(general: Int, membership: Int) {
super.generalPrice = general
super.membershipPrice = membership
}
}
이렇게 구현한 뒤, 다른 사람이 상품들의 가격을 타입 상관 없이 조정하기 위해 다음과 같은 함수를 만들었다고 하자.
fun changePrice(item: Product, newGeneralPrice: Int, newMembershipPrice: Int) {
item.setPrice(newGeneralPrice, newMembershipPrice)
}
이걸 구현한 사람은 반지 클래스도 일단은 상품이니까 일반 회원 가격과 멤버십 회원 가격을 각각 설정해주고 싶었다. 따라서 반지 인스턴스를 만들어 다음과 같이 changePrice를 호출하게 된다.
changePrice(ring, 1400, 1000)
그러나 코드는 의도한 대로 동작하지 않고, 일반 회원 가격과 멤버십 회원 가격 모두 1400원으로 설정되는 현상이 발생한다.
설명을 위해 살짝 무지성으로 예를 들어서 좀 이상할 수도 있지만, 이런 상황과 같이 자식 클래스는 부모 클래스가 의도한 동작에서 벗어나면 안된다. 이 예시에서 부모 클래스가 의도한 동작은, 값 2개를 받아 각각 일반 회원 가격, 멤버십 회원 가격에 설정해주는 것이다. 그러나 반지 클래스는 이를 어기고 값 하나로 모든 가격을 설정해주었다.
리스코프 치환 원칙은 자식 클래스가 부모 클래스를 대체할 수 있어야 하는 원칙이기 때문에, 예시와 같은 상황이 발생하지 않게 작성해야 원칙을 지킬 수 있다.
인터페이스를 각각 용도에 맞게 잘게 분리해야한다는 원칙이다. 인터페이스는 다중 상속이 가능하기 때문에 하나의 인터페이스에 많은 기능을 넣는 것보다, 목적과 용도에 맞게 적은 기능으로 분리하는 것이 인터페이스 분리 원칙을 지킬 수 있는 방법이다.
원칙이 5개나 있는데 2개만 해놓고 나몰라라 하는 건 좀 아닌 것 같아 마저 정리해보았다. 상속은 어떤 때에 사용하는 것인지 대충은 알 것 같다. 정말 대충..