728x90

첫번째 미션으로 유저 객체를 생성할 때 Builder 패턴을 이용해야 했다.

프로그래머스 스터디 뿐만 아니라 이전에 참고했던 여러 서적들(책 ‘처음 배우는 스프링 부트2’와 ‘스프링 부트와 AWS로 혼자 구현하는 웹서비스’)에서도 빌더 패턴을 사용해서 구현한다. 무엇보다도 찾아봤던 블로그, 책마다 빌더 패턴을 구현하는 모습이 서로 조금씩 달라서 빌더패턴을 코드로 이렇게 작성하는게 맞는건지 너무 헷갈렸다.

왜 빌더 패턴을 사용해야 하는 걸까? 빌더 패턴과 생성자의 차이는 무엇이고 어떤 상황에서 사용해야 하는 걸까? 코드로는 어떻게 구현할까? 이에 대해 한번 정리해 봐야겠다.


먼저 생성자란,

인스턴스가 생성될 때 호출되는 인스턴스 초기화 메소드이다. 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것은 아니다.

User Sara = new User();

 

빌더패턴이란

생성자처럼 인스턴스를 초기화하고 new 대신 build() 메소드를 이용해 객체를 생성하는 방법이다. 기존 생성자의 문제점을 보완해서 객체 생성을 더 깔끔하고 유연하게 할 수 있다.

 

빌더 패턴은 왜 / 언제 사용해야할까?

일반 생성자 이용시, 인자 수가 늘어나면 코드 작성이 어려워지고 읽기 어려운 코드가 되고 만다

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);

호출 코드만 보고서는 저 많은 인자가 도대체 무슨 값인지 알 수가 없고 특히 자료형이 같은 인자들이 많을 경우 컴파일 단계에서 걸러지지 않기 때문에 런타임시 문제 발생하기가 딱 좋다 - 실수하기 좋은 구조.

대안책으로 setter메소드를 이용한 자바빈 패턴이 있다.

// cocaCola을 영양 성분 정보를 setter메소드를 통해 초기화하는 자바빈 패턴
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohdydrate(27);

가독성은 좋아졌지만, 1회의 함수 호출로 객체 생성을 끝낼 수 없고 setter메서드로 인해 Immutable한 클래스를 만들 수가 없다. Setter 메소드 사용은 지양해야하며 변경 가능성은 최소화해야한다.

따라서 가독성도 좋고 immutable 객체로도 만들 수 있는 Builder 패턴을 사용하게 된다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                            .calories(100)
                                            .sodium(35)
                                            .carbohydrate(27)
                                            .build();

각 인자가 어떤 값을 의미하는지 알기가 쉽고 setter메소드가 없으므로 변경 불가능한 객체도 만들 수 있다. 한번에 객체를 생성하므로 객체 일관성이 깨지지 않으며, build() 함수로 잘못된 값이 입력되었는지 검증하게 할 수도 있다.

Builder패턴은

  • 생성자의 인자수가 많을 때 사용하며
  • 불변성과 네임드 파라미터(Named Parameter, 매개변수 이름을 통해서 인자 값을 전달)를 가져갈 수 있어
  • 안전하고 가독성이 좋다.

 

빌더패턴을 코드로 작성해보자

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters(필수 인자)
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder() {
        }

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

위와 같이 하면 다음처럼 객체를 생성해 사용하면 된다.

NutritionFacts cocaCola = new NutritionFacts
    .Builder(240, 8)    // 필수값 입력
    .calories(100)
    .sodium(35)
    .carbohydrate(27)
    .build();           // build() 가 객체를 생성해 돌려준다.

이렇게 정리해보니 궁금함이 생겼다. 생성자의 단점을 보완해서 나온게 빌더패턴이라면 생성자말고 빌더패턴만 사용하는게 좋지 않나? 그런데 왜 생성자와 빌더패턴을 섞어서 사용할까? => 스터디 리더분께 물어보니 명쾌하게 의견을 주셨다. 정답은 없다고. 생성자의 매개변수가 많아지면 네임드 파라미터의 빌더 패턴 사용을 고려해보는 것처럼 그 상황과 용도에 맞게 더 fit한 생성자를 이용하는게 더 좋다고. 결국 프로그래밍에 있어서 각 방법의 장단점을 확실하게 인지하고 상황에 맞는 방법을 유연하게 적용할 수 있는 능력이 정말 중요한 것 같다.

references

