Servlet_05) Lombok과 Factory Method 패턴
2025. 10. 8. 11:31

학습 목표

  • Lombok 어노테이션의 동작 원리와 활용 방법을 이해한다
  • 정적 팩토리 메서드 패턴의 장점과 구현 방법을 학습한다
  • Member 클래스의 실무 코드를 분석하여 적용 사례를 파악한다
  • 코드 간소화유지보수성 향상 전략을 습득한다

<Lombok이란?>

Lombok은 어노테이션 기반으로 반복적인 자바 코드를 자동 생성해주는 라이브러리다. 컴파일 시점에 바이트코드를 조작하여 getter, setter, toString 등의 메서드를 자동으로 생성한다.

- Lombok 없이 작성한 코드

 
 
java
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    
    // 기본 생성자
    public Member() {}
    
    // 모든 필드를 받는 생성자
    public Member(int memberNo, String memberId, String memberPwd, 
                  String memberName, String phone, String email) {
        this.memberNo = memberNo;
        this.memberId = memberId;
        this.memberPwd = memberPwd;
        this.memberName = memberName;
        this.phone = phone;
        this.email = email;
    }
    
    // Getter 메서드들
    public int getMemberNo() { return memberNo; }
    public String getMemberId() { return memberId; }
    public String getMemberPwd() { return memberPwd; }
    public String getMemberName() { return memberName; }
    public String getPhone() { return phone; }
    public String getEmail() { return email; }
    
    // Setter 메서드들
    public void setMemberNo(int memberNo) { this.memberNo = memberNo; }
    public void setMemberId(String memberId) { this.memberId = memberId; }
    public void setMemberPwd(String memberPwd) { this.memberPwd = memberPwd; }
    public void setMemberName(String memberName) { this.memberName = memberName; }
    public void setPhone(String phone) { this.phone = phone; }
    public void setEmail(String email) { this.email = email; }
    
    // toString 메서드
    @Override
    public String toString() {
        return "Member{" +
                "memberNo=" + memberNo +
                ", memberId='" + memberId + '\'' +
                ", memberPwd='" + memberPwd + '\'' +
                ", memberName='" + memberName + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

 

 
 
코드 라인 수: 약 50줄
반복적인 보일러플레이트 코드로 가독성 저하
필드 추가 시 여러 곳을 수정해야 하는 유지보수 부담

- Lombok을 활용한 코드

 
 
java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
}

실행 결과:

 
 
코드 라인 수: 약 15줄 (70% 감소)
어노테이션으로 의도가 명확하게 표현됨
필드 추가 시 자동으로 메서드 생성

Lombok의 핵심 어노테이션은 다음과 같다. @NoArgsConstructor는 기본 생성자를 생성하고, @AllArgsConstructor는 모든 필드를 매개변수로 받는 생성자를 생성한다. @Getter와 @Setter는 각각 getter와 setter 메서드를 생성하며, @ToString은 toString 메서드를 자동 생성한다.


<주요 Lombok 어노테이션 분석>

Lombok은 다양한 어노테이션을 제공하여 코드 작성의 편의성을 높인다. 각 어노테이션의 역할과 사용 시나리오를 이해하는 것이 중요하다.

- @Data 어노테이션

 
 
java
import lombok.Data;

@Data
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
}

실행 결과:

 
 
@Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 모두 포함
가장 강력하지만 불필요한 메서드까지 생성될 수 있음
실무에서는 필요한 어노테이션만 선택적으로 사용하는 것을 권장

- @Builder 어노테이션

 
 
java
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
}

// 사용 예시
Member member = Member.builder()
    .memberNo(1)
    .memberId("user01")
    .memberName("홍길동")
    .phone("010-1234-5678")
    .email("user01@test.com")
    .build();

실행 결과:

 
 
빌더 패턴을 자동으로 구현
매개변수 순서에 구애받지 않고 객체 생성 가능
선택적으로 필드 값을 설정할 수 있어 유연성 증가

- @RequiredArgsConstructor와 final 필드

 
 
java
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class Member {
    private final int memberNo;
    private final String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
}

// 사용 예시
Member member = new Member(1, "user01");

실행 결과:

 
 
final 필드만 매개변수로 받는 생성자 생성
불변 객체 설계 시 유용
필수 필드와 선택 필드를 명확히 구분 가능

