빌더 패턴과 Static Nested Class

yboy·2022년 12월 12일
0

Learning Log 

목록 보기
27/41
post-thumbnail

학습동기

이펙티브 자바 아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라를 읽으며 내용을 기록하고 궁금한 내용을 공부하기 위해서 학습을 다짐하게 되었다.

학습내용

생성자와 정적 팩터리는 매개변수가 많아진다면 대응하기 어렵다. 이럴 때는 최종적으로 객체의 불변성과 가독성 모두를 보장해주는 빌더 패턴을 사용해야 한다. 빌더 패턴을 알아보기 전에 간단하게 점증적 생성자 패턴자바빈즈 패턴 에 대해 알아보고 빌더 패턴에 대해 알아보자. 두 패턴 모두 생성자의 매개변수가 많을 때 사용할 수 있는 방법들이지만 각각의 단점이 존재하며 빌더 패턴은 이 단점들을 해결해준다.

점층적 생성자 패턴

점층적 생성자 패턴은 매개변수가 많아졌을때 가장 쉽게 대응할 수 있는 방법이다. 불변 객체를 만들 수 있다는 장점이 있지만 매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기가 어렵다는 단점이 있다. 즉, 가독성이 많이 떨어진다.
이로 인해 다음과 같은 단점이 존재한다.

1. 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈린다. 
2. 매개변수가 몇 개인지도 세어 보아야 한다. 
3. 타입이 같은 매개변수가 연달아 늘어서 있으면 찾기 어려운 버그로 이어질 수도 있다. 
public class User {
    private final String name;
    private final int age;
    private final int height;
    private final int weight;
    private final String address;
}
 public User(String name, int age) {
        this(name, age, 0);
    }

    public User(String name, int age, int height) {
        this(name, age, height, 0);
    }

    public User(String name, int age, int height, int weight) {
        this(name, age, height,weight, "");
    }

    public User(String name, int age, int height, int weight, String address) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.address = address;
    }
User user = new User("잉", 26, 173, 65, NY);

자바빈즈 패턴

자바빈즈 패턴은 매개변수가 없는 생성자로 객체를 만든 후, setter 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다. 점증적 생성자 패턴보다 가독성은 좋아졌지만, setter 메서드를 사용하여 상태 변수에 final을 붙일 수 없기 때문에 불변 객체를 만들 수 없다는 치명적인 단점이 존재한다. 따라서 스레드 안전성을 얻으려면 개발자가 추가 작업을 따로 해줘야 한다. 또한 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고,객체가 완전히 생성되기 전까지는 일관성이 무너진다는 단점도 존재한다.

public class User {
    private String name;
    private int age;
    private int height;
    private int weight;
    private String address;

     public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
User user = new User();
user.setName("잉")
user.setAge(26);
user.setHeight(70);
user.setWeight(70);
user.setAddress("NY");

빌더 패턴

빌더 패턴은 점층적 생성자 패턴의 안전성(불변객체 보장)과 자바빈즈 패턴의 가독성을 겸비한 패턴이다.

  • 불변이며, 모든 매개변수의 기본값들을 한곳에 모아뒀다.
  • 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
  • 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있다.
  • 점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.
  • 하지만 API는 시간이 지날수록 매개변수가 많아지는 경향이 있다. 따라서 애초에 빌더로 시작하는 편이 나을 때가 많다.
public class User {
    private final String name;
    private final int age;
    private final int height;
    private final int weight;
    private final String address;

    private User(Builder builder) {
        name = builder.name;
        age = builder.age;
        height = builder.height;
        weight = builder.weight;
        address = builder.address;
    }

    static class Builder {
        private final String name;
        private final int age;
        private int height;
        private int weight;
        private String address;

        public Builder(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Builder height(int height) {
            this.height = height;
            return this;
        }

        public Builder weight(int weight) {
            this.weight = weight;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }
    }
}
 	new User.Builder("잉", 26)
                .weight(65)
                .height(173)
                .address("NY");
    }

위의 빌더 패턴 예시를 보면 static 클래스를 사용하고 있음을 알 수 있다. static 메서드는 많이 봤지만 static 클래스는 싱글톤을 Lazy Holder 방식으로 생성(진정한 싱글톤을 보장해 주기위해 이 방식을 사용한다고 기억하는데 잘은 모르겠다....ㅎ)할 때 빼고는 본 적이 없는 것 같아 이 참에 학습해 보기 위해 추가로 학습을 했다.

Static Class

static class는 Nested Class(중첩 클래스)로 사용된다. 그럼 Nested Class란 뭘까?

Nested Class

다른 클래스 안에 정의된 클래스를 Nested Class(중첩 클래스)라고 부른다.

Nested Class를 사용하는 이유

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
하나의 클래스에서만 사용하는 클래스인 경우 Nested Class를 사용한다. 이러한 클래스를 Nested Class로 만듬으로서 캡슐화를 증가시키고 가독성을 증가시켜 유지보수하기 좋은 코드를 짤 수 있다.

Nested class는 Inner Class(Non-static Nested Class)와 static Nested Class 두 종류로 나뉜다.

inner Class

  • 외부 인스턴스에 대한 참조가 유지된다.
  • 외부 인스턴스가 존재해야 생성될 수 있다. 따라서 외부 인스턴스에 대한 외부 참조를 갖게 된다.

static Nested Class

  • static이 붙은 중첩 클래스
  • 외부 인스턴스 없이 Nested Class의 인스턴스를 바로 생성할 수 있다. 따라서 외부 참조를 갖지 않는다.

❗️inner class가 외부 참조를 갖기 때문에 Nested Class에서 외부 클래스에 접근할 수 있지만 외부 참조는 치명적인 단점이 존재한다.

1. 참조값을 담아야 하기 때문에, 인스턴스 생성시 시간적, 공간적으로 성능이 낮아진다.
2. 외부 인스턴스에 대한 참조가 존재하기 때문에, 가비지 컬렉션이 인스턴스 수거를 하지 못하여 메모리 누수가 생길 수 있다.

따라서 Nested Class를 선언할 때는 static 키워드를 붙여주는 것이 좋다.

마무리

스프링에서 lombok으로만 사용하던 빌더 패턴에 대해 알아볼 수 있는 유익한 시간이었다. 오늘부터 이펙티브 자바를 읽기 시작했는데 꾸준히 읽어 성장하고자 다짐하면서 글을 마무리 하겠다.🙏🏻

프로그래밍 고수 경지에 오르려면 먼저 정석이 되는 규칙들을 배운 후, 언제 그 규칙을 깨도 되느냐를 익혀야 한다.
- 조슈아 블로크(이펙티브 자바 저자)

0개의 댓글