최종 프로젝트 구현하면서 거의....바이브코딩이라고 해도 될 정도로 AI 와 함께 코드를 짜다 보니까 처음 보는 문법이 보여서 정리하고 넘어가기
1. @Embeddable 어노테이션
: 필드를 객체로 묶어서 관리하고 싶을 때 사용하는 클래스
@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TimeSlotInfo {
@Column(nullable = false)
private LocalDate careDate;
@Column(nullable = false)
private LocalTime startTime;
@Column(nullable = false)
private LocalTime endTime;
@Column(nullable = false)
private Integer sequence;
public static TimeSlotInfo of(LocalDate careDate, LocalTime startTime,
LocalTime endTime, Integer sequence) {
TimeSlotInfo info = new TimeSlotInfo();
info.careDate = careDate;
info.startTime = startTime;
info.endTime = endTime;
info.sequence = sequence;
return info;
}
}
사용처에서 이렇게 쓴다
( TimeSlotInfo Class 는 CareRequestTimeSlot, PostTimeSlot, ReservationTimeSlot 세 클래스에서 embedded 해서 사용)
@Embedded
private TimeSlotInfo timeSlotInfo;
이렇게 작성하면 내가 TimeSlotInfo 안에 있는 field 들을 다 구현하지 않아도 @Embedded 해온 클래스 내부에서 접근 가능하다.
Abstract 클래스로 구현하면 사실 넣을 필요가 없음
public class CareRequestTimeSlot extends TimeSlotInfo {
처음에는 세 클래스가 BaseEntity 를 상속하도록 만들고 있었기 때문에 abstract class 는 클래스당 하나만 상속 가능하다는 특징 때문에 Embedded 를 사용했다.
| abstract | @Embeddable | |
| 목적 | 공통 컬럼 상속 ex) createdAt 등 ,,, |
필드 묶음을 객체화 |
| 관계 | 상속 (is-a) | 포함 (has-a) |
| 개수 | 클래스당 하나 | 여러개 가능 |
지금은 BaseEntity 를 받고있지 않으니까 사실상 ... TimeSlotInfo class 를 abstract 로 바꿔서 extends 하도록 해도 전혀 상관 없다는 사실,......
하지만 뭐 의미상 공통 컬럼을 물려주는 개념이 아니라, TimeSlotInfo 내부 필드 묶음을 객체로 관리하기 위한 것이니까 @Embeddable 이 더 정확하긴 하다
2. <? extends HasTimeSlot>
public interface HasTimeSlotInfo {
TimeSlotInfo getTimeSlotInfo();
}
HasTimeSlotInfo 인터페이스는 getTimeSlotInfo() 메서드를 반드시 구현하도록 강제한다.
public class CareRequestTimeSlot implements HasTimeSlotInfo {
인터페이스니까 implements 해서 사용함!
처음에 헷갈린 이유가 ... CareRequestTimeSlot 내부에 getTimeSlotInfo() 라는 method 가 없어서 음? 내가 아는 ... interface 구현이 아닌가 했는데 알고보니 Lombok 의 @Getter 어노테이션이 자동으로 넣어줘서 인터페이스 입장에서는 있다고 판단한것
실제로는 구현되어있다.
@Override
public TimeSlotInfo getTimeSlotInfo() {
return this.timeSlotInfo;
}
Getter 안 쓰고 이르케 넣어줘도 됨
또 헷갈렸던 부분이 이 메서드다....
생긴것부터 저 지피티가 만들었어요 하고 있음 ㅎ...
public boolean hasTimeConflict(List<? extends HasTimeSlotInfo> slotsA,
List<? extends HasTimeSlotInfo> slotsB)
매개변수 의미가 HasTimeSlotInfo를 implements한 애들의 List는 다 받겠다 << 임
메서드 내부에서는 꺼낸 원소가 뭐든 간에 HasTimeSlotInfo 로 보기 때문에 getTimeSlotInfo() 를 꼭 구현하라고 HasTimeSlotInfo 에서 작성해둔것이였다
for (HasTimeSlotInfo a : slotsA) {
TimeSlotInfo infoA = a.getTimeSlotInfo();
}
요렇게 바로 사용 가능
HasTimeSlotInfo 를 extends 한 이유는 저기에 Reservation에서 사용한 TimeSlot 이 들어오든... CareRequest에서 사용한 TimeSlot 이 들어오든 hasTimeConflict method 에서 재사용하기 위해서
3. ? vs Generic T
위에서 사용된 ? 가 조금 헷갈렸는데
? (와일드카드) vs T (제네릭 타입 변수) 를 비교했을 때
public <T extends HasTimeSlotInfo> List<T> filter(List<T> slots) {
// 입력이 List<PostTimeSlot> 이면 반환도 List<PostTimeSlot>
}
제네릭 변수 T 는 타입에 이름을 붙여서 입력과 출력 타입을 연결할 때 사용하고
public boolean hasTimeConflict(List<? extends HasTimeSlotInfo> slotsA,
List<? extends HasTimeSlotInfo> slotsB) {
// slotsA 가 뭔지, slotsB 가 뭔지 이름 붙일 필요 없음
// 그냥 꺼내서 getTimeSlotInfo() 호출만 하면 됨
}
와일드 카드 ? 는 타입 이름이 필요 없을 때 사용한다.
내가 사용한 hasTimeConflict 메서드는 반환값도 boolean 이고 꺼내서 읽기만 하니까 타입 이름이 필요없다 -> ? 사용하기