생성자를 선언하고 그 생성자로 객체를 초기화
=> 비지니스 로직 수정, DB 스키마 변경 등으로 인해 클래스 구조가 변경되면 생성자를 추가, 변경해야할 필요가 있음
생성자가 아닌 static 메소드를 이용해 객체를 생성
LocalDateTime.now(); // 현재 시간을 담은 LocalDateTime 객체 생성
특정 필드에 접근할 수 있는 메소드를 선언해 간접적으로 값을 변경하는 패턴
생성자를 사용한 초기화와 달리 파라미터에 의존적이지 않은 유연한 객체 초기화 가능
class Class {
String A,B,C;
}
Class obj = Class.builder().A("a").B("bb").build();
참조변수, 객체가 가진 필드의 변경은 예상치 못한 side effect를 유발할 수있음
1) setter 사용을 줄이기
2) unmodifable collection (ex: Collections.unmodifiableList(list))
3) 데이터를 전달받는 객체(DTO)와 비지니스 로직을 처리하고 data layer에 접근하는데 사용하는 객체(VO,Entity)로 계층을 분리 (ex: JPA 활용)
와 같은 방법으로 객체가 변경될 수 있는 가능성을 낮출 수 있음
자바에서 Stream API를 사용할 경우 기존 Collection 객체를 변경하지 않고 중간연산 결과를 확인하거나, Stream에 고차함수를 거친 새로운 결과를 생성할 수 있음(side effect 제거)
DTO(Data Transfer Object)는 view layer에서 데이터를 전달받고 전달하는데 사용하는 객체로 정의할 수 있음
view layer에서 사용하기 때문에 비지니스 로직을 포함하거나 영향을 받아 변경되지 않아야함
VO(Value Obejct), Entity는 data layer와 데이터를 주고받고 비지니스 로직에서 사용할 수 있는 객체로 정의할 수 있음
비지니스 로직에 의해 데이터가 변경될 가능성이 있으므로 주로 getter/setter를 통해 데이터를 가져오고 변경하는 경우가 많음
=> 데이터를 직접 변경하는 경우 로직이 복잡해지거나 비동기 처리를 하는 등 흐름을 알기 어려운 경우 객체 변화를 쉽게 파악하기 어려울 수 있음
setter가 없다고 가정하면 Entity는 선언할 때 초기화 해준 필드 값을 그대로 가지고 있다고 생각할 수 있고 그에 따른 side effect도 피할 수 있음(Builder 패턴으로 Entity,VO 객체 선언!!)
setter가 없으면 아예 필드 값을 변경할 수 없느냐하면 그것은 아님
대표적인 비지니스 로직 형태를 생각해보면
- obj.getAge()를 통해 얻은 나이를 특정 값과 비교해 특정 로직 수행
- 20xx.12.31이 지나면 obj.Age 값을 obj.setAge(obj.getAge()+1)으로 변경
위와 같이 obj의 age에 대해 getter/setter를 사용하는 예를 들 수 있음
두 예제를 getter/setter 없이 구현하기 위해 클래스에 새로운 메소드를 선언할 수 있음
class class {
Integer age;
public Boolean isNewYear(){
// 새로운 해가 되었으면 return true
}
public changeAgeWhenNewYear() {
Integer currentAge = age;
System.out.println("current age = %d", currentAge);
if(isNewYear()){
age += 1
System.out.println("Happy New Year");
}
}
}
위와 같이 클래스 내부적으로 필드에 접근해 데이터에 접근/변경하도록 변경하면 메소드의 역할을 명확하게 기술할 수 있고, 직접적인 데이터 변경으로 인한 side effect도 최소화할 수 있음