728x90
- Kotlin에서는 데이터 집합을 표현할 때 data 클래스를 사용하면 여러 가지 편리한 기능을 자동으로 제공받을 수 있습니다. data 클래스를 사용하면 코드가 간결해지고, 데이터 객체의 비교, 복사, 문자열 표현 등을 쉽게 처리할 수 있습니다.
- equals: 객체의 내용을 비교합니다.
- hashCode: 객체의 해시 코드를 생성합니다.
- toString: 객체의 문자열 표현을 제공합니다.
- copy: 객체를 복사할 수 있습니다.(얕은 복사)
- componentN: 객체의 각 속성에 접근할 수 있습니다.
// 얕은복사
data class Address(var street: String, var city: String)
data class Person(var name: String, var address: Address)
fun main() {
val originalAddress = Address("Main St", "Springfield")
val person1 = Person("John", originalAddress)
val person2 = person1.copy()
// 두 객체가 동일한 내부 객체를 참조
println(person1.address === person2.address) // true
// person2의 주소를 변경
person2.address.street = "Elm St"
// person1의 주소도 변경됨
println(person1.address.street) // Elm St
println(person2.address.street) // Elm St
}
// 깊은복사
data class Address(var street: String, var city: String)
data class Person(var name: String, var address: Address) {
fun deepCopy(): Person {
return Person(name, Address(address.street, address.city))
}
}
fun main() {
val originalAddress = Address("Main St", "Springfield")
val person1 = Person("John", originalAddress)
val person2 = person1.deepCopy()
// 두 객체가 동일한 내부 객체를 참조하지 않음
println(person1.address === person2.address) // false
// person2의 주소를 변경
person2.address.street = "Elm St"
// person1의 주소는 변경되지 않음
println(person1.address.street) // Main St
println(person2.address.street) // Elm St
}
- 명확성: 데이터 클래스는 각 필드에 의미 있는 이름을 부여할 수 있어 코드의 가독성을 높입니다.
- 자동 생성 메서드: 데이터 클래스는 equals, hashCode, toString, copy, componentN 메서드를 자동으로 생성합니다. 이를 통해 객체 비교, 해시 코드 생성, 문자열 표현, 객체 복사, 구조 분해 선언 등이 간편해집니다.
- 구조 분해 선언: 데이터 클래스는 구조 분해 선언을 통해 객체의 각 필드를 개별 변수로 쉽게 추출할 수 있습니다.(아이템 2)
- 불변성: 데이터를 불변 객체로 쉽게 만들 수 있어 데이터의 일관성을 유지하고, 예기치 않은 변경으로부터 보호할 수 있습니다.
아래는 data class 를 상속받아 사용하고싶은 니즈와, response 가 대부분 비슷한데, 한두개만 달랐을때 어떻게 사용하면 좋을까 라는 동료의 질문에 궁금해서 찾아본 코드들..
@GetMapping("/response1")
fun getResponse1(): Any {
val commonFields = CurationRecCommonFields(
recType = "Type1",
iids = "Item IDs 1",
cids = "Category IDs 1",
exiids = "Excluded Item IDs 1",
excids = "Excluded Category IDs 1",
someCommonField = "Some common data"
)
val result = CurationRecApiResponseDto(common = commonFields, additionalField1 = "Additional data 1")
return try {
return responseEntityOk(BAD_REQUEST, null, result, null)
} catch (e: Exception) {
logger.error("getResponse1 error", e)
return responseEntityOk(BAD_REQUEST, null, null, null)
}
}
@GetMapping("/response2")
fun getResponse2(): Any {
val commonFields = CurationRecCommonFields(
recType = "Type2",
iids = "Item IDs 2",
cids = "Category IDs 2",
exiids = "Excluded Item IDs 2",
excids = "Excluded Category IDs 2",
someCommonField = "Some other common data"
)
val result = CurationRecApiResponseDto2(common = commonFields, aa = "Different field")
return try {
return responseEntityOk(BAD_REQUEST, null, result, null)
} catch (e: Exception) {
logger.error("getResponse2 error", e)
return responseEntityOk(BAD_REQUEST, null, null, null)
}
}
data class CurationRecCommonFields(
var recType: String? = null,
var iids: String? = null,
var cids: String? = null,
var exiids: String? = null,
var excids: String? = null,
var someCommonField: String? = null // 임시로 추가한 필드
)
data class CurationRecApiResponseDto(
@JsonUnwrapped
val common: CurationRecCommonFields,
val additionalField1: String? = null
)
data class CurationRecApiResponseDto2(
@JsonUnwrapped
val common: CurationRecCommonFields,
val aa: String? = null
)
위 호출내역
// http://localhost:8080/discovery/api/v1/live-shop/response1
{
"status": "SUCCESS",
"code": 200,
"message": null,
"data": {
"recType": "Type1",
"iids": "Item IDs 1",
"cids": "Category IDs 1",
"exiids": "Excluded Item IDs 1",
"excids": "Excluded Category IDs 1",
"someCommonField": "Some common data",
"additionalField1": "Additional data 1"
},
"pagination": null
}
---------------
// http://localhost:8080/discovery/api/v1/live-shop/response2
{
"status": "SUCCESS",
"code": 200,
"message": null,
"data": {
"recType": "Type2",
"iids": "Item IDs 2",
"cids": "Category IDs 2",
"exiids": "Excluded Item IDs 2",
"excids": "Excluded Category IDs 2",
"someCommonField": "Some other common data",
"aa": "Different field"
},
"pagination": null
}
- 좋은방법인지는 모르겠으나, 보일러 플레이트 코드가 너무 과하게 많을때, 단순하게 DTO 를 반환할대는 괜찮아보인다.
데이터 클래스의 문제점
- 엔티티 클래스로는 사용안하는 것을 권장
- 1:N 관계에서 순환 참조로 인해 StackOverFlow Error 발생할 수 있다.
- 해결 방안: toString, equals, hashCode를 오버라이드해서 순환 참조를 방지한다. 또는 한쪽에서 삭제한다.
- https://stackoverflow.com/questions/48926704/kotlin-data-class-entity-throws-stackoverflowerror
- copy의 얕은 복사로 인해 참조가 복사되어 문제가 발생할 수 있다.
- Hibernate의 Lazy Loading을 사용하기 위해서는 데이터 클래스를 사용할 수 없다. (프록시 객체 문제)
- https://velog.io/@donghyunele/kotlin-jpa-2-%EC%9A%B0%EC%95%84%ED%95%9C%ED%98%95%EC%A0%9C%EB%93%A4-%EA%B8%B0%EC%88%A0%EB%B8%94%EB%A1%9C%EA%B7%B8
- 자동으로 만들어주는 함수 때문에 용량이 크다. (App의 경우는 용량에 대한 이슈가 있을 수 있다.)
갓현호님b 감사감사!
728x90
'공부 > 이펙티브코틀린' 카테고리의 다른 글
아이템39 - 태그 클래스보다는 클래스 계층을 사용하라 (0) | 2024.08.01 |
---|---|
아이템 38 - 연산 또는 액션을 전달할 때는 인터페이스 대신 함수 타입을 사용하라 (0) | 2024.08.01 |
아이템 36 - 상속보다는 컴포지션을 사용하라 (0) | 2024.07.14 |
아이템35 - 복잡한 객체를 생성하기 위한 DSL을 정의하라 (0) | 2024.07.11 |
아이템 34 - 기본 생성자에 이름 있는 옵션 아규먼트를 사용하라 (0) | 2024.07.11 |
댓글