• 도메인 로직을 도메인레이어에 작성 할 수 있을까 에 대해 많이 고민을 했고, 그 해답을 공유 할 것

  • 함수형 프로그래밍 / 객체지향 프로그래밍 둘 다 활용 가능

  • 불변 객체란 무엇일까 ?

    • 변하지 않는 객체

    • 변경 할 수 없는 객체 ?

      val mutableList = mutableListOf()
      
      • 위 mutableList는 불변객체인가 ?
      val mutableNumbers = mutableListOf()
      val numbers: List<Int>
      		get() = mutableNumbers
      
      val mutableNumbers = mutableListOf()
      val numbers: List<Int>
      		get() = mutableNumbers.toList()
      
      • 내부에서 변경한 변경점이 외부로 전달 될 수 있음 / 외부에서 받은 값을 강제 캐스팅을 통해 내부로 전달 될 수 있음
      • 서로 다른 객체 인스턴스를 만든다면, 내부에서 변경하는것 / 외부에서 변경하는것이 전달이 안되어 사이드이펙트가 없어짐
      • 방어적 전파/ 방어적 복사 라는 키워드로 검색해보면 좀 더 많은 정보가 있음
  • 방어적 복사

    • 기존 객체와 똑같은 객체를 만듬
    • 같은 객체끼리의 변경이 공유되는 것을 막기 위해 활용
    • 불변 객체를 만드는 방법이 아님
    • toList / data class의 copy 함수 내부 코드를 좀 더 볼것
  • 왜 방어적 복사를 하는걸까 ?

    data class User(val name: String, val email:String) {
    		init {
    				require(name.isNotBlank())
    				require(isValidEmail(email))
    		}
    
    		private fun isValidEmail(email: String): Boolean
    }
    
    • 위 코드처럼 짜면 장점

      • 잘못된 형식의 유저 객체가 만들어 질 수 없음
      class SomethingRepository {
      		fun doSomething(name: String, email: String) {
      				TODO()
      		}
      }
      
      • 위 코드의 경우엔, name / email이 비어있지 않다고 확신 할 수는 없음
      class SomethingRepository {
      		fun doSomething(user: User) {
      				TODO()
      		}
      }
      
      • 위 코드의 경우엔, name / email이 비어있지 않다고 확신 할 수 있음
  • 불변객체를 통해 가독성 올리기

    • 책 목록을 보여주는 앱을 가정
    data class Book(
    		val name: String,
    		val publishedAt: LocalDateTime,
    		val type: BookType
    )
    
    enum class BookType {
    		Cartoon ...
    }
    
    class BookViewModel : ViewModel() {
    		private val _books = MutableLiveData ... 
    		val books: LiveData<List<Book>>
    				get() = _book
    }
    
    • 위 코드에서, ViewModel에서 만화만 받고싶다 ?
    class BookViewModel : ViewModel() {
    		private val _books = MutableLiveData ... 
    		val books: LiveData<List<Book>>
    				get() = _book
    
    		val cartoonBooks = _books.map ... 
    }
    
    • 확장함수로 더 깔끔하게 만들어보자
    fun List<Book>.filterCartoon(): List<Book> {
    		return filter { it.type == BookType.Cartoon }
    }
    
    val cartoonBooks = _books.map(::filterCartoon)
    
    • filterCartoon 함수는 순수함수가 됨
    • 테스트 용이
    data class Books(
    		private val values: List<Book>
    ) {
    		fun filterCartoon(): Books {
    				return Books(values.filter { it.type == BookType.Cartoon })
    		}
    }
    
    val cartoonBooks = books.map(Books::filterCartoon)
    
    • 순수 함수의 장점을 가져올 수 있고, 클래스로 선언한다는 장점이 있음
    • 클래스로 감쌈으로써 가독성에 굉장한 도움을 줄 수 있음
    • 도메인에 어울리는 다양한 이름이 나올 수 있음
    data class CartoonBooks() ... 
    data class LatestBooks() ... 
    
    • 이렇게 클래스를 나누고, 도메인 로직들을 담아 둘 수 있음
      • 테스트 하기도 쉽고, 도메인 관련된 로직이 도메인 클래스 안에 있음으로써 뷰모델도 가독성이 훨씬 좋아 질 수 있음
    • add / remove 라는 용어 보다는 plus / minus를 활용하는것이 좋음
    • add / remove는 자기 자신에 객체를 더하거나 빼고, 자기 자신을 리턴하지 않음
    • plus / minus는 자기 자신 말고 다른객체를 만들고, 만들어진 객체를 리턴해줌
  • 주의사항 & 팁

    • 네이밍

      • Books ? / Library ? / BookInfo ? ...

      • 뭘로 이름을 짓지 상세히 고민해볼것

      • 추천하지 않는 방식

        data class Books(
        		private val values: List<Book>
        ) : List<Book> by values
        
    • 방어적 복사 후 List 반환

      data class Books(
      		private val values: List<Book> 
      ) {
      		fun toList(): List<Book> = books.toList()
      
      		companion object {
      				fun of() ... 
      		}
      }