
들어가기 전
Java에 대해 공부하다 보니 'Getter와 Setter를 지양하라'는 말을 알게 됐다.
책 '소트웍스 앤솔러지'의 객체지향 생활체조 원칙에도 포함되어 있는데, 왜 Getter와 Setter를 지양하라는 것일까?
이전에 Spring을 공부하면서 Setter는 데이터의 무결성을 해칠 가능성이 있기 때문에 지양해야 된다는 것은 많이 들었지만 Getter를 지양하라는 것은 생소했다.
그래서 이번 기회에 Getter / Setter를 지양해야 되는 이유와 그에 대한 대안을 알아보려 한다.
Getter / Setter를 왜 지양하라는 거지?
객체를 만들고 필드를 선언할 때, 우리는 보통 캡슐화를 위해 private 접근자로 선언한다.
하지만 Getter / Setter는 다른 객체에서 데이터를 받기 위해 public 접근자로 선언하게 되고, 이는 곧 private로 선언한 필드가 public으로 노출되는 것과 다름없다.
Getter를 통해 다른 객체에서 마음껏 해당 객체내의 데이터를 볼 수가 있고, Setter를 통해서 다른 객체가 마음껏 객체 내의 필드를 수정할 수 있게 된다. 결국, 캡슐화를 하기 위해 필드를 private로 선언했던 의미가 사라지게 되는 것이다.
객체지향 프로그래밍은 객체들이 각자 맡은 역할과 책임을 성실히 수행하며, 다른 객체들과 협력하는 것이다.
객체 내의 상태가 어떤지, 역할을 어떻게 수행하는지 다른 객체는 알 필요가 없다. 단지 다른 객체에게 메시지를 보내고 원하는 결과를 받아오기만 하면 되는 것이다.
하지만 다른 객체의 데이터를 가져와 비즈니스 로직을 수행하는데에 사용하게 된다면 역할이 제대로 분배되지 않을 가능성이 생기고, 메시지를 보내 협력한다는 객체지향적인 목표에서 멀어질 것이다.
우리는 객체지향 언어를 사용하여 객체지향 프로그래밍을 하기 때문에 위와 같은 상황은 피해야 한다.
이제 Getter / Setter를 지양해야되는 이유에 대해 코드를 통해 알아보자.
Setter를 지양해야 하는 이유
1. Setter는 데이터를 변경하는 이유를 알 수 없다
class Product {
private int price;
public Product(int price) {
this.price = price;
}
public void setPrice(int price) {
this.price = price;
}
}
public class ProductService {
public static void main(String[] args) {
Product product = new Product(1000);
...
product.setPrice(3000);
}
}
위 코드에서 ProductService는 Product 객체를 1000원의 가격으로 생성하고 있다.
이후, 비즈니스 로직을 수행한 뒤 모종의 이유로 처음에 생성했던 Product 객체의 가격을 3000원으로 수정하려고 한다.
이 때 Setter를 사용하게 된다면 어떻게 될까?
Setter 메서드를 봤을 때 어떤 이유에서 Product 객체의 가격을 수정하려고 하는건지 알 수가 없다.
결국 다른 사람이 내 코드를 볼 때 다시 한 번 위쪽 부분의 코드를 보는 경우가 생길 수 있고, 이는 가독성 또한 해치게 된다.
이렇게 Setter를 사용하게 된다면 객체 내의 데이터가 변경되는 명확한 이유를 알 수가 없게 된다.
2. 다른 객체로 책임이 분산된다
class Product {
private int stock;
public Product(int stock) {
this.stock = stock;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
public class ProductService {
public void sale(Product product) {
product.setStock(product.getStock() - 1);
}
}
위 코드에서 ProductService 클래스 내의 sale 메서드에서 상품이 판매됐을 때 Setter를 사용해서 재고량을 수정하고 있다.
그런데 상품의 재고량을 수정하는 책임은 ProductService가 아닌 Product 객체에 있다. 재고량은 Product의 상태이고, 자신의 상태를 책임지는 것은 자기 자신이기 때문이다.
그래서 객체의 역할에 따른 책임의 분배가 제대로 되지 않고 있을 뿐만 아니라 Product의 상태를 외부에서 수정함으로써 캡슐화와 데이터 무결성이 깨지고 있다.
이렇게 Setter를 사용하게 된다면 다른 객체로 책임이 분산되고, 객체지향을 위한 캡슐화가 지켜지지 않게 된다.
Getter를 지양해야 하는 이유
Getter가 조회에만 사용되지 않는 경우가 생길 수 있다
public class ProductService {
public void sale(Product product) {
product.setStock(product.getStock() - 1);
}
}
앞에서 봤던 sale() 메서드를 다시 한 번 보자.
상품이 팔리면 Getter를 통해 Product 객체 내의 재고량을 가지고 와서 -1 연산을 한 뒤에 재고량을 수정하고 있다.
Getter를 객체지향적으로 사용하기 위해서는 비즈니스 로직에 사용하는 것이 아닌 데이터를 조회할 때만 사용하도록 해야한다.
DTO와 같이 데이터를 전달하는 객체를 통해서 뷰에 객체의 데이터를 출력해야 되는 경우 Getter는 단순 조회에 사용되므로 객체지향면에서 문제가 생기지 않는다.
하지만 위와 같이 비즈니스 로직에 사용하게 되어 객체의 책임을 제대로 분산할 수 없게 되는 경우가 발생할 수 있다.
Getter / Setter 를 어떻게 지양할까?
지금까지 Getter와 Setter를 지양해야 하는 이유에 대해서 알아보았다.
그런데 프로그래밍을 하다보면 객체의 값을 불러오고, 수정해야 되는 경우가 매우 많다.
그러면 어떻게 이 기능을 Getter / Setter를 지양하며 객체지향적으로 프로그래밍해야할까?
객체의 역할에 맞는 책임을 가진 메서드를 사용하자
앞에서 봤던 재고량-판매 관련 코드를 다시 한 번 보고, 이를 Getter / Setter를 사용하지 않고 객체지향적으로 리팩토링한 코드를 보자.
// Before
class Product {
private int stock;
public Product(int stock) {
this.stock = stock;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
public class ProductService {
public void sale(Product product) {
product.setStock(product.getStock() - 1);
}
}
// After
class Product {
private int stock;
public Product(int stock) {
this.stock = stock;
}
public void saled() {
stock -= 1;
}
}
public class ProductService {
public void sale(Product product) {
product.saled();
}
}
두 코드의 차이점은 무엇일까?
단순히 보면 Getter / Setter가 사라지고 판매에 따른 재고량 수정 기능이 Product 객체에 옮겨갔다.
하지만 이를 통해서 앞에서 말했던 Getter / Setter를 통해 발생할 수 있는 객체지향적인 문제점이 모두 해결됐다.
Getter를 조회에만 사용하지않고 비즈니스 로직에서 사용함으로써 Product이 가져야 할 재고량 수정이라는 책임을 ProductService가 가지는 문제가 해결됐다.
Setter를 사용함으로써 판매에 따른 재고량 수정이라는 기능이 명확한 이름을 가진 메서드가 가지면서 한 눈에 어떤 기능이 동작하는지 볼 수 있게 됐다. 또한 Getter와 마찬가지로 책임의 분산이 적절히 수행됐다.
결국 Getter / Setter를 사용하지 않고 객체지향적으로 프로그래밍하는 방법은 객체의 역할에 맞는 책임을 가진 메서드를 사용하는 것이다.
항상 기능을 구현할 때 이 기능이 이 객체에서 정말 수행되야 되는것인지 생각하며 프로그래밍을 한다면 Getter와 Setter를 지양하면서 객체지향적으로 프로그래밍하는 능력을 기를 수 있을 것이다.
마치며
꼭 기억해야 할 부분은 '지양하라'는 점이다. '사용하지 말라'는 것이 아니다.
앞에서 말했듯이, DTO와 같이 데이터 전달만을 목적으로 한 객체의 경우에는 역할과 책임이 데이터 전달이기 때문에 Getter / Setter를 사용해도 될 것이다.
결국 목표는 객체지향이기 때문에 상황에 따라 유연하게 사용할 수 있도록 노력해야 할 것이다.
Reference
https://colabear754.tistory.com/173
[OOP] Getter와 Setter는 지양하는게 좋다
목차 들어가기 전에 얼마 전 사내에서 Getter와 Setter를 함부로 사용하면 안되는 이유에 대한 세미나가 있었다. Setter에 대한 이야기는 워낙 많이 알려져있었지만 Getter에 대한 이야기는 잘 하지 않
colabear754.tistory.com
getter 쓰지 말라고만 하고 가버리면 어떡해요
설명 좀 해주고 가요
velog.io
Entity에서 Setter 사용 지양, 그렇다면 DTO에서는?
Controller에서 API 개발을 할 때 Entity를 바로 접근하지 말고 DTO를 사용해야 한다.Entity에서는 Setter를 사용하는 것을 지양해야 하는데, 그렇다면 DTO에서 @Setter를 사용해도 되는 것인지 궁금해졌다.htt
velog.io
https://limdingdong.tistory.com/15
[객체지향 생활체조 원칙] 규칙 9. getter/setter/property를 쓰지 않는다
getter/setter/property를 쓰지 않는다 도메인 오브젝트로 설계한 Entity 또는 VO 클래스에는 getter/setter/property 사용을 지양해 상태노출을 최소화 하라는 지침이다. 숨은 의미 객체지향 프로그래밍의 핵
limdingdong.tistory.com
댓글