QnA 카테고리에는 학습 중 궁금했던 내용을 질문하고 그에 대해 오갔던 이야기와 당시에 들었던 생각들을 담습니다.
이야기 이후의 고민 과정은 해당 링크에서 확인하실 수 있습니다.
ME
생성자 오버로딩과 this()메서드에 대해서 공부하다보니, setter와 용도에서 큰 차이가 있나 싶은 의문이 생깁니다. 물론 기능을 지원한다는 것은 어딘가 쓸모가 있으니까 지원하는 것이겠지만, 어느 부분에서 this()메서드, 생성자 오버로딩이 필요할지 아직은 가늠이 잘 안되네요. 불완전한 객체라면 불완전한 생성자를 통해 인스턴스를 만들고 setter를 활용하면 되지 않나 싶어서요.
예를 들어, 공연장의 좌석이라는 객체가 있다면, 좌석 번호와 좌석에 앉을 사람 데이터가 필요할겁니다. 좌석의 번호는 객체 인스턴스를 생성하는 단계에서 생성자를 통해 넘겨줄 수 있겠지만, 좌석에 앉는 사람의 정보는 사람이 앉아야 알 수 있으니 setter로 추가적으로 초기화하는거죠.
데이터의 변경 가능성에 대한 여부 때문에 setter를 잘 사용하지 않는 것은 알겠지만, setter가 필요한 경우도 반드시 있다고 생각이 됩니다. setter보다 생성자 오버로딩과 this()메서드의 활용이 더 효율적인 구체적인 예시나 사례가 있을까요??
🔎질문을 한 이유
어떤 객체를 사용하고자 한다면, 해당 객체를 구성하고 있는 필드의 변수들을 모두 채워줘야 한다고 생각했다. 만일 객체를 생성할 당시에는 알 수 없는 정보가 있다면 setter를 통해서 추후에 해당 객체의 내용을 채워주는 형식으로 사용하면 될 것이라고 생각했다.
그런데 생성자 오버로딩은 그런 개념 자체가 다르게 적용되는 것 같았다. 필드의 여러 값들을 굳이 서로 다른 생성자 argument를 통해서 다르게 채워준다는 개념이 다가오지가 않았다. 생성자를 통해서 전달해줘야하는 것은 '객체 인스턴스를 초기화 하기 위해서 반드시 필요한 정보'라고 생각했는데, 어떤 경우에는 a 값만, 어떤 경우에는 a-b값을 동시에 전달한다는게 납득이 되지가 않았다.
객체를 완성시키기 위한 정보라면, 추후에 setter를 통해서 채워주면 되는데, 왜 '반드시 필요한 값'을 넣어주는 생성자를 여러 개로 만들까? 왜 필요하지 않은 값을 굳이 생성자를 통해 넣어줄까? 라는 생각이 질문의 시작이었다.
Crew A
생성자 오버로딩 같은경우는 오버로딩에 따른 여러가지 Value Object를 넘겨주면서 해당 Value Object를 통해 해당 클래스의 데이터를 설정함으로써 불변을 유지할수 있죠 반면에 setter를 쓰게되면 불변은 깨지게 되다보니 트레이드오프를 생각하면서 쓰는게 좋다고생각합니다. 또한 생성자로 넘겨진 객체에 따라서 다르게 동작시킬때도 사용할 수 있겠네요. this() 같은경우는 써보질 않아서 잘모르겠습니다
👀 해당 답변을 보고 든 생각
오케이. 생성자를 통해 넘겨준 값은 immuable 한 것은 알겠다. 그런데, 그렇게 중요한 데이터면 모든 생성자에서 반드시 넣어줘야 하는거 아닌가? 내가 궁금한 건 '있을 수도 있고 없을 수도 있는 데이터값'을 왜 '클래스 단위'에서 선언하며, 그 값을 생성자를 통해 '넣어 줄 수도 있고, 안 넣어 줄 수도 있는지'인데... 이해가 잘 안되네.
ME
흠... 생성자 오버로딩을 통해서 객체 인스턴스를 생성하면 immutable 한 것은 이해가 됩니다.
그런데 생성자 오버로딩을 통해서 객체에 필요한 데이터가 여러 variation으로 전달이 된다면, 결국 '비어있는 데이터'가 있는데도 그 자체만으로 완전한 객체 인스턴스의 형태가 있어야 한다는건데, 이게 이해가 잘 안되네요
. 지금 드는 생각은 '음료' 정도가 있는데요. Beverage 라는 클래스에는 뭐 주류도 있을거고, 탄산음료도 있을거고, 무설탕 탄산음료도 있을 수 있겠죠. 그러면 각 객체는 alcohol이라는 데이터가 있을 수도 있고 없을 수도 있고, 아니면 sugar가 그럴 수도 있고요.
그런데 이런 경우에는 Beverage를 인터페이스로 만들고 이를 구현한 구현체를 나누면 되지 않나 싶단 말이죠.
핵심은 저거인 것 같아요. '왜 굳이 없어도 되는 데이터를 필드에 지정하고 생성자 오버로딩으로 여러 형태의 param을 전달하는 생성자를 만들까.' 이 의문이 해결이 안되네요.
Crew B
예를 들면 Integer.parseInt() 메서드가 예시가 되지 않을까요. 우리는 기본적으로 10진법을 사용하니 보통은 매개변수가 1개지만 필요시 다른 진법을 기준으로 정수를 생성해주는 것을 동일한 메서드로 처리했죠.
이처럼 대다수의 경우 고정된 파라메터라 생성자에서는 생략하지만 일부의 경우에만 추가적으로 생성할때 변경해주는 경우를 고려하여 생성자 오버로딩을 하는 게 효율적이지 않을까요?
👀 해당 답변을 보고 든 생각
대충 이해가 되는 것 같기도 하고... 그러니까 일반적인 경우에는 고정된 값을 지니는데, 필요한 경우에만 생성자를 통해 데이터를 전달하고 이를 수정한다는 의미인가?
Crew C
저는 생성자와 setter의 역할이 다르다고 생각이 들고 생성자를 할 때 객체가 생성되면서 기본적으로 해주고 싶은 영역이 있을수고 있습니다. 예를 들어서
public class BlackBox {
String modelName;
String resolution;
int price;
String color;
static boolean canAutoReport = false; // 자동신고기능 // static : 클래스 변수 모든 인스턴스 객체가 가지고 있는 값이 됨
int serialNumber;// 시리얼 번호
static int counter = 0;
BlackBox() {
System.out.println("기본 생성자 호출");
this.serialNumber = ++counter;
System.out.println("새로운 시리얼 넘버를 발급 받았습니다 : " + this.serialNumber);
}
BlackBox(String modelName, String resolution, int price, String color){
this(); // 기본 생성자 호출
System.out.println("사용자 정의 생성자 호출");
this.modelName = modelName;
this.resolution = resolution;
this.price = price;
this.color = color;
}
}
이런 블랙박스를 위한 클래스를 만든다고 할 때 모델명과 해상도 등등의 것은 아직 결정하지 않았지만 일단 serialNumber 줘서 생성해놓고 싶을 수도 있습니다. 아니면 매개변수를 줘서 처음부터 생성하고 싶을 수도 있고요
그리고 기본 생성자를 보면 이 클래스를 통해 객체를 생성할 때 기본적으로 해주고 싶은 기능들이 담겨 있는데 이거를 생성자 오버로딩을 할 때 또 적어줄 필요 없이 this()를 해주면 기본생성자에 있는 기본 기능들을 손쉽게 가져 올 수 있는 거죠.
그리고 기본 생성자만 이용해서 만들어 두었던 블랙박스 모델에 있어서 내가 사용자에게 바꿀 수 있도록 허락한 데이터만 바꿀 수 있도록 setter를 지정해주는 겁니다.
👀 해당 답변을 보고 든 생각
그러니까, 시리얼 넘버라는 데이터는 반드시 있어야되니까 기본 생성자에 반드시 필요한 데이터를 다루는 기능을 추가하고, 비어있는 데이터는 차후에 setter를 통해서 추가를 한다. 다른 데이터가 추가적으로 결정되어있다면 그건 오버로딩 된 생성자를 통해서 같이 넣어주고 this()를 통해서 기본 생성자까지 실행해서 반드시 필요한 데이터인 시리얼 넘버까지 설정을 한다. 이렇게 이해가 되네.
Coach A
안녕하세요 JK님,
setter는 의미적으로는 '수정자'라는 것으로 볼 수 있으나, setter는 단순히 필드의 값을 바꿔주거나 할당해주는 역할을 하는 것이 아닌, 다음주에 배울 캡슐화의 맥락에서 특정한 필드만 변경할 수 있도록 통로를 열어두는 데에 사용됩니다.
setter로도 객체의 필드를 초기화시킬 수는 있지만, setter 본연의 용도는 캡슐화에 있으며, 따라서 객체의 필드를 초기화하는 데에만 사용되는 생성자와는 본질적인 용도에 차이가 있습니다.
추가) Crew C님 답글 맨 마지막 부분의 "허락한 데이터만 바꿀 수 있도록" 하고, 수정 또는 참조를 허락하지 않는 필드는 내부적으로 감추는 것이 다음주에 배울 캡슐화의 일부입니다. : )
👀 해당 답변을 보고 든 생각
캡슐화된, 그러니까 다른 객체들로부터 숨겨놓은 내부의 데이터 중 특정 데이터만 손보고 싶으면 해당 객체를 통해서 진행하라는 의미로 setter를 사용한다는 말씀이시군. 근데 그거는 알고 있기도 했고... 생성자는 n개의 데이터를 동시에 초기화'만' 하는데 사용하고 setter는 초기화 된 '특정한' 데이터를 손보거나 추후에 초기화 하는데 사용한다가 말씀의 핵심이신 것 같은데...
ME
Crew C님이 들어주신 예시를 보니까 좀 이해가 되는 것 같네요. 객체 인스턴스를 생성할 당시에 '어떤 정보가 완성되어있는지를 모르니까 생성자 오버로딩을 사용한다. 기본적으로 주어지는 값은 있지만 그 이외의 추가적인 데이터는 있을 수도 있고 없을 수도 있다.' 정도로 이해하면 될까요?
Coach A
이해하신대로 정리해주신 상황에서 생성자 오버로딩이 쓰일 수 있습니다. 그러나 생성자 오버로딩은 꼭 그러한 상황에만 쓰이는 것은 아닙니다.
예를 들어보겠습니다. 아래는 다음주에 실습하실 '복사 생성자 적용을 통한 문제 해결' 파트입니다.
public class Drink extends Product {
...
// 1번 생성자 : 모든 필드를 매개변수로 받음
public Drink(int id, String name, int price, int kcal, boolean hasStraw) {
super(id, name, price, kcal);
this.hasStraw = hasStraw;
}
// 2번 생성자 : 복사 생성자로, 객체를 매개변수로 받아와, 해당 객체와 동일한 필드 값을 가진 새로운 객체를 만듦.
public Drink(Drink drink) {
super(drink.getName(), drink.getPrice(), drink.getKcal());
this.hasStraw = drink.hasStraw();
}
...
}
코드를 보면, 1번 생성자와 2번 생성자가 오버로딩되어 있습니다. 1번 생성자는 매개변수로 개별적인 값을 받아와 필드를 초기화하는 일반적인 생성자입니다. 반면, 2번 생성자는 복사 생성자로, 인스턴스를 받아와, 받아온 인스턴스와 동일한 값을 가진 새로운 인스턴스를 만드는 역할을 합니다.
여기서 알 수 있는 것은 생성자 오버로딩이 꼭 정보 완성 시점과 관련해서만 사용하는 것은 아니라는 것입니다.
👀 해당 답변을 보고 든 생각
음 '같은 값을 가지지만 다른 객체 인스턴스'를 사용할 수도 있겠구만, 그리고 그걸 만들고 싶으면 이런 방법으로 할 수도 있겠어. 이런 방식으로 객체 인스턴스를 생성하면 확실히 부담을 덜 수 있고.
ME
아! Coach A님의 마지막 예시를 보니까 어느 상황에 써야 되는지 일부가 이해가 되네요. '같은 데이터 값을 가진 다른 객체를 사용한다' 라는 개념이 안잡혀있었네요. 좀 더 공부하면서 사용 목적과 사례에 대해서 알아가보겠습니다.
댓글