정리한 내용 중 잘못된 내용이 있을 수 있습니다. 잘못된 내용이 있다면 꼭 알려주세요.
//Laptop Class
public class Laptop {
private String modelName;
private String company;
}
//Constructor
public Laptop(String modelName, String company) {
this.modelName = modelName;
this.company = company;
}
//이름을 가진 Static Factory Method
public static Laptop ofModelNameAndCompany(String modelName, String company) {
Laptop laptop = new Laptop();
laptop.company = company;
laptop.modelName = mpdelName;
return laptop;
}
Lombok에 @AllargsConstructor
가 있는데도 불구하고 Static Factory Mathod를 사용하면
언제 편할까?
public class LaptopForm {
private String name;
private String corp;
}
@PostMapping("/add")
public LaptopDto addLaptop(@RequestBody LaptopForm laptopform) {
//생략
}
위와 같이 Controller에 addLaptop이라는 메서드가 있다라고 가정할 때,
LaptopForm과 같이 Request를 받는 객체를 지정하여 사용하는 경우가 많다.
이럴 때 아래와 같이 Laptop Class에 Static Factory Method를 사용하여 convert 해줄 수 있다.
public class Laptop {
private String modelName;
private String company;
public static Laptop from(LaptopForm laptopForm) {
Laptop laptop = new Laptop();
laptop.modelName = laptopForm.getName();
laptop.company = laptopForm.getCorp();
return laptop;
}
}
또한 Response를 응답 할 때도 아래와 같이 Static Factory Method를 사용하여 convert 해줄 수 있다.
public class LaptopDto {
private String modelName;
private String companyName;
public static LaptopDto from(Laptop laptop) {
LaptopDto laptopDto = new LaptopDto();
laptopDto.modelName = laptop.getModelName();
laptopDto.companyName = laptop.getCompany();
return laptopDto;
}
}
그래서 Static Factory Method를 사용했을 때 장 단점을 정리해보자면
public class NutritionFacts {
private final int servingSize; // ml, 1회 제공량 (필수)
private final int servings; // 회, 총 n회 제공량 (필수)
private final int calories; // 1회 제공량당 (need)
private final int fat; // g/1회 제공량 (option)
private final int solum; // mg/1회 제공량 (option)
private final int carbohydrate; // g/1회 제공량 (option)
}
Pattern 1 : 여러 개의 Constructor를 둔다.
Pattern 2 : Java beans Pattern (Setter)
Builder를 Lombok을 통해서만 사용해 왔으면 해당 코드를 한번은 주의깊게 살필 필요가 있다.
public class NutritionFacts {
private final int servingSize; // ml, 1회 제공량 (필수)
private final int servings; // 회, 총 n회 제공량 (필수)
private final int calories; // 1회 제공량당 (need)
public static class Builder {
private final int servingSize;
private final int servings;
private int calories = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}
Lombok을 통하면 훨씬 더 Simple해 질 수 있다.
@Data
@Builder(builderMethodName = "hiddenBuilder")
public class NutritionFacts {
private final int servingSize; // ml, 1회 제공량 (필수)
private final int servings; // 회, 총 n회 제공량 (필수)
@Builder.Defatult private final int calories = 0; // 1회 제공량당 (need)
@Builder.Defatult private final int fat = 0; // g/1회 제공량 (option)
@Builder.Defatult private final int solum = 0; // mg/1회 제공량 (option)
@Builder.Defatult private final int carbohydrate = 0; // g/1회 제공량 (option)
public static NutritionFacts builder(int servingSize, int servings) {
return hiddenBuilder()
.servingSize(servingSize)
.servings(servings);
}
}
그래서 Builder를 사용했을 때 장 단점을 정리해보자면
//대표적인 Singleton Pattern
public class Speaker {
public static final Speaker INSTANCE = new Speaker();
privete Speaker() {}
public static Speaker getInstance() {
return INSTANCE;
}
}
public class Speaker {
private static Speaker instance;
private Speaker() {}
public static synchronized Speaker getInstance() {
if (instance == null) {
instance = new Speaker();
}
return instance;
}
}
Enum Type으로 Singleton Pattern을 사용할 수 있다.
public enum Speaker {
INSTANCE;
private String message;
public Speaker getInstance() {
return INSTANCE;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
실행코드
Speaker speaker1 = Speaker.INSTANCE.getInstance();
speaker1.setMessage("안내 방송 중입니다.");
Speaker speaker2 = Speaker.INSTANCE;
System.out.println(speaker1.getMessage()); // 안내 방송 중입니다.
System.out.println(speaker2.getMessage()); // 안내 방송 중입니다.
하지만 오히려 혼란이 생겨 현업에서 enum으로 잘 사용하지는 않는다고 한다.
정리하자면
Singleton pattern에 대해 알아보았고, 항상 잘 숙지해 두자.
각 방법의 차이점에 대해 인지해 두고 이를 상황에 맞게 선택해 사용할 수 있도록 하자.
Enum 방식은 팀원들과 함께 고려해 보자.
public class PatternUtil {
private static final String PATTERN =
"\\b[A-Z0-9._%+-]+@{A-Z0-9.-]+\\.[A-Z\{2,}\\b";
private PatternUtil() {}
public static boolean isEmailValid(String email) {
return email.matches(PATTERN);
}
public String getPattern() {
return PATTERN;
}
}
private PatternUtil() {}
가 없다고 가정하고,
어느 한 개발자가 PATTERN String 값 자체가 필요해지는 순간이 온다라고 가정하였을 때,
private으로 되어있는 PATTERN을 public으로 변경하려고 하다가, 코드에 수정 없이
public String getPattern()
메소드를 정의하게 된다.
만약에 private PatternUtil() {}
을 선언해준다면 PatternUtil 객체를 생성할 수 없다는 것을
알게되고 public static String getPatter()
으로 변경하여 가져올거다.
사람은 믿지 말자.
과거의 나😃 : 에이 누가 필요도 없는 Instance를 생성하겠어? 굳이 막지 않아도 알아서 다들 잘 지킬 수 있겠지!
현재의 나😂 : 바쁘다 바빠, (습관적으로 Instance 생성)
미래의 나😈 : (리팩토링중) 누가 필요도 없는 Instance를 생성한 거야? 어?? 나네?
나와 팀원의 실수를 줄이기 위해 조금 귀찮더라도 생성자를 막자.
정리하자면
누군가(나를 포함한)를 믿지 못할 상황이 있고, Human error를 방지하기 위해
private constructor를 권장한다. (많은 노력이 필요한 것이 아니다.)
일반적으로 Util class처럼 관용적으로 Instance화를 하지 않을 거라고 모두가 이미 알고 있는 경우는 생략하기도 한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
...
상식 쌓기 차원에서 @Configuration 의 명세를 가져와 봤습니다.
@Configuration은 @Component를 포함하고 있고,
@Component가 Singleton이기에 자연스럽게 @Configuration 또한 Singleton !
변수들을 Config class 내부에 직접 정의하여 사용하는것은 좋지 않다.
그 이유는 여러 자원을 활용해야 할 경우에 적합하지 않고,
환경(dev, local)별로 다른 값들이 들어가야 할 때 적절하지 않다.
아래에 안 좋은 예를 살펴보자
@Configuration
public class Config {
private static final String ADDRESS = "서울시 강남구";
public Config() {
this.ADDRESS = "서울시 강남구";
}
}
그렇다면 좋은 예를 살펴보자
application.yml에 등록된 정보
config:
address: '서울시 강남구'
@Configuration
public class Config {
@Value("${config.address}")
private String address;
}
Constructor Injection의 경우 Test, flexibility를 높일 수 있다.
고정해 높은 값은 비해 훨씬 유연해진 class (pattern Injection 가능)
Test Code 작성 시 Injection하기도 편리하다.
public class PhonePatternChecker {
private final String pattern;
public PhonePatternChecker(String pattern) {
this.pattern = pattern;
}
public boolean isValid(String phone) { ... }
}
Config Class를 생각없이 사용하고 있지는 않았는지
Singleton을 이해하고 있는지
Dependency Injection (DI)를 잘 이해하고 있는지
생각해보시면 좋을 것 같다.