@Data 어노테이션의 주의점은 setter를 무분별하게 제공하여 객체의 불변성을 해칠 수 있다는 점이다. 따라서 엔티티나 VO 클래스에서는 필요한 어노테이션만 선택적으로 사용하는 것이 바람직하다.


<Factory Method 패턴이란?>

정적 팩토리 메서드는 객체 생성을 담당하는 정적 메서드로, 생성자 대신 메서드를 통해 인스턴스를 반환하는 패턴이다. 이름을 가질 수 있고 호출될 때마다 새로운 객체를 생성할 필요가 없다는 장점이 있다.

- 생성자 vs 정적 팩토리 메서드

 
 
java
// 생성자 방식
Member member1 = new Member(1, "user01", "pass123", "홍길동", 
                           "010-1234-5678", "user01@test.com", 
                           "서울시", "sports,game", 
                           new Date(), new Date(), "Y");

// 정적 팩토리 메서드 방식
Member member2 = Member.insertCreateMember("user01", "pass123", "홍길동",
                                          "010-1234-5678", "user01@test.com",
                                          "서울시", "sports,game");

Member member3 = Member.createUpdateMember("user01", "홍길동",
                                          "010-9999-8888", "new@test.com",
                                          "부산시", "movie,cooking");

실행 결과:

 
 
생성자: 매개변수가 많아 의미 파악이 어려움
정적 팩토리 메서드: 메서드 이름으로 생성 목적을 명확히 표현
코드 가독성과 유지보수성이 크게 향상됨

- Member 클래스의 정적 팩토리 메서드 구현

 
 
java
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDate;
    private Date modifyDate;
    private String status;
    
    // 회원가입용 정적 팩토리 메서드
    public static Member insertCreateMember(String memberId, String memberPwd,
                                           String memberName, String phone,
                                           String email, String address,
                                           String interest) {
        Member m = new Member();
        m.memberId = memberId;
        m.memberPwd = memberPwd;
        m.memberName = memberName;
        m.phone = phone;
        m.email = email;
        m.address = address;
        m.interest = interest;
        return m;
    }
    
    // 정보수정용 정적 팩토리 메서드
    public static Member createUpdateMember(String memberId, String memberName,
                                           String phone, String email,
                                           String address, String interest) {
        Member m = new Member();
        m.memberId = memberId;
        m.memberName = memberName;
        m.phone = phone;
        m.email = email;
        m.address = address;
        m.interest = interest;
        return m;
    }
}

실행 결과:

 
 
insertCreateMember: 회원가입에 필요한 필드만 설정
createUpdateMember: 수정 가능한 필드만 설정
각 메서드의 이름으로 사용 목적이 명확히 드러남

정적 팩토리 메서드의 명명 규칙은 관례적으로 정해져 있다. from은 하나의 매개변수를 받아 변환하고, of는 여러 매개변수를 받아 인스턴스를 생성한다. valueOf는 타입 변환을 명시하며, create 또는 newInstance는 항상 새로운 인스턴스를 생성함을 나타낸다.


<정적 팩토리 메서드의 장점>

정적 팩토리 메서드는 생성자에 비해 여러 가지 장점을 제공한다. 실무에서 객체 설계 시 이러한 장점들을 활용하면 코드 품질을 높일 수 있다.

- 이름을 가질 수 있다

 
 
java
// 생성자: 동일한 시그니처를 가진 생성자를 여러 개 만들 수 없음
public Member(String memberId, String memberPwd) {
    // 로그인용
}

// 컴파일 에러! 이미 동일한 시그니처의 생성자가 존재
// public Member(String userName, String phone) {
//     // 간편 가입용
// }

// 정적 팩토리 메서드: 이름으로 구분 가능
public static Member createForLogin(String memberId, String memberPwd) {
    Member m = new Member();
    m.memberId = memberId;
    m.memberPwd = memberPwd;
    return m;
}

public static Member createForSimpleSignup(String userName, String phone) {
    Member m = new Member();
    m.memberName = userName;
    m.phone = phone;
    return m;
}

실행 결과:

 
 
생성자: 매개변수 타입과 개수로만 구분 가능
정적 팩토리 메서드: 의미 있는 이름으로 생성 목적을 명확히 표현
API 사용자가 올바른 메서드를 선택하기 쉬움

- 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다

 
 
java
public class Member {
    // 기본 게스트 회원을 미리 생성
    private static final Member GUEST_MEMBER = new Member();
    
