본문 바로가기

Back-End/JAVA

[JAVA] 자바 기초 - 의존성(Dependency), 생성자&수정자, 빌더패턴(Builder)

~ 목차 ~

 

의존성과 의존성 관계 주입(Dependency Injection, DI)🦄

 

 

* 의존성

- 한 객체가 다른 객체를 사용할 때

- A클래스가 B클래스 또는 인터페이스를 사용하고 있는 경우

A가 B를 의존한다는 것이 의미하는 것은 의존 대상 B가 변하면, 그것이 A에 영향을 미친다는 것.

 

* 의존성 관계 주입(Dependency Injection, DI)

- 의존성이 강할 때 나타나는 문제점을 해결하기 위해 사용

- 의존성 주입 : 두 객체 간의 관계(의존성)를 맺어주는 것

- 방법 : 생성자 주입, 필드 주입, 수정자 주입

→ 객체를 주입받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해 넘겨받는다는 것 = 결합도 낮춤(유연한 구조).

 

 

<의존성 관계 주입 예시>

Class Minicar {

          private Motor motor;

          public Minicar() {

                     motor = new TripleMotor();

          }

}

- 의존성

interface Motor {

           public void booster();

}

- interface 설정

class TripleMotor implements Motor {

           public void booster() {

                      ....

           }

}

- 의존성 관계 주입 : TripleMotor 클래스가 Motor 인터페이스를 구현

 

 

필드 주입 방식(Field Injection)🦄

 

 

* 필드주입 방식 (in Spring Framework) 

- @Autowired : 필드에서 바로 @Autowired 어노테이션을 통해 의존성을 주입하는 방식

 매우 간단하다.

주입된 객체를 Immutable 한 상태(불변)를 만들 수 없다, 
     Spring이 아니면 해당 필드에 Injection 할 수 있는 방법이 없다.

 

 

* 수정자 주입 방식(Setter Injection)

- Setter를 기반으로 한 의존성 주입(DI)

주입받는 객체가 변경될 가능성이 있는 경우에 사용

실제 개발에서는 의존 관계 주입의 변경이 필요한 상황이 거의 없으며,
     수정자 주입을 사용하게 되면 불필요하게 객체의 수정 가능성을 열어두게 되는데,
     이는 OOP(객체 지향 프로그래밍)의 5가지 개발 원칙 중에 OCP(Open-Closed Principal, 개방-폐쇄 원칙) 를 위반하게 됩니다.

 

 

* 생성자 주입 방식(Constructor Injection)

-  생성자 주입을 통해 변경의 가능정을 배제하고, 불변성을 보장 받을 수 있기 때문에 주입받을 필드를 final으로 선언 가능

- 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 Spring 프레임워크에서 지원

- final 키워드를 붙임으로써 Lombok과 결합되어 코드를 간결하게 작성
(Lombok에는 final 변수를 위한 생성자를 대신 생성해주는 @RequiredArgsConstructor 어노테이션이 존재합니다.)

객체의 불변성(Immutable)을 확보
     컴파일 시점에 누락된 의존성을 확인할 수 있다는 장점

      NullPointerException을 방지(  객체 생성 시점에 필수적으로 빈 객체 초기화를 수행 )

 

 

※ OCP(Open-Closed Principle, 개방-폐쇄 원칙)

: 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다는 프로그래밍 원칙

 

 

 

 

<의존성 관계 주입 예시>

@Controller

public class FieldInjectionController {

         @Autowired

         private FieldInjectionService fieldInjectionService;

}

- 필드 주입 방식

@Controller

public class SetterInjectionController {

 

           private SetterInjectionService setterInjectionService;

 

          @Autowired

           public void setSetterInjectionService(SetterInjectionService setterInjectionService) {                                                            this.setterInjectionService = setterInjectionService;

           }

}

- 수정자 주입 방식

@Controller

public class ConstructorInjectionController {

           private ConstructorInjectionService constructorInjectionService;

         

           // 생성자가 1개인 경우 @Autowired를 생략해도 주입 가능하도록 지원

