본문 바로가기
Java

자바 빌더패턴 @Builder , 메서드 체인(Method chaining) 알아보기

by Ms.Pudding 2022. 1. 15.

빌더패턴이란...?

 

복잡한 객체의 생성과 그 표현을 분리하는 것이다. 그렇게 함으로써, 같은 생성자로 다른 표현을 만들 수 있다.

 

빌더패턴이 필요한 이유를 찾아보자.

 

먼저, 메서드 체인(method chaining)이라는 개념부터 알아야한다. 메서드가 객체를 반환하게 되면, 메서드의 반환 값인 객체를 통해 또 다른 함수를 호출하는 것을 말한다. 생성자에 return 특정 변수가 아닌, return this를 사용하여 클라스 인스턴스 변수를 모두 가져오는 방법이 있다.

 

final class Student{

	private int id;
    private String name;
   
    
   //setter
   public Student setId(int id)
   {
   	this.id = id;
    return this; 
   }
   
   public Student setName(String name)
   {
   		this.name = name;
        return this;
   }
   
   @Override
   public String toString()
   {
   	return "id = " + this.id + ", name =" + this.name;
   
   }

}

public class MethodChanin{
	public static void main(String[] args){
    
    	 Student s1 = new Student();
         Student s2 = new Student();
         
         s1.setId(1),setName("ram");
         s2.setId(2).setName("shyam");
    }
}

 

원래 setter를 쓸 때 , 

public void setName(String name)

{

     this.name = name;

}

이런식으로 리턴타입을 void로 하였다. 하지만 return this값을 줘서 setName()을 한번 쓸 때마다 클라스 필드 변수를 모두 가져올 수 있게 하였다. 원래는 s1.setId(1)이렇게 한번만 쓸 수 있는데 return this로 클라스 변수 모두를 부르기 떄문에 s1.setId(1).setName("ram") 이렇게 쓸 수 있는 것이다.

 

메소드 체인은 이처럼 유용한 디자인 패턴일 수 있다. 하지만 하나의 변수 값을 동시에 불러내면 값이 안나오거나 에러가 나올 수 있다.

 

메서드 체인 에러나는 경우 

final class Studnet{

	private int id;
    private String name;
    private String address;
    
    //setter
    public Student setId(int id)
    {
    	this.id = id;
        return this;
    }
    
    public Student setName(String name)
    {
    	this.name = name;
        return name;
    }
    
    public Student setAddress(String address)
    {
    	this.address = address;
        return this;
    }
    
    @Override
    public String toString()
    {
    	return "id = " + this.id + ", name = " + this.name + ", address = " + this.address;
    }

}
//client side code
class StudentReceiver{

	private final Student student = new Student();
    
    public StudentReceiver()
    {
    	Thread t1 = new Thread(new Runnable(){
        
        @Override
        public void run()
        {
        	student.setId(1).setName("ram").setAddress("Noida");
        }
        
        });
        
        Thread t2 = new Thread(new Runnable(){
        
        @Override
        public void run()
        {
        	student.setId(2).setName("shyam").setAddress("delhi");
        }
        
        });
        
        t1.start();
        t2.start();
    
    }
    
    public Student getStudent()
    {
    	return student;
    }

}

//driver class
public class BuilderNeedDemo{
	public static void main(String[] args){
    	studentReceiver sr = new StudentReceiver();
        System.out.println(sr.getStuent());
    }
}

 

위에서 run() 에 변수값을 주었는데 , 이때 thread 객체에 둘이 같이 들어가있고, 동시에 변수 값을 찾으려고 한다.

따라서 에러가 발생하거나 id = 2 name = null address = noida 이런식으로 불규칙으로 발생할 수 있다. 

이러한 결과를 막기 위해 Builder 패턴을 사용한다.

 

 

빌더패턴 사용법: 

 

// Server Side Code
final class Student {
  
    // final instance fields
    private final int id;
    private final String name;
    private final String address;
  
    public Student(Builder builder)
    {
        this.id = builder.id;
        this.name = builder.name;
        this.address = builder.address;
    }
  
    // Static class Builder
    public static class Builder {
  
        /// instance fields
        private int id;
        private String name;
        private String address;
  
        public static Builder newInstance()
        {
            return new Builder();
        }
  
        private Builder() {}
  
        // Setter methods
        public Builder setId(int id)
        {
            this.id = id;
            return this;
        }
        public Builder setName(String name)
        {
            this.name = name;
            return this;
        }
        public Builder setAddress(String address)
        {
            this.address = address;
            return this;
        }
  
        // build method to deal with outer class
        // to return outer instance
        public Student build()
        {
            return new Student(this);
        }
    }
  
    @Override
    public String toString()
    {
        return "id = " + this.id + ", name = " + this.name + 
                               ", address = " + this.address;
    }
}
  
// Client Side Code
class StudentReceiver {
  
    // volatile student instance to ensure visibility
    // of shared reference to immutable objects
    private volatile Student student;
  
    public StudentReceiver()
    {
  
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run()
            {
                student = Student.Builder.newInstance()
                              .setId(1)
                              .setName("Ram")
                              .setAddress("Noida")
                              .build();
            }
        });
  
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run()
            {
                student = Student.Builder.newInstance()
                              .setId(2)
                              .setName("Shyam")
                              .setAddress("Delhi")
                              .build();
            }
        });
  
        t1.start();
        t2.start();
    }
  
    public Student getStudent()
    {
        return student;
    }
}
  
// Driver class
public class BuilderDemo {
    public static void main(String args[])
    {
        StudentReceiver sr = new StudentReceiver();
        System.out.println(sr.getStudent());
    }
}

 

위에서 Builder.newInstance()를 하면 새로운 Builder객체를 만들어낸다.

 
public static Builder newInstance()
        {
            return new Builder();
        }
  따라서 newInstance()로 인하여 각각의 변수값을 찾을 때 t1과 t2가 새로운 메모리 공간으로 들어가서 겹치지 않는다.
 
 
 
참고:
 

 

댓글