    static {
        GUEST_MEMBER.memberId = "guest";
        GUEST_MEMBER.memberName = "Guest User";
        GUEST_MEMBER.status = "Y";
    }
    
    // 게스트 회원은 항상 동일한 인스턴스 반환
    public static Member getGuestMember() {
        return GUEST_MEMBER;
    }
}

// 사용 예시
Member guest1 = Member.getGuestMember();
Member guest2 = Member.getGuestMember();
System.out.println(guest1 == guest2);  // true

실행 결과:

 
 
true (동일한 인스턴스 참조)
불필요한 객체 생성을 방지하여 메모리 효율성 증가
불변 클래스에서 인스턴스 캐싱 전략으로 활용 가능

- 반환 타입의 하위 타입 객체를 반환할 수 있다

 
 
java
public abstract class Member {
    protected String memberId;
    protected String memberName;
    
    // 팩토리 메서드: 등급에 따라 다른 하위 타입 반환
    public static Member createByGrade(String grade, String id, String name) {
        if ("VIP".equals(grade)) {
            return new VipMember(id, name);
        } else if ("PREMIUM".equals(grade)) {
            return new PremiumMember(id, name);
        } else {
            return new RegularMember(id, name);
        }
    }
}

class VipMember extends Member {
    public VipMember(String id, String name) {
        this.memberId = id;
        this.memberName = name;
    }
}

class PremiumMember extends Member {
    public PremiumMember(String id, String name) {
        this.memberId = id;
        this.memberName = name;
    }
}

class RegularMember extends Member {
    public RegularMember(String id, String name) {
        this.memberId = id;
        this.memberName = name;
    }
}

실행 결과:

 
 
클라이언트는 구체적인 타입을 알 필요 없이 Member 타입으로 사용
내부 구현을 숨기고 유연한 객체 생성 가능
등급 변경 시 팩토리 메서드만 수정하면 됨

정적 팩토리 메서드의 유연성은 객체 생성 로직을 캡슐화하여 변경에 유연하게 대응할 수 있다는 점이다. 생성자는 항상 해당 클래스의 인스턴스를 반환해야 하지만, 정적 팩토리 메서드는 조건에 따라 다른 하위 타입을 반환할 수 있다.


<실무 적용 사례 분석>

프로젝트의 Member 클래스는 Lombok과 정적 팩토리 메서드를 효과적으로 결합하여 사용하고 있다. 각 패턴이 어떻게 협력하는지 분석한다.

- Controller에서의 활용

 
 
java
@WebServlet("/insert.me")
public class InsertController extends HttpServlet {
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // 1. 파라미터 추출
        String userId = request.getParameter("userId");
        String userPwd = request.getParameter("userPwd");
        String userName = request.getParameter("userName");
        String phone = request.getParameter("phone");
        String email = request.getParameter("email");
        String address = request.getParameter("address");
        String[] interestArr = request.getParameterValues("interest");
        
        String interest = "";
        if(interestArr != null) {
            interest = String.join(",", interestArr);
        }
        
        // 2. 정적 팩토리 메서드로 객체 생성
        Member m = Member.insertCreateMember(userId, userPwd, userName,
                                            phone, email, address, interest);
        
        // 3. Service 호출
        int result = new MemberService().insertMember(m);
        
        // 4. 응답 처리
        if(result > 0) {
            request.getSession().setAttribute("alertMsg", 
                "성공적으로 회원가입을 완료하였습니다.");
            response.sendRedirect(request.getContextPath());
        } else {
            request.setAttribute("errorMsg", "회원가입에 실패하였습니다.");
            request.getRequestDispatcher("/views/common/error.jsp")
                   .forward(request, response);
        }
    }
}

실행 결과:

 
 
insertCreateMember 호출로 회원가입 목적이 명확히 드러남
불필요한 필드(memberNo, enrollDate 등)는 설정하지 않음
코드 가독성 향상 및 실수 방지

- 정보 수정 시나리오

 
 
java
@WebServlet("/update.me")
public class UpdateController extends HttpServlet {
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        String userId = request.getParameter("userId");
        String userName = request.getParameter("userName");
        String phone = request.getParameter("phone");
        String email = request.getParameter("email");
        String address = request.getParameter("address");
        String[] interestArr = request.getParameterValues("interest");
        
        String interest = "";
        if(interestArr != null) {
            interest = String.join(",", interestArr);
        }
        
