일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 오픈패스
- 디자인교육
- 백엔드개발자
- 디자인강의
- KDT
- 객체지향
- UXUIPrimary
- 국비지원
- 티스토리챌린지
- 백엔드
- Java
- 패스트캠퍼스
- 오픈챌린지
- 내일배움카드
- UXUI챌린지
- UXUI기초정복
- 백준
- Be
- mysql
- 부트캠프
- 국비지원취업
- baekjoon
- 백엔드 부트캠프
- Spring
- 오블완
- 내일배움캠프
- 국비지원교육
- 환급챌린지
- OPENPATH
- 디자인챌린지
- Today
- Total
군만두의 IT 공부 일지
[스터디6] 04. 자바가 확장한 객체 지향 본문
목차
이번에는 책에서 읽은 내용을 현재 진행 중인 MSA(Microservice Architecture) 기반 물류 시스템의 '주문 서비스'와 연결하여 정리했습니다.
04. 자바가 확장한 객체 지향
1. abstract 키워드 - 추상 메서드와 추상 클래스
- 추상 메서드(Abstract Method): 선언부는 있는데 구현부가 없는 메서드
- 추상 메서드를 하나라도 갖고 있는 클래스는 반드시 추상 클래스(Abstract Class)로 선언해야 한다.
- 추상 메서드 없이도 추상 클래스를 선언할 수는 있다.
- 추상 클래스는 인스턴스, 즉 객체를 만들 수 없다. 즉, new를 사용할 수 없다.
- 추상 메서드는 하위 클래스에게 메서드의 구현을 강제한다. 오버라이딩 강제.
- 추상 메서드를 포함하는 클래스는 반드시 추상 클래스여야 한다.
추상 클래스는 직접 인스턴스화할 수 없고, 상속을 통해서만 사용할 수 있는 클래스다. 이 프로젝트에서는 Timestamped를 추상 클래스라고 할 수 있다.
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
@CreatedDate
@Column(name = "created_at", updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@CreatedBy
@Column(name = "created_by", length = 50, updatable = false)
private String createdBy;
// ... 기타 필드 및 메서드
public void delete(String deletedBy) {
this.deletedBy = deletedBy;
this.deletedAt = LocalDateTime.now();
}
}
위 추상 클래스는 엔티티의 생성/수정/삭제 시간과 사용자 정보를 자동으로 관리하는 공통 기능을 제공한다. Order와 OrderProduct 클래스는 이 추상 클래스를 상속받아 사용한다.
@Entity
@Table(name = "p_order")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Order extends Timestamped {
// ... 필드 및 메서드
}
추상 클래스를 사용함으로써 코드 중복을 방지하고, 공통된 기능을 한 곳에서 관리할 수 있다.
2. 생성자
- 클래스의 인스턴스, 즉 객체를 만들 때마다 new 키워드를 사용한다.
- 개발자가 아무런 생성자도 만들지 않으면 자바는 인자가 없는 기본 생성자를 자동으로 만들어준다.
- 인자가 있는 생성자를 하나라도 만든다면 자바는 기본 생성자를 만들어 주지 않는다.
- 생성자는 개발자가 필요한 만큼 오버로딩해서 만들 수 있다.
- 생성자로 줄여 부르지만 정확히 표현하면 객체 생성자 메서드다.
자바의 생성자는 객체가 생성될 때 초기화 작업을 담당한다. 이 프로젝트에는 다양한 생성자 패턴을 적용했다.
1. Lombok 어노테이션을 통한 생성자 자동 생성
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Order extends Timestamped {
// ... 필드
}
2. 이벤트 클래스에서 생성자 패턴 사용
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderCreatedEvent {
private UUID orderId;
private UUID deliveryId;
// ... 기타 필드
}
3. Timestamped 클래스의 명시적 생성자
public Timestamped() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
createdBy = authentication.getName();
}
}
3. 클래스 생성 시의 실행 블록, static 블록
- 객체 생성자가 있지만, 클래스 생성자는 존재하지 않는다.
- 클래스가 스태틱 영역에 배치될 때 실행되는 코드 블록(static 블록)이 있다.
- static 블록에서 사용할 수 있는 속성과 메서드는 static 멤버 뿐이다.
클래스가 처음 사용될 경우
- 클래스의 정적 속성을 사용할 때
- 클래스의 정적 메서드를 사용할 때
- 클래스의 인스턴스를 최초로 만들 때
자바에서는 클래스 초기화 블록과 인스턴스 초기화 블록을 사용할 수 있다. 이 프로젝트에서는 JPA의 생명주기 이벤트를 활용한 초기화 블록을 사용했다.
@PrePersist
public void prePersist() {
if (this.orderId == null) {
this.orderId = UUID.randomUUID();
}
if (this.status == null) {
this.status = OrderStatus.WAITING;
}
}
이 메서드는 JPA 엔티티가 저장되기 전에 자동으로 호출되어 초기화 작업을 수행한다.
4. final 키워드
- final 키워드가 나타날 수 있는 곳은 객체 지향 언어의 구성 요소(클래스, 변수, 메서드) 뿐이다.
- 클래스에 final이 붙으면 상속을 허락하지 않겠다는 의미다.
- 변수에 final이 붙었다면 변경 불가능한 상수가 된다.
- 정적 상수는 선언 시에, 또는 정적 생성자에 해당하는 static 블록 내부에서 초기화가 가능하다.
- 객체 상수 역시 선언 시에, 또는 객체 생성자 또는 인스턴스 블록에서 초기화할 수 있다.
- 지역 상수는 선언 시에, 또는 최초 한 번만 초기화가 가능하다.
- 메서드가 final이라면 최종이니 재정의, 즉 오버라이딩을 금지하게 된다.
final 키워드는 변수, 메서드, 클래스에 적용하여 변경 불가능성을 보장한다.
1. 서비스 클래스의 의존성 주입에 final 키워드 사용
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderRepository orderRepository;
private final OrderDomainService orderDomainService;
private final CompanyClient companyClient;
private final DeliveryClient deliveryClient;
// ... 메서드
}
2. 상수 정의에 final 키워드 사용
public class KafkaTopics {
public static final String ORDER_CREATED = "order-created";
public static final String DELIVERY_CREATED = "delivery-created";
public static final String DELIVERY_STATUS_CHANGED = "delivery-status-changed";
}
5. instanceof 연산자
- 인스턴스: 클래스를 통해 만들어진 객체
- instanceof 연산자: 만들어진 객체가 특정 클래스의 인스턴스인지 물어보는 연산자
- 인터페이스의 구현 관계에서도 동일하게 적용된다.
- 결과로 true 또는 false를 반납한다.
- LSP(리스코프 치환 원칙)를 어기는 코드에서 주로 나타나는 연산자이기에 리팩토링의 대상이 아닌지 검토해야 한다.
instanceof 연산자는 객체가 특정 타입인지 검사하는 데 사용된다. 예외 처리 로직에서 주로 사용된다.
try {
// ... 코드
} catch (Exception e) {
if (!(e instanceof CustomConflictException)) {
throw new CustomConflictException("배송 정보 조회 중 오류가 발생했습니다: " + e.getMessage());
}
throw e;
}
이 코드는 예외가 CustomConflictException 타입인지 확인하고, 그렇지 않은 경우 새로운 CustomConflictException 예외로 래핑한다.
6. package 키워드
- package 키워드는 네임스페이스(이름공간)를 만들어주는 역할을 한다.
- 특별히 하는 일은 없다.
자바에서 패키지는 관련 클래스를 그룹화하고 네임스페이스를 제공한다. 이 프로젝트에서는 계층형 패키지 구조가 사용된다.
com._hateam.common // 공통 모듈
com._hateam.common.config // 설정 클래스
com._hateam.common.dto // DTO 클래스
com._hateam.common.entity // 엔티티 클래스
com._hateam.common.event // 이벤트 클래스
com._hateam.common.exception // 예외 클래스
com._hateam.order // 주문 모듈
com._hateam.order.application.dto // DTO 클래스
com._hateam.order.application.service // 애플리케이션 서비스
com._hateam.order.domain.model // 도메인 모델
com._hateam.order.domain.repository // 저장소 인터페이스
com._hateam.order.domain.service // 도메인 서비스
이 패키지 구조는 DDD(Domain-Driven Design) 원칙에 따라 도메인, 애플리케이션, 인프라스트럭처 등의 관심사를 분리하여 코드의 가독성과 유지보수성을 높였다.
7. interface 키워드와 implements 키워드
- 인터페이스는 public 추상 메서드와 public 정적 상수만 가질 수 있다.
- 인터페이스는 추상 메서드와 정적 상수만 가질 수 있기에 따로 메서드에 pubilc과 abstract, 속성에 public과 static, final을 붙이지 않아도 자동으로 자바가 붙여준다.
인터페이스는 클래스가 구현해야 하는 메서드의 계약을 정의한다. 이 프로젝트에서는 다양한 인터페이스와 구현체가 있다.
1. 저장소 인터페이스와 구현체
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(UUID orderId);
// ... 기타 메서드
}
@Repository
@RequiredArgsConstructor
public class OrderRepositoryImpl implements OrderRepository {
private final JpaOrderRepository jpaOrderRepository;
@Override
public Order save(Order order) {
return jpaOrderRepository.save(order);
}
// ... 기타 메서드 구현
}
2. Feign 클라이언트 인터페이스
@FeignClient(name = "company-service", url = "${services.company.url}")
public interface CompanyClient {
@GetMapping("/companies/{companyId}")
ResponseDto<CompanyDto> getCompanyById(@PathVariable("companyId") UUID companyId);
// ... 기타 메서드
}
3. Spring 설정을 위한 인터페이스 구현
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userHeaderInfoArgumentResolver());
}
// ... 기타 메서드
}
8. this 키워드
- this: 객체가 자기 자신을 지칭할 때 쓰는 키워드
- 지역 변수와 속성(객체 변수, 정적 변수)의 이름이 같은 경우 지역 변수가 우선한다.
- 객체 변수와 이름이 같은 지역 변수가 있는 경우 객체 변수를 사용하려면 this를 접두사로 사용한다.
- 정적 변수와 이름이 같은 지역 변수가 있는 경우 정적 변수를 사용하려면 클래스명을 접두사로 사용한다.
this 키워드는 현재 객체를 참조한다. 이 프로젝트에서는 다양한 용도로 사용된다.
1. 필드 접근 시 명확성을 위해
@PrePersist
public void prePersist() {
if (this.orderId == null) {
this.orderId = UUID.randomUUID();
}
}
2. 자기 자신을 다른 객체에 전달
public void addOrderProduct(OrderProduct orderProduct) {
this.orderProducts.add(orderProduct);
orderProduct.setOrder(this); // 자기 자신을 OrderProduct에 전달
}
3. 메서드 체이닝을 위한 자기 자신 반환
public void delete(String deletedBy) {
this.deletedBy = deletedBy;
this.deletedAt = LocalDateTime.now();
}
9. super 키워드
- super: 단일 상속만을 지원하는 자바에서 바로 위 상위 클래스의 인스턴스를 지칭하는 키워드
- super 키워드로 바로 위의 상위 클래스 인스턴스에는 접근할 수 있지만 super.super 형태로 상위의 상위 클래스의 인스턴스에는 접근이 불가능하다.
super 키워드는 부모 클래스의 멤버에 접근하는 데 사용된다. 이 프로젝트에서는 명시적인 super 키워드 사용은 없지만, 상속 관계가 있다.
public class Order extends Timestamped {
// ... 필드 및 메서드
}
Order 클래스는 Timestamped의 모든 메서드와 필드를 상속받는다. 예를 들어, Timestamped의 delete 메서드를 Order 객체에서 직접 호출할 수 있다.
이 글은 『스프링 입문을 위한 자바 객체 지향의 원리와 이해』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > 객체지향' 카테고리의 다른 글
[스터디5] 06. 쇼핑몰 서비스 - 도메인 모델, 유스케이스, 책임-주도 설계 (0) | 2025.04.05 |
---|---|
[스터디6] 05. 객체 지향 설계 5원칙 - SOLID (0) | 2025.03.29 |
[스터디6] 03. 자바와 객체지향 (0) | 2025.03.15 |
[스터디6] 02. 자바와 절차적/구조적 프로그래밍 (1) | 2025.03.07 |
[스터디6] 01. 사람을 사랑한 기술 (0) | 2025.03.01 |