
Setter 메소드를 사용하지 않도록 지양하는 이유는 객체의 무분별한 변경을 방지하고, 객체의 일관성을 유지하기 위함임. Setter 메소드는 객체의 내부 상태를 외부에서 쉽게 수정할 수 있게 해주지만, 이로 인해 객체의 캡슐화가 깨지고, 예상치 못한 부작용이 발생할 수 있음. 특히, 도메인 객체나 엔티티 객체에서 Setter 메소드를 남발하는 것은 여러 가지 문제를 일으킬 수 있음.
다음은 Setter 사용을 지양하는 주요 이유임.
Setter 메소드를 사용하면 객체의 내부 상태를 쉽게 변경할 수 있기 때문에 객체의 불변성을 유지하기 어려움.
불변 객체는 생성 시 상태가 완전히 결정되며, 이후 절대 변하지 않는 객체임. 불변 객체는 스레드 안전성, 일관성 유지에 유리함.
Setter를 사용하게 되면 객체가 중간에 변경될 수 있으므로, 이를 피하기 위해 Setter 대신 생성자 또는 메소드 체인 방식을 사용하여 객체의 상태를 설정하고, 이후 변경되지 않도록 설계하는 것이 좋음.
Setter 사용의 문제 예시:
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
User user = new User();
user.setName("John");
user.setAge(25);
// 나중에 외부에서 값이 변경될 수 있음
user.setName("Doe"); // 예기치 않은 상태 변경
이처럼 객체의 상태가 예기치 않게 변경될 수 있기 때문에, 객체의 상태가 불변하거나 일관된 상태로 유지되기를 원할 때는 Setter 사용을 지양해야 함.
Setter를 사용하면 객체의 상태가 부분적으로 설정될 수 있어, 불완전한 상태가 될 가능성이 있음.
생성자를 통해 객체의 모든 필드를 초기화하는 경우, 객체는 일관된 상태로 만들어지지만, Setter를 사용하면 특정 필드만 부분적으로 변경할 수 있기 때문에, 객체가 일관된 상태를 잃을 위험이 있음.
public class Order {
private String orderId;
private String productId;
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public void setProductId(String productId) {
this.productId = productId;
}
}
// 주문을 생성할 때
Order order = new Order();
order.setOrderId("123"); // orderId는 설정되었으나, productId는 설정되지 않음
위 코드에서는 Order 객체가 생성되었지만, productId가 설정되지 않아 불완전한 상태임. 생성자를 통해 모든 필드를 설정하는 방식이 일관성 유지를 위한 좋은 방법임.
캡슐화는 객체의 내부 상태를 외부로부터 보호하고, 내부 상태를 외부에서 임의로 변경하지 못하도록 하는 객체 지향 설계 원칙 중 하나임.
Setter 메소드를 제공하면, 객체의 내부 상태가 외부에 노출되어 외부에서 직접 변경할 수 있기 때문에 캡슐화가 깨지게 됨. 이는 객체의 변경 가능성을 증가시키고, 예측 불가능한 결과를 초래할 수 있음.
내부 상태는 객체의 책임 내에서만 변경되고 관리되는 것이 바람직함.
public class Account {
private double balance;
public void setBalance(double balance) {
this.balance = balance; // 외부에서 마음대로 balance 변경 가능
}
}
캡슐화를 유지하는 방식
public class Account {
private double balance;
public Account(double balance) {
this.balance = balance; // 생성자를 통해 초기화
}
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
}
}
public void withdraw(double amount) {
if (amount > 0 && this.balance >= amount) {
this.balance -= amount;
}
}
}
위 방식처럼 Setter를 사용하지 않고, 필요한 경우에만 내부 상태를 변경하는 비즈니스 로직 메소드를 제공하면 객체의 캡슐화가 유지됨.
Setter 메소드를 사용하면 외부에서 객체의 상태를 임의로 변경할 수 있어, 시스템의 다른 부분에 의도치 않은 부작용을 일으킬 수 있음.
특히, 도메인 객체나 엔터티 객체에서 내부 상태를 외부에서 마음대로 변경하게 되면, 객체의 상태가 예상치 못한 시점에 변경될 수 있어 데이터 무결성이 깨지거나 예상치 못한 버그가 발생할 수 있음.
public class User {
private String name;
public void setName(String name) {
this.name = name;
}
}
// 특정 비즈니스 로직에서
user.setName("John"); // 외부에서 잘못된 값이 설정될 경우 부작용 발생
Setter 대신 비즈니스 로직을 포함한 메소드를 제공하면, 부작용을 최소화할 수 있음.
객체의 상태 변경은 의미가 명확한 메소드를 통해 이루어져야 함. 즉, 단순히 Setter로 값을 설정하기보다, 도메인 로직을 담고 있는 메소드를 통해 상태가 변경되도록 설계해야 함.
이로 인해 객체의 상태 변경이 명확한 목적을 가지고 수행되며, 코드의 가독성과 유지보수성이 높아짐.
public class User {
private String name;
public User(String name) {
this.name = name;
}
// 상태 변경은 의미가 명확한 메소드로 처리
public void changeName(String newName) {
if (newName != null && !newName.isEmpty()) {
this.name = newName;
}
}
}
위 예시에서는 Setter 대신, 상태 변경에 의미가 있는 메소드를 제공하여 명확한 의도를 전달하고, 객체의 상태가 일관성 있게 유지되도록 설계함.
도메인 객체에서 Setter를 남발하면, 객체가 자신의 상태를 스스로 관리하지 못하고 외부에서 상태를 변경하는 경우가 많아짐. 이는 객체의 책임을 약화시킴.
도메인 객체는 자신의 상태를 자기 완결적으로 관리해야 하며, 외부에서 제어될 경우 객체의 응집성이 약화될 수 있음.
Setter 대신 도메인 메소드를 활용해 비즈니스 로직을 포함시키는 것이 좋음.
객체의 초기 상태는 생성자를 통해 설정되도록 하고, 객체가 생성될 때 필요한 모든 값이 설정되도록 함.
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
Setter를 사용하지 않고, 객체의 모든 필드를 final로 선언하여 불변 객체로 만들 수 있음.
public class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 필드 값 변경 불가
}
복잡한 객체를 생성할 때는 빌더 패턴을 사용하여 유연하게 객체 생성을 처리할 수 있음. 이는 상태 변경 없이 객체를 일관된 방식으로 생성할 수 있게 해줌.
public class User {
private final String name;
private final int age;
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
public static class Builder {
private String name;
private int age;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public User build() {
return new User(this);
}
}
}
빌더 패턴은 유연한 객체 생성과 일관성 유지를 위한 좋은 방법 중 하나임.
실무에서 Setter 사용을 지양하는 이유는 객체의 일관성, 캡슐화, 불변성을 유지하고 부작용을 방지하기 위함임. 객체의 상태가 외부에서 무분별하게 변경되지 않도록 하고, 객체의 책임을 명확히 하기 위해 Setter 대신 생성자, 비즈니스 로직을 포함한 메소드, 빌더 패턴 등의 방법을 사용하는 것이 좋음. 이를 통해 객체 지향적인 설계를 유지하고, 코드의 유지보수성과 안정성을 높일 수 있음.