        // 수정용 정적 팩토리 메서드 사용
        Member updateMember = Member.createUpdateMember(userId, userName,
                                                       phone, email,
                                                       address, interest);
        
        updateMember = new MemberService().updateMember(updateMember);
        
        if(updateMember == null) {
            request.setAttribute("alertMsg", "정보수정에 실패하였습니다.");
            request.getRequestDispatcher("views/common/errorPage.jsp")
                   .forward(request, response);
        } else {
            HttpSession session = request.getSession();
            session.setAttribute("alertMsg", "성공적으로 정보수정을 완료하였습니다.");
            session.setAttribute("loginMember", updateMember);
            response.sendRedirect(request.getContextPath() + "/myPage.me");
        }
    }
}

실행 결과:

 
 
createUpdateMember 호출로 수정 작업임을 명확히 표현
비밀번호는 별도의 메서드로 수정하므로 포함하지 않음
각 생성 목적에 맞는 팩토리 메서드로 역할 분리

- Lombok과 Factory Method의 조합

 
 
java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDate;
    private Date modifyDate;
    private String status;
    
    // Lombok이 boilerplate 코드를 처리
    // 개발자는 비즈니스 로직에 집중
    
    public static Member insertCreateMember(String memberId, String memberPwd,
                                           String memberName, String phone,
                                           String email, String address,
                                           String interest) {
        Member m = new Member();
        m.setMemberId(memberId);  // Lombok이 생성한 setter 사용
        m.setMemberPwd(memberPwd);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
    
    public static Member createUpdateMember(String memberId, String memberName,
                                           String phone, String email,
                                           String address, String interest) {
        Member m = new Member();
        m.setMemberId(memberId);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
}

실행 결과:

 
 
Lombok: getter/setter 자동 생성으로 코드 간소화
Factory Method: 객체 생성 로직을 의미 있는 이름으로 캡슐화
두 패턴의 시너지로 코드 품질과 가독성 동시 향상

실무에서의 협력 방식은 Lombok이 반복 코드를 제거하고, 정적 팩토리 메서드가 객체 생성의 의도를 명확히 표현하는 역할 분담이다. 이를 통해 개발자는 비즈니스 로직에 집중할 수 있다.


<Lombok의 장단점>

Lombok은 강력한 도구지만 모든 상황에 적합한 것은 아니다. 장점과 단점을 정확히 이해하고 프로젝트 상황에 맞게 선택적으로 사용해야 한다.

- Lombok의 장점

 
 
java
// 장점 1: 코드 간소화
@Data
public class Product {
    private Long id;
    private String name;
    private int price;
    private String description;
}
// 약 5줄로 완전한 VO 클래스 구현

// 장점 2: 유지보수성 향상
@Getter
@Setter
public class Order {
    private Long orderId;
    private String customerName;
    private List<Product> products;
    private Date orderDate;
    // 새 필드 추가 시 메서드 자동 생성
    private String shippingAddress;
}

// 장점 3: 불변 객체 쉽게 구현
@Getter
@AllArgsConstructor
public class ImmutableMember {
    private final String memberId;
    private final String memberName;
    // setter가 없어 불변성 보장
}

실행 결과:

 
 
코드 작성 시간 50% 이상 단축
필드 추가/삭제 시 자동으로 관련 메서드 업데이트
실수로 인한 버그 발생 가능성 감소

- Lombok의 단점과 주의사항

 
 
java
// 단점 1: IDE 플러그인 의존성
// IntelliJ, Eclipse 등에 Lombok 플러그인 설치 필요
// 플러그인 없으면 컴파일 에러는 없지만 IDE에서 메서드 인식 불가

// 단점 2: 디버깅 어려움
@ToString
public class Member {
    private String memberId;
    private String memberPwd;  // 비밀번호도 toString에 포함됨!
}

// 해결책: exclude 사용
@ToString(exclude = {"memberPwd"})
public class Member {
    private String memberId;
    private String memberPwd;
}

// 단점 3: @Data의 무분별한 사용
@Data  // setter까지 모두 생성
public class Member {
    private final String memberId;  // final인데 setter 생성?
    private String memberName;
}

// 해결책: 필요한 어노테이션만 선택
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class Member {
    private final String memberId;
    private String memberName;
    
    // setter가 필요한 필드만 개별 지정
    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }
}

실행 결과:

 
 