Effective Java 2/E
자바의 정석
https://johngrib.github.io/wiki/builder-pattern/
https://softwareengineering.stackexchange.com/questions/380397/why-do-we-need-a-builder-class-when-implementing-a-builder-pattern

 

Why do we need a Builder class when implementing a Builder pattern?

I have seen many implementations of the Builder pattern (mainly in Java). All of them have an entity class (let's say a Person class), and a builder class PersonBuilder. The builder "stacks" a vari...

softwareengineering.stackexchange.com

 

 

https://yjna2316.github.io/study/2020/11/06/%EC%83%9D%EC%84%B1%EC%9E%90%EC%99%80-%EB%B9%8C%EB%8D%94%ED%8C%A8%ED%84%B4/

 

 

728x90

JSONException and JSONObject components called from JSON Library were not imported in Jenkins compiling stage.

Components are not included in or are not recognized at the JVM level, which is expected.

 

Despite these issues causing some users, I have no idea why they happen.

 

Change Below 

import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.boot.configurationprocessor.json.JSONException;

 

to

import org.json.JSONException;
import org.json.JSONObject;

 

Also, import JSON directly

implementation 'org.json:json:20240303'

 

'Java > Spring Boot JPA' 카테고리의 다른 글

생성자와 Builder패턴  (1) 2024.04.26
JobParametersIncrementer  (0) 2024.04.03
Running Jobs from within a Web Container  (31) 2024.04.03
Builder and AllArgsConstructor Annotation  (31) 2024.04.02
Item Processing in Spring Batch  (0) 2024.04.01
728x90

JobParametersIncrementer

Most of the methods on JobOperator are self-explanatory, and you can find more detailed explanations in the Javadoc of the interface. However, the startNextInstance method is worth noting. This method always starts a new instance of a Job. This can be extremely useful if there are serious issues in a JobExecution and the Job needs to be started over again from the beginning. Unlike JobLauncher (which requires a new JobParameters object that triggers a new JobInstance), if the parameters are different from any previous set of parameters, the startNextInstance method uses the JobParametersIncrementer tied to the Job to force the Job to a new instance:

 

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

 

 

The contract of JobParametersIncrementer is that, given a JobParameters object, it returns the “next” JobParameters object by incrementing any necessary values it may contain. This strategy is useful because the framework has no way of knowing what changes to the JobParameters make it the “next” instance. For example, if the only value in JobParameters is a date and the next instance should be created, should that value be incremented by one day or one week (if the job is weekly, for instance)? The same can be said for any numerical values that help to identify the Job, as the following example shows:

 

public class SampleIncrementer implements JobParametersIncrementer {

    public JobParameters getNext(JobParameters parameters) {
        if (parameters==null || parameters.isEmpty()) {
            return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
        }
        long id = parameters.getLong("run.id",1L) + 1;
        return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
    }
}

 

In this example, the value with a key of run.id is used to discriminate between JobInstances. If the JobParameters passed in is null, it can be assumed that the Job has never been run before and, thus, its initial state can be returned. However, if not, the old value is obtained, incremented by one, and returned.

 

For jobs defined in Java, you can associate an incrementer with a Job through the incrementer method provided in the builders, as follows:

 

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}
Copied
728x90

Historically, offline processing (such as batch jobs) has been launched from the command-line, as described earlier. However, there are many cases where launching from an HttpRequest is a better option. Many such use cases include reporting, ad-hoc job running, and web application support. Because a batch job (by definition) is long running, the most important concern is to launch the job asynchronously:

 

Figure 1. Asynchronous Job Launcher Sequence From Web Container

 

 

The controller in this case is a Spring MVC controller. See the Spring Framework Reference Guide for more about Spring MVC. The controller launches a Job by using a JobLauncher that has been configured to launch asynchronously, which immediately returns a JobExecution. The Job is likely still running. However, this nonblocking behavior lets the controller return immediately, which is required when handling an HttpRequest. The following listing shows an example:

@Controller
public class JobLauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

    @RequestMapping("/jobLauncher.html")
    public void handle() throws Exception{
        jobLauncher.run(job, new JobParameters());
    }
}
Copied!

 

'Java > Spring Boot JPA' 카테고리의 다른 글

not importing JSONException and JSONObject issue  (0) 2024.04.04
JobParametersIncrementer  (0) 2024.04.03
Builder and AllArgsConstructor Annotation  (31) 2024.04.02
Item Processing in Spring Batch  (0) 2024.04.01
ItemReaders and ItemWriters  (31) 2024.03.27

+ Recent posts