           @Autowired

           public ConstructorInjectionController(ConstructorInjectionService constructorInjectionService) {                                           this.constructorInjectionService = constructorInjectionService;

            }

}

---------------------------------------------------------------------------------------------------------------------------------------------------------

@Controller

@RequiredArgsConstructor

public class ConstructorInjectionController {

 

           private final ConstructorInjectionService constructorInjectionService;

 

}

- 생성자 주입 방식

 

 

* 생성자 주입 장점

- 순환 참조 방지( StakOverflowError 방지 )

- 만약 두 클래스가 서로의 메서드를 호출하고 있다면 코드 호출 시 new A(new B(new A(new B(new A...))) 같은 상황이 반복되기 때문에 StakOverflowError가 발생

 

- 수정자 주입(Setter Injection)과 필드 주입(Field Injection) 방식은 빈을 먼저 생성한 후에 어노테이션이 붙은 필드에 해당하는 빈을 찾아서 주입하는 방식. 즉, 빈을 먼저 생성한 후에 필드에 대해서 주입하기 때문에 빈 객체를 생성한 시점에는 순환 참조의 발생 여부를 알 수가 없다. (생성 -> 주입)

 

- 생성자 주입(Constructor Injection) 같은 경우에는 생성자로 객체를 생성하는 시점에 필요한 빈을 주입.

다시 말하면 빈 객체를 생성하는 시점에 생성자의 파라미터 빈 객체를 찾아서 먼저 주입한 뒤에 주입받은 빈 객체를 이용하여 생성하게 됨. (주입 -> 생성)

때문에 런타임 시점이 아니라 애플리케이션 구동 시점에서 순환 참조 오류를 발견할 수 있게 됨.

 

 

 

 

Builder Pattern을 사용하는 이유🦄

 

 

1. 객체를 생성하기 위해서는 생성자 패턴, 정적 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있다.

 

[ 빌더 패턴(Builder Pattern)의 장점 ]

  1. 필요한 데이터만 설정할 수 있음
  2. 유연성을 확보할 수 있음
  3. 가독성을 높일 수 있음
  4. 변경 가능성을 최소화할 수 있음

2. 클래스 변수는 변경 가능성을 최소화하는 것이 좋다.


why? 만약 값을 할당하는 시점이 객체의 생성뿐이라면 객체에 잘못된 값이 들어왔을 때 그 지점을 찾기 쉬우므로 유지보수성이 훨씬 높아짐.

→ 변경 가능성을 최소화하는 가장 좋은 방법은 변수를 final로 선언함으로써 불변성을 확보하는 것

    final로 강제할 수 있다면 가장 바람직하지만 final을 붙일 수 없는 경우라면 Setter를 넣어주지 않으면 된다.

 

 

 

 

1. 생성자 패턴, 정적 메소드 패턴을 이용하여 객체 생성

@NoArgsConstructor 
@AllArgsConstructor 
public class User { 
    private String name;
    private int age;
    private int height;
    private int iq;

    public User (String name, int height, int iq) {
        this.name = name;
        this.height = height;
        this.iq = iq;
    }
    
    public static User of(String name, int height, int iq) {
        return new User(name, 0, 180, 150);
    }
    
}

 

 

 

2. 빌더(Builder)로 동적으로  처리하여 객체 생성

User user = User.builder()
             .name("망나니 개발자")
             .height(180)
             .iq(150).build(); 

 

 

 

3. final로 선언함으로써 불변성을 확보

@Builder
@RequiredArgsConstructor
public class User {

    private final String name;
    private final int age;
    private final int height;
    private final int iq;

}

 

 

 

 

Builder Pattern을 구현할 필요가 없는 경우🦄

 

 

1. 객체를 생성을 라이브러리로 위임하는 경우

- 엔티티(Entity) 객체나 도메인(Domain) 객체로부터 DTO를 생성하는 경우라면 직접 빌더를 만들고 하는 작업이 번거로우므로 MapStruct나 Model Mapper와 같은 라이브러리를 통해 생성을 위임할 수 있다.

 

 

2. 변수의 개수가 2개 이하이며, 변경 가능성이 없는 경우

- 변수가 늘어날 가능성이 거의 없으며, 변수의 개수가 2개 이하인 경우에는 정적 팩토리 메소드를 사용하는 것이 더 좋을 수도 있다.

 

→ 변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 된다.

 

 

 

Builder Pattern 사용 방법 - Lombok🦄

 

1. builder 사용( 함수 내에서)

    // ✨ 필요한 값만 정의한다면, 나머지는 null로 자동 설정
    User build = new User.Builder()
            .email(email)
            .password(password)
            .name(name)
            .build();

 

* 빌더 패턴을 사용해서 필요한 값만 설정 가능하고, 어떤 값을 설정하는지 명확하게 알 수 있기 때문에 가독성을 높일 수 있습니다.

 

 

2. @builder 설정

  • 클래스 전체 Builder 적용
@Data 
@Builder // ✨ 클래스 전체 필드를 빌더로 사용 가능!
public class MemberVO {
	private String memberId;

    private String memberName;

    private String memberAge;

    private String memberPhone;

    private String memberAddr;

    private String memberSex;

    private String memberEmail;

    private String memberDepartment;

    private String memberLeaveYN;

}

// 사용예제 - 입력한 데이터만 조회 (R)
    @GetMapping("/{memberId}")
    public ResponseEntity<MemberVO> getMember(@PathVariable String memberId) {
        MemberVO member = memberService.selectMember(memberId);

        MemberVO builder = MemberVO.builder()
        		.memberId(member.getMemberId())
                .memberName(member.getMemberName())
                .memberAge(member.getMemberAge())
                .memberDepartment(member.getMemberDepartment())
                .memberEmail(member.getMemberEmail())
                .memberPhone(member.getMemberPhone())
                .memberSex(member.getMemberSex())
                .memberAddr(member.getMemberAddr())
                .memberLeaveYN(member.getMemberLeaveYN())
                .build();

        return ResponseEntity.ok(builder);
    }

 

  • 특정 생성자에서만 Builder 적용 가능
@Data
public class MemberVO {

    @Getter(AccessLevel.NONE) // 해당 필드에 대한 getter 생성하지 않음
    private String memberId;

    private String memberName;

    private String memberAge;

    private String memberPhone;

    private String memberAddr;

    private String memberSex;

    private String memberEmail;

    private String memberDepartment;

    private String memberLeaveYN;

    @Builder
    public MemberVO(String memberName, String memberAge, String memberPhone, String memberAddr, String memberSex, String memberEmail, String memberDepartment){
        this.memberName = memberName;
        this.memberAge = memberAge;
        this.memberPhone = memberPhone;
        this.memberAddr = memberAddr;
        this.memberSex = memberSex;
        this.memberEmail = memberEmail;
        this.memberDepartment = memberDepartment;

    }

}

// 사용예제 - member_id, member_leave_yn 출력X : NULL로 표현
@GetMapping("/{memberId}")
public ResponseEntity<MemberVO> getMember(@PathVariable String memberId) {
    MemberVO member = memberService.selectMember(memberId);

    MemberVO builder = MemberVO.builder()
            .memberName(member.getMemberName())
            .memberAge(member.getMemberAge())
            .memberDepartment(member.getMemberDepartment())
            .memberEmail(member.getMemberEmail())
            .memberPhone(member.getMemberPhone())
            .memberSex(member.getMemberSex())
            .memberAddr(member.getMemberAddr())
            .build();

    return ResponseEntity.ok(builder);
}

 

 

 

 

 

 

https://wildeveloperetrain.tistory.com/139

https://mangkyu.tistory.com/163

https://velog.io/@midas/Builder-%ED%8C%A8%ED%84%B4-%ED%95%84%EC%9A%94%EC%84%B1%EA%B3%BC-%EC%82%AC%EC%9A%A9%EB%B2%95

 

728x90