프로젝트 주제; 통합 결제 모듈 클론코딩
아키텍처; MSA 기반 멀티 모듈? + DDD 구조
구현해야 기능;
우리가 구현해야 하는 기능을 구현할 때, 우리는 단순하게 기능만 구현하지 않을 것이다. 첫 번째로, 작성된 코드를 보면서 현대식 자바 문법에 대해서 자세하게 알아 볼 것이다. 단순히 자바 문법을 배운다고 자바를 잘 작성할 수 있는 것은 아니기 때문에, 자바를 Java스럽게 작성하는 방법에 대해서 이야기를 함께 나눠보자.
두 번째로, 우리가 무의식적?으로 사용하는 Spring Boot에 대한 어노테이션나 기술들을에 대해서 자세하게 살펴 볼 것이다. 무엇을 통해서?! 코드와 디버깅을 통해서 매우 딥하게 살펴 볼 것이다. 두 번째 수업에서의 최종 목표는 기술 면접 때 자주 나오는 문제들을 단순히 외운 내용으로 설명하는 것이 아니라, 이해를 바탕으로 대답을 함으로써 예측 불가능한 질문까지 생각해서 답을 찾아낼 수 있는 능력을 배양하는 것이다.
세 번째로, 객체지향 관점에서 프로젝트 구조를 설계를 왜 이렇게 했고, 어떤 구조로 비즈니스 로직을 추상화 했는지 알아보자. 이때 우리가 자주 사용 및 거론되는 기술은 아마도 접근 제한자와 추상화, 인터페이스일 것 같은데, 생각보다 우리는 이것들에 대해서 상세하게 잘 모른다. 그래서 객체지향스러운? 코드를 함께 보면서 이야기를 나눠보자.
1.1 정적 팩토리 메서드를 이용한 인스턴스 생성
1.1.1 장점
첫 번째, 이름(Naming)을 가질 수 있다.
생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩토리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
두 번째, 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.[Singleton]
세 번째, 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
네 번째, 입력 매개변수에 따라 매번 다른 클래스의 인스턴스를 반환 할 수 있다.
다섯 번째, 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
1.1.2 단점
첫 번째, 상속할 수 없다.
두 번째, 정적 팩토리 메서드 찾기가 어렵다.
명명 규칙
from; Date d = Date.from(instant);
매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
of; Set<Developer> dev = EnumSet.of(YOO, JIN, HO);
여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
valueOf; Boolean isTrue = Boolean.valueOf(”true”);
예제 코드
PaymentRequestDto 클래스
@Getter
public class PaymentRequestDto {
private final String pgCorpName;
private PaymentRequestDto(String name) {
this.pgCorpName = PgCorp.valueOf(name.toUpperCase()).toString().toLowerCase();
}
public static PaymentRequestDto of(String pgCorpName) {
return new PaymentRequestDto(pgCorpName);
}
}
2.1 new 키워드를 이용한 인스턴스 생성
2.1 생성자와 정적 팩토리의 한계
선택적 매개변수가 많을 때 정적 팩토리와 생성자는 적절히 대응하기 어렵다.
왜? 첫 번째는, 메서드를 호출하는 과정에서 파라미터를 할당할 때 매개변수의 순서를 틀리거나 누락할 수 있다는 휴먼 에러를 내포하고 있다.
두 번째는, 가독성이 매우 현저하게 떨어진다. 계속하는 이야기이지만, 개발자의 실무는 새로운 코드를 작성하는 것이 아니라, 기존 코드를 읽고 fxxk를 외치는 것이 주요 업무이다. 그래서 좋은 개발자 || 휼륭한 개발자는 읽고 싶은 코드를 작성하는 사람이지 않을까 싶다. 그런데, 선택적 매개변수(Parameters)가 많은 경우, 생성자 관련 메서드를 이용한 인스턴스 생성은 매우 협오스럽다. 왜? 해당하는 파라미터가 어떤 값을 의미하는지 전혀 알 수 없기 때문이다.
public class PaymentApproveMessage {
private String mId;
private String currency;
private String method;
private String lastTransactionKey;
private String paymentKey;
private int totalAmount;
private int balanceAmount;
private int suppliedAmount;
private int taxFreeAmount;
private String requestedAt;
private String approvedAt;
public PaymentApproveMessage(String mId, String currency, String method, String lastTransactionKey, String paymentKey,
int totalAmount, int balanceAmount, int suppliedAmount, int taxFreeAmount, String requestedAt,
String approvedAt) {
this.mId = mId;
this.currency = currency;
this.method = method;
this.lastTransactionKey = lastTransactionKey;
this.paymentKey = paymentKey;
this.totalAmount = totalAmount;
this.balanceAmount = balanceAmount;
this.suppliedAmount = suppliedAmount;
this.taxFreeAmount = taxFreeAmount;
this.requestedAt = requestedAt;
this.approvedAt = approvedAt;
}
}
2.2 Builder 패턴으로 문제 해결
왜 생성자를 private || protected로 선언하는가? protected는 같은 패키지 내에서만 사용하거나 상속이 가능함. private은 해당 클래스 내에서만 호출이 가능하다.
이렇게 선언하게 되면 인스턴스 생성하는 방식 강제화 할 수 있고 불완전한 객체를 만들어내서 오류를 발생 시키는 일 또한 막을 수 있다. effective-java에서는 기본 생성자의 접근 제한자를 무조건 public으로 하는 것을 지양하라고 이야기하고 있다. 왜냐하면 불완전한 객체를 만들어내기 때문이다. 이러한 문제로 오류가 발생하면 생각보다 해당하는 오류 찾기가 쉽지 않다.
3.1 불변식; Invariant
프로그램이 실행되는 동안 또는 정해진 기간 동안 반드시 만족해야 하는 조건을 말한다. 다시 말해 변경을 허용할 수 있으나, 주어진 조건 내에서만 허용한다는 뜻이다. 예를 들면, **“리스트(List)의 경우, size의 크기는 반드시 0 이상이어야 하고 한 순간이라도 음수 값이 될 수 없다.”**라는 조건식이 List.size()의 불변식이다.
3.2 불변 클래스 선언하는 방법
3.2.1 final 클래스
3.2.2 클래스의 모든 필드 final로 선언 한다. 단, 예외도 존재한다.
설계자의 의도를 명확히 드러내는 방법 중 가장 좋은 방법이다. 클라이언트(정의된 클래스를 인스턴스로 생성해서 사용하는 주체)는 주적이다. 이들은 항상 설계자의 의도에서 벗어나는 행동을 한다. 그렇기 때문에 행동에 제약을 걸어야 한다.
3.2.3 private 필드
3.2.4 클래스의 확장(extends)를 막는다; only private 생성자와 Static Factory
public || protected 범위로 생성자를 선언하면 상속(extends)이 가능하다. 그리고 생성자의 접근 제한자를 통해서 클라이언트는 해당 클래스가 상속에 대한 확장을 염두에 두고 설계가 되었다고 생각할 수 있다. 하지만, 설계자는 한 가지 놓친 사실이 하나 있다. 클라이언트는 설계자의 의도대로 행동하지 않는다라는 것이다. 그래서 상속을 받은 하위 클래스에서 부주의하게 혹은 나쁜 의도로 객체의 상태를 변하게 할 수 있다. 그렇게 되면 불변성 또한 깨지게 된다.
이러한 문제는 Only private 생성자 || Static 팩토리 메서드를 통해서 막을 수 있다.