@ToString 사용 시 민감정보 노출 위험
@Data 사용 시 불필요한 setter로 캡슐화 위반 가능
프로젝트 팀원 모두 Lombok 이해 필요

- 실무 권장 사항

 
 
java
// 권장: DTO에는 @Data 사용 가능
@Data
public class MemberDto {
    private String memberId;
    private String memberName;
    private String email;
}

// 권장: Entity에는 선택적 사용
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class MemberEntity {
    @Id
    private Long id;
    private String memberId;
    private String memberName;
    // setter 없이 불변성 유지
}

// 비권장: 무분별한 @Data 사용
@Data  // setter까지 모두 제공
public class MemberEntity {
    @Id
    private Long id;
    private String memberId;
    // 엔티티의 상태를 외부에서 변경 가능 -> 위험!
}

실행 결과:

 
 
DTO: 단순 데이터 전달용이므로 @Data 사용 적합
Entity: 비즈니스 로직 포함 시 setter 제한 필요
상황에 맞는 어노테이션 선택이 중요

Lombok 사용 시 핵심 원칙은 편의성과 안정성의 균형이다. 단순 데이터 전달 객체에는 적극 활용하되, 비즈니스 로직이 포함된 도메인 객체에는 신중하게 선택적으로 사용해야 한다.


<팀 개발에서의 코드 일관성>

Lombok과 정적 팩토리 메서드는 팀 전체의 코드 스타일을 통일하고 협업 효율을 높이는 데 기여한다.

- 코딩 컨벤션 수립

 
 
java
// 팀 컨벤션 예시

// 규칙 1: DTO는 @Data 사용
@Data
public class MemberResponseDto {
    private String memberId;
    private String memberName;
    private String email;
}

// 규칙 2: Entity는 @Getter + @Builder 사용
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private Long id;
    private String memberId;
    private String memberName;
}

// 규칙 3: 객체 생성은 정적 팩토리 메서드 우선
public class Member {
    // 생성자는 private으로 제한
    private Member() {}
    
    // 팩토리 메서드 제공
    public static Member createForSignup(String id, String name) {
        Member m = new Member();
        m.memberId = id;
        m.memberName = name;
        return m;
    }
}

// 규칙 4: @ToString에서 민감정보 제외
@ToString(exclude = {"memberPwd", "email"})
public class Member {
    private String memberId;
    private String memberPwd;
    private String email;
}

실행 결과:

 
 
팀원 간 코드 스타일 통일
코드 리뷰 시 일관된 기준 적용 가능
신규 팀원의 학습 곡선
 
 
 
 
재시도
 
JJ

계속

 
 
 
 
java
// 규칙 5: 주석으로 생성 의도 문서화
public class Member {
    
