OOP : 일급컬렉션

Murjune·2024년 3월 15일
2

OOP

목록 보기
2/2
post-thumbnail

Intro

우테코에서는 객체 지향 프로그래밍 에 대해 굉장히 강조하고 있고, 이를 실천하기 위해 소트웍스 엔톨로지객체지향 생활 체조 원칙을 지키면서 미션을 수행하도록 가이드하고 있다.

지난 포스팅에서 원시값 포장
에 대해서 정리를 하였고, 이번에는 일급 컬렉션 에 대해 정리하겠다!!

스포를 하자면 일급 컬렉션 은 원시값 포장과 거의 비슷하다.
kotlin 혹은 java에서 제공하는 Collection (List, Map, ...) 을 class 로 wrapping하여 의미 있는 객체(Entity)로 만드는 것이다.

지난 원시값 포장에서 사용했던 Member 의 멤버변수인 subways를 일급 컬렉션를 활용해 개선해나간 경험을 공유하려 한다.

1️⃣ 일급 컬렉션을 적용하기 전 Member

data class Member(
	...
    val subways: Set<SubwayStation>,
) {
	...
    fun matchSubwayLine(other: Member): SubwayLine? {...}

    fun matchSubwayStation(other: Member): SubwayStation? {...}

    fun matchSubwayStations(other: Member): List<SubwayStation> {...}
    
    ...

현재 Member 는 다른 Member 와 일치하는 지하철 역 혹은 지하철 호선 을 가져오고 있다.

fun matchSubwayLine(other: Member): SubwayLine? {
        subways.forEach { subway ->
            val matchedLine = other.subways.find { it.matchSubwayLine(subway) != null }
            if (matchedLine != null) return subway.matchSubwayLine(matchedLine)
        }
        return null
    }

그중에 다른 사용자와 공통된 지하철 노선을 찾아 반환해주는 matchSubwayLine 함수다.

과연 이 코드는 객체지향적인가??

❌ 현재 matchSubwayLineSet<Subway>의 자율성을 침해하고 있다 😱

일치하는 지하철 노선을 찾는 로직을 Set<Subway> 스스로가 하고 있지 않고, getter()를 통해
Subway 들을 가져와 Member 에서 수행하고 있다.

이는 객체 지향 생활 체조 원칙 9 : getter/setter/프로퍼티 사용 금지 에 위반되는 행위이다.

객체 지향 생활 체조 원칙 9: getter/setter/프로퍼티 사용 금지

이에 관련해서 2기 오렌지 센빠이가 잘 정리해주신 이 있다.

이 원칙은 getter를 사용해서 객체의 상태를 외부로 꺼내와서 조건에 따라 값을 변경한다거나, 직접 로직을 수행하는 것이 아닌 객체가 스스로 수행할 수 있어야 한다를 강조한다.

matchSubwayLine() 와 같이 무분별하게 Set<Subway> 의 상태 를 외부에서 꺼내 로직을 처리하게되면, Set<Subway> 의 세부사항이 변경되게되면 Member 또한 변경되어야 한다.

Member 뿐 만 아니라 다른 class에서도 Set<Subway> 의상태를 getter() 로 가져와서 로직을 처리하면 결국 변경해야하는 코드의 범위가 넓어지게 된다. 결국 Set<Subway> 와 다른 class의 이는 결합도가 높기 때문에 확장성과 유지보수성이 떨어지게 된다.

SRP 위반! 결합도 🆙 응집도 ↓ 유지 보수성 ↓ 확장성 ↓ 🥹

(저번에 포스트한 원시값 포장 글을 잘 이해했다면 이해하기 쉬울 것이다 😸)

따라서, Set<Subway>matchSubwayLine() 를 수행할 수 있도록 하고 싶지만 Set 은 코틀린에서 제공하는 기본 api 이다.

따라서, class 로 Set<Subway> 를 wrapping 하여 Subways 를 만들어 책임을 부여했고 이를 일급컬렉션이라 한다.

🤔 확장함수로도 책임을 부여할 수 있는거 아닌가요??
확장 함수로 책임을 부여할 수는 있으나, public한 확장함수는 오히려 동료 개발자가 stdlib 에서 제공하는 함수인줄 오인할 수 있고, 수많은 함수들 때문에 찾기도 힘들다.. 따라서, 비추함!😅

확장 함수 형태로 만들었다고 가정해보자!

fun Set<SubwayStation>.matchSubwayLine(other: SubwayStations): SubwayLine? {...}
  • 수많은 collection 함수들 때문에 찾기가 힘들다..

2️⃣ SubwayStations 일급컬렉션 적용

@JvmInline
value class SubwayStations(val stations: Set<SubwayStation>) {

    fun matchSubwayLine(other: SubwayStations): SubwayLine? {
        stations.forEach { subway ->
            other.stations.forEach { otherSubway ->
                val matchedLine = subway.matchFirstLine(otherSubway)
                if (matchedLine != null) return matchedLine
            }
        }
        return null
    }

    fun matchSubwayStation(other: SubwayStations): SubwayStation? {...}

    fun matchSubwayStations(other: SubwayStations):List<SubwayStation {...}
}

Member 에 있었던 Set<SubwayStation> 가 수행할 책임들은 이제 SubwayStations 이 담당한다!

따라서, 외부에서 Set<SubwayStation> 를 직접 getter로 요소들을 뽑아와서 로직 처리를 하지 않고, SubwayStations 에게 메세지를 보내 요청만 하면 된다.(응집도 🆙)

변경에 유연한 일급컬렉션

상대방과 만나기 좋은 지하철역들을 추천해주는 기능이 생겼다고 가정해보자!

만약, SubwayStations 가 없었다면 recommend 관련 모듈에서 Set<SubwayStation> 의 상태를 getter()로 가져와 로직을 수행할 것이다.
그러면, Set<SubwayStation>recommend 의 결합도가 올라갔을 것이고, Set<SubwayStation> 의 응집도는 내려가 추후 변경사항에 유지보수하기 힘들었을 것이다.

// recommend 모듈 - 예시
fun recommendSubwayStation(subways: Set<SubwayStation>, otherMemberLocation: location): List<SubwayStation> {}

그러나, 현재 우리는 SubwayStations(일급컬렉션)이 이에 대한 로직을 수행할 수 있다 😸

@JvmInline
value class SubwayStations(val stations: Set<SubwayStation>) {
	...
	
    fun findCloseSubwayStations(otherMemberLocation: Location): List<SubwayStation> {...}
    ...
}

정리

일급컬렉션은 불명확한 의미를 갖은 일반 Collection 을 class 로 감싸 Entity로 만들어 관련된 상태와 행위를 한 곳에서 관리한다.
일급컬렉션을 잘 활용한다면 유연한 어플리케이션을 만들 수 있는 기반이 될 것이다!! 끗

Reference

향로님의 블로그

  • 소틀로지 블로그

규칙 8 - 일급컬렉션 사용하라

profile
열심히 하겠슴니다:D

0개의 댓글