Closed cwdoh closed 7 years ago
어제 변환 작업을 하다가 궁금한 것이 있어 질문 드립니다! (~클래스 내용이 비슷한 구조라서~)
public class Person {
private String name;
private int age;
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
private class Dog{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
이와 같은 클래스가 있다고 가정하면 이 클래스를 코틀린으로 변환했을때
데이터 클래스로 만들어서 사용하려면 어떻게 해야할까요?
현재 클래스 안에 inner클래스로 추가로 들어가 있는 상황인데 따로 분할하지 않고 사용하는게 가능할지
그리고 이런 상황에서는 데이터 클래스를 사용하는 것이 효율적일지에 대한 의문이 생겨서 질문드려요 !
일단 제가 아직 data class는 공부를 하지 못해서 뭐라고 답변드리기는 어려울 것 같아요. Nested class에 대해 정리를 하고 넘어가는게 옳지 않을까 싶습니다.
이 문서를 참조해보시면 목적은 다음과 같습니다.
궁금한게 Dog이 왜 inner class로 정의된 것인지가 중요할 것 같습니다.
특히 private로 정의되어 있는데, 이는 클래스 내부에서 생성해서 사용하겠다라는 뜻으로 보이거든요. 그렇다면 외부에서 Dog을 생성해서 전달받는 형태는 아닌 것 같기도 하고요.
용도와는 무관하지만 굳이 하자면 외부에 인터페이스가 존재하는 형태여야 할 것 같습니다. 비슷한 사례를 구현해보자면 아래와 같은 경우는 존재할 것 같네요. inner
키워드는 inner class에서 outer class의 멤버를 액세스하는 키워드입니다.
abstract class Pet(val name: String, val age: Int) {
abstract fun getHomeAddress() : String
}
class Person(val name: String, val age: Int, var homeAddress: String = "Unknown") {
private inner class MyDog(name: String, age: Int, val type: String = "Unknown") : Pet(name, age) {
var nickName: String = ""
// method will be used within Person class
override fun getHomeAddress() : String = homeAddress
}
private val dog : MyDog = MyDog("똘똘이", 3, "믹스").apply {
nickName = "똘이"
}
fun getDog() : Pet? = dog as Pet
}
아, 그래서 결론은...
- private class Dog 과 outer class에 존재하는 getDog() 등의 접근은 코드 상으로는 동작하겠지만 구조는 이상해보여요.
- 저는 data class를 잘 모릅니다. ㅋ 이 부분은 @tonyjs @taehwandev @Pluu @ganadist 님을 소환해봅니다.
data class 용도 자체가 어렵고 그렇지 않아요. https://kotlinlang.org/docs/reference/data-classes.html
The compiler automatically derives the following members from all properties declared in the primary constructor:
equals()/hashCode() pair,
toString() of the form "User(name=John, age=42)",
componentN() functions corresponding to the properties in their order of declaration,
copy() function (see below).
자바에서 해당 클래스 같은 지를 비교해야 할 때 보통 equals
와 hashcode
를 override 하잔아요? 이걸 자동으로 해주는 용도도 있고
ex)
Dog dog = new Dog();
...
List<Dog> dogs = new ArrayList<>();
dogs.add(dog);
...
dogs.indexOf(dog);
toString
같은 것도 override 해주고 등등 Immutable 유틸리티? 적인 측면이 많죠.
Nested class 를 data class 로 만들어야 할까? 는 작업 방향에 따라 달라질 것 같아요.
Person
이 같은지를 비교하려면 Person
이 가진 Dog
도 같은지를 비교해야 된다면 Dog
도 data class
로 지정해주던가 Person
의 equals
, hashcode
를 override 해줘야 겠죠?
좋은 내용 감사합니다!
제가 위의 코드를 사용하고자 했던 의도는 아래의 의도가 더 가까운것 같습니다.
컴퓨터 클래스가 있는데 컴퓨터마다 내부 디테일한 스팩들이 있다.
스팩은 컴퓨터에 종속(?) 되는 개념이니깐 내부클래스로 선언하자
(~아직 코알못 신입이라 제가 객체지향을 잘 생각하는지는 모르겠어요~)
public class Computer{
private String name;
private String brand;
...
private class Spec{
private String cpu;
private String ram;
...
}
}
제가 의도한 바의 코드는 약간 이쪽이 좀 더 가까울 것 같습니다. 말해주신 내용 공부를 열심히 해봐야되겠네요
감사합니다 ^^
@soulduse 설계의 문제에 가까운 것 같아요.
abstract class Spec(val cpu: String, val ram: Int)
open class Computer(
cpu: String,
ram: Int,
val storage: String = "ssd"
) : Spec(cpu, ram)
class BrandComputer(
val model: String,
val brand: String,
cpu: String,
ram: Int,
storage: String
) : Computer(cpu, ram) {
fun getDescription() : String = """
|$brand $model has:
|- $cpu CPU
|- ${ram}mb memory
|- $storage storage
""".trimMargin()
}
fun main(args: Array<String>) {
val computer = BrandComputer(
brand = "Kotlin",
model = "STUDY_MACHINE",
cpu = "i7",
ram = 8096,
storage = "hdd"
)
println(computer.getDescription())
}
위와 같은 전통적 상속이라던지 혹은 인터페이스를 이용해서 아래처럼
interface BoardSpec {
fun getCPU() : String
fun getMemoryCapacity() : Int
}
interface ComputerSpec : BoardSpec {
fun getStroage(): String
}
class ComputerModule(
val cpu: String,
val ram: Int,
val storage: String = "ssd"
) : ComputerSpec {
override fun getCPU() : String = cpu
override fun getMemoryCapacity() : Int = ram
override fun getStroage(): String = storage
}
// Coding convention from https://kotlinlang.org/docs/reference/coding-conventions.html
class BrandComputer(
val model: String,
val brand: String,
computerModule: ComputerModule
) : ComputerSpec by computerModule {
fun getDescription() : String = """
|$brand $model has:
|- ${getCPU()} CPU
|- ${getMemoryCapacity()}mb memory
|- ${getStroage()} storage
""".trimMargin()
}
fun main(args: Array<String>) {
val module = ComputerModule("i7", 8096, "hdd")
val computer = BrandComputer(
brand = "Kotlin",
model = "STUDY_MACHINE",
computerModule = module
)
println(computer.getDescription())
}
data class는 공부해보고 추가할께요.
delegation 공식 자료 https://kotlinlang.org/docs/reference/delegation.html
Delegation은 #25 를 참조하셔도 됩니다.
왜 field가 아니고 property라고 부르는가에 대해 고민해봤는데, 많이 살펴보지는 않았지만, 괜찮은 답변이 있다. (솔직히 필드와 프로퍼티는 코드 수준이라기 보다는 개념적인 느낌만 가지고 있어 매번 헷갈리는 부분이다. ㅜ)
간략하게 field를 정적이지 않은 인스턴스의 멤버로, property를 getter/setter를 가진다는 의미로 보았을 때 Android 내부의 convention인 m prefix는 그다지 어울리지 않는다. 이는 Naming Convention 등에도 영향을 미칠 수 있는 부분이다.
예를 들어
var mName: String
을 지정했을 때 생성된 자바 코드에서getMName()
,setMName()
과 같이 애매한 메소드가 탄생하기 떄문이다. 아래의 경우를 보자기본적으로 멤버 등의 prefix rule은 만들지 말자.
굳이 이런 룰을 벋어난다면 아래처럼 getter/setter가 없는 경우 정도겠지만, 상수의 경우는 더더욱 mPrefix가 어울리지 않는다. 그렇다면
@JvmField
에만 해당될 수도 있겠다.