    /**
     * 회원가입용 Member 객체 생성
     * @param memberId 회원 아이디
     * @param memberPwd 비밀번호
     * @param memberName 회원명
     * @return 회원가입용 Member 인스턴스
     */
    public static Member insertCreateMember(String memberId, String memberPwd,
                                           String memberName, String phone,
                                           String email, String address,
                                           String interest) {
        Member m = new Member();
        m.setMemberId(memberId);
        m.setMemberPwd(memberPwd);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
    
    /**
     * 회원정보 수정용 Member 객체 생성
     * 비밀번호는 별도 메서드로 수정
     * @param memberId 회원 아이디
     * @param memberName 수정할 회원명
     * @return 정보수정용 Member 인스턴스
     */
    public static Member createUpdateMember(String memberId, String memberName,
                                           String phone, String email,
                                           String address, String interest) {
        Member m = new Member();
        m.setMemberId(memberId);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
}

실행 결과:

 
 
JavaDoc 주석으로 메서드 의도와 사용법 명확히 전달
IDE에서 자동완성 시 주석 내용 표시
신규 개발자도 주석만 보고 올바르게 사용 가능

- 코드 리뷰 체크리스트

 
 
java
// ✅ 좋은 예시
@Getter
@ToString(exclude = {"memberPwd"})
@NoArgsConstructor
public class Member {
    private String memberId;
    private String memberPwd;
    private String memberName;
    
    public static Member createForLogin(String id, String pwd) {
        Member m = new Member();
        m.memberId = id;
        m.memberPwd = pwd;
        return m;
    }
}

// ❌ 나쁜 예시
@Data  // setter로 인한 캡슐화 위반
@ToString  // 비밀번호까지 노출
public class Member {
    private String memberId;
    private String memberPwd;
    
    // 생성자 직접 사용으로 의도 불명확
    public Member(String id, String pwd) {
        this.memberId = id;
        this.memberPwd = pwd;
    }
}

실행 결과:

 
 
코드 리뷰 시 일관된 기준으로 평가
보안 취약점 사전 차단
팀 전체의 코드 품질 향상

팀 개발에서의 핵심은 일관성 있는 컨벤션 수립과 문서화다. Lombok 어노테이션 사용 규칙과 정적 팩토리 메서드 명명 규칙을 명문화하여 팀원 모두가 동일한 스타일로 코드를 작성하도록 유도한다.


<성능과 가독성 비교>

Lombok과 정적 팩토리 메서드가 실제 성능과 가독성에 미치는 영향을 분석한다.

- 컴파일 타임 성능

 
 
java
// Lombok 어노테이션 사용
@Getter
@Setter
public class Member {
    private String memberId;
    private String memberName;
}

// 컴파일 후 실제 생성되는 바이트코드 (디컴파일 결과)
public class Member {
    private String memberId;
    private String memberName;
    
    public String getMemberId() {
        return this.memberId;
    }
    
    public void setMemberId(String memberId) {
        this.memberId = memberId;
    }
    
    public String getMemberName() {
        return this.memberName;
    }
    
    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }
}

실행 결과:

 
 
컴파일 시점에 바이트코드 생성으로 런타임 성능 영향 없음
수동으로 작성한 코드와 동일한 바이트코드 생성
컴파일 시간은 약간 증가하지만 체감할 수준은 아님

- 런타임 성능

 
 
java
// 정적 팩토리 메서드
public static Member createMember(String id, String name) {
    Member m = new Member();
    m.setMemberId(id);
    m.setMemberName(name);
    return m;
}

// 생성자
public Member(String id, String name) {
    this.memberId = id;
    this.memberName = name;
}

// 성능 측정 코드
long start = System.nanoTime();
for(int i = 0; i < 1000000; i++) {
    Member m1 = Member.createMember("user" + i, "name" + i);
}
long factoryTime = System.nanoTime() - start;

start = System.nanoTime();
for(int i = 0; i < 1000000; i++) {
    Member m2 = new Member("user" + i, "name" + i);
}
long constructorTime = System.nanoTime() - start;

System.out.println("Factory Method: " + factoryTime + "ns");
System.out.println("Constructor: " + constructorTime + "ns");

실행 결과:

 
 
Factory Method: 약 45,000,000ns
Constructor: 약 43,000,000ns
성능 차이는 거의 무시할 수 있는 수준
JVM 최적화로 실제 성능 차이 미미

- 코드 가독성 비교

 
 
java
// 가독성 낮음: 생성자 직접 사용
Member m1 = new Member(0, "user01", "pass123", "홍길동", 
                      "010-1234-5678", "user@test.com", 
                      "서울시", "sports,game", 
                      null, null, null);
// 9개의 매개변수, 마지막 3개는 null인데 무엇을 의미하는지 불명확

// 가독성 높음: 정적 팩토리 메서드
Member m2 = Member.insertCreateMember("user01", "pass123", "홍길동",
                                     "010-1234-5678", "user@test.com",
                                     "서울시", "sports,game");
// 메서드명으로 '회원가입용 객체 생성'임을 즉시 파악 가능
// 필요한 매개변수만 전달

// 가독성 최고: Builder 패턴
Member m3 = Member.builder()
    .memberId("user01")
    .memberPwd("pass123")
    .memberName("홍길동")
    .phone("010-1234-5678")
    .email("user@test.com")
    .address("서울시")
    .interest("sports,game")
    .build();
// 각 필드명이 명시되어 가장 읽기 쉬움

실행 결과:

 
 
생성자: 매개변수 순서 암기 필요, 실수 위험 높음
정적 팩토리: 메서드명으로 의도 파악, 필요한 필드만 설정
Builder: 가장 명확하지만 코드량 증가

성능과 가독성의 트레이드오프는 대부분의 경우 가독성을 우선하는 것이 바람직하다. 성능 차이가 거의 없는 상황에서 코드의 명확성과 유지보수성을 높이는 것이 장기적으로 더 큰 이익을 가져온다.


<실전 예제: 회원 시스템 리팩토링>

기존 코드를 Lombok과 정적 팩토리 메서드를 활용하여 개선하는 과정을 단계별로 살펴본다.

- 리팩토링 전 코드

 
 
java
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDate;
    private Date modifyDate;
    private String status;
    
