[엘레강트 오브젝트] 2-9장 인터페이스를 짧게 유지하고 스마트(smart)를 사용하세요.
이 글은 엘레강트 오브젝트 새로운 관점에서 바라본 객체지향 도서를 보며 스터디한 글입니다.
책에서 주장하는 내용을 정리하였으며 예제들은 모두 코틀린 코드로 변환하여 작성하였습니다.
목차
- 인터페이스를 정의하는 방법
- 스마트 클래스
- 나중에 배울 데코레이터
1. 인터페이스를 정의하는 방법
인터페이스는 구현자에게 너무 많은 것을 요구하면 안됩니다.
// Bad interface..
interface Exchange {
fun rate(target: String): Float
fun rate(source: String, target: String): Float
}
위 코드는 나쁜 설계의 인터페이스입니다. 구현자에게 동일한 기능을 수행하는 rate() 메서드에서 속성값만 다르게 구현하라고 강요하고 있습니다. 이럴 경우 SRP( 단일 책임 원칙 )를 위반하는 클래스를 설계할 가능성을 높입니다.
이 책에서는 위와 같이 설계를 하려면 인터페이스 안에 스마트 클래스를 추가하여 해결 방안을 제시합니다.
2. 스마트 클래스
interface Exchange {
fun rate(source: String, target: String): Float
class Smart { // smart class
private lateinit var origin: Exchange
fun toUsd(source: String) =
this.origin.rate(source, "USD")
}
}
스마트 클래스는 명확하고 공통적인 작업을 수행하는 많은 메서드를 다룰 수 있습니다. 위 Smart 클래스는 아래와 같이 사용할 수 있습니다.
val rate = Exchange.Smart().toUsd("EUR")
하지만 위와 같은 코드를 사용하는 곳이 점점 많이진다면 "EUR" 코드를 계속 작성해줘야하는 단점이 생깁니다. 이 때 새로운 기능을 추가하려고 할때 Smart 클래스를 이용해서 추가할 수 있습니다.
interface Exchange {
fun rate(source: String, target: String): Float
class Smart {
private lateinit var origin: Exchange
fun toUsd(source: String) =
this.origin.rate(source, "USD")
fun eurToUsd() = this.toUsd("EUR") // 추가
}
}
// use
val rate = Exchange.Smart().eurToUsd()
이처럼 많은 기능을 제공하려면 Smart 클래스의 크기는 점점 커질 것입니다. 하지만 interface는 적은 메서드만을 제공할 수 있습니다. 기본적으로 interface는 짧게 만들고 Smart 클래스를 인터페이스와 함께 배포함으로써 공통 기능을 추출하고 코드 중복을 피할 수 있습니다.
3. 나중에 배울 데코레이터
스마트 클래스는 객체에 새로운 메서드를 추가하지만 데코레이터는 이미 존재하는 메서드를 오버라이드하여 유연하게 사용할 수 있습니다.
interface Exchange {
fun rate(origin: String, target: String): Float
class Fast: Exchange {
private lateinit var origin: Exchange
override fun rate(source: String, target: String): Float {
return if (source == target) 1.0f
else origin.rate(source, target)
}
fun toUsd(source: String) = this.origin.rate(source, "USD")
}
}
Fast 클래스는 데코레이터인 동시에 스마트 클래스입니다. Fast 클래스는 rate() 메서드를 오버라이드하여 유연하게 코드를 작성할 수 있습니다.