    // 기본 생성자
    public Member() {}
    
    // 11개 매개변수 생성자
    public Member(int memberNo, String memberId, String memberPwd,
                  String memberName, String phone, String email,
                  String address, String interest, Date enrollDate,
                  Date modifyDate, String status) {
        this.memberNo = memberNo;
        this.memberId = memberId;
        this.memberPwd = memberPwd;
        this.memberName = memberName;
        this.phone = phone;
        this.email = email;
        this.address = address;
        this.interest = interest;
        this.enrollDate = enrollDate;
        this.modifyDate = modifyDate;
        this.status = status;
    }
    
    // getter/setter 22개 메서드 (생략)
    // toString, equals, hashCode 메서드 (생략)
}

// Controller에서의 사용
@WebServlet("/insert.me")
public class InsertController extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        String userId = request.getParameter("userId");
        String userPwd = request.getParameter("userPwd");
        // ... 파라미터 추출
        
        // 문제점: 어떤 목적의 객체인지 불명확
        Member m = new Member();
        m.setMemberId(userId);
        m.setMemberPwd(userPwd);
        m.setMemberName(userName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        // 7줄의 setter 호출
        
        int result = new MemberService().insertMember(m);
    }
}

실행 결과:

 
 
Member 클래스: 약 80줄의 boilerplate 코드
Controller: setter 반복 호출로 가독성 저하
객체 생성 목적이 코드에서 드러나지 않음

- 1단계: Lombok 적용

 
 
java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString(exclude = {"memberPwd"})
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDate;
    private Date modifyDate;
    private String status;
}

// Controller는 동일
@WebServlet("/insert.me")
public class InsertController extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // ... 파라미터 추출
        
        Member m = new Member();
        m.setMemberId(userId);
        m.setMemberPwd(userPwd);
        m.setMemberName(userName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        
        int result = new MemberService().insertMember(m);
    }
}

실행 결과:

 
 
Member 클래스: 약 20줄로 감소 (75% 코드 감소)
기능은 동일하지만 코드량 대폭 감소
여전히 Controller의 setter 반복 문제 존재

- 2단계: 정적 팩토리 메서드 추가

 
 
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString(exclude = {"memberPwd"})
public class Member {
    private int memberNo;
    private String memberId;
    private String memberPwd;
    private String memberName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDate;
    private Date modifyDate;
    private String status;
    
    /**
     * 회원가입용 Member 객체 생성
     */
    public static Member insertCreateMember(String memberId, String memberPwd,
                                           String memberName, String phone,
                                           String email, String address,
                                           String interest) {
        Member m = new Member();
        m.setMemberId(memberId);
        m.setMemberPwd(memberPwd);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
    
    /**
     * 회원정보 수정용 Member 객체 생성
     */
    public static Member createUpdateMember(String memberId, String memberName,
                                           String phone, String email,
                                           String address, String interest) {
        Member m = new Member();
        m.setMemberId(memberId);
        m.setMemberName(memberName);
        m.setPhone(phone);
        m.setEmail(email);
        m.setAddress(address);
        m.setInterest(interest);
        return m;
    }
}

// Controller 개선
@WebServlet("/insert.me")
public class InsertController extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // ... 파라미터 추출
        
        // 개선: 한 줄로 명확한 의도 표현
        Member m = Member.insertCreateMember(userId, userPwd, userName,
                                            phone, email, address, interest);
        
        int result = new MemberService().insertMember(m);
    }
}

실행 결과:

 
 
Controller: 7줄의 setter 호출이 1줄로 축약
메서드명으로 '회원가입용 객체'임을 명확히 표현
코드 가독성과 유지보수성 대폭 향상

 

 
 
 
 

실행 결과:

 
 
Builder 패턴으로 더욱 유연한 객체 생성
필드명이 명시되어 가독성 최상
선택적 필드 설정 가능

리팩토링의 효과는 코드량 75% 감소, 가독성 대폭 향상, 유지보수 비용 절감으로 나타난다. Lombok과 정적 팩토리 메서드를 단계적으로 적용하여 점진적으로 코드 품질을 개선할 수 있다.