최근 클린코드에 관심이 늘면서 클린코드와 관련된 공부를 진행하던 도중 객체지향방식에서 클린 코드를 적용하기에 가장 유명하면서 가장 효울적인 방식이 객체 지향 생활 체조 원칙이라고 생각하였다.
그러하여 객체 지향 생활 체조 원칙에 대해서 클린코드와 객체지향에 익숙하지 않은 사람도 쉽게 볼 수 있도록 블로그를 작성해보고 싶어 해당 블로그를 작성합니다
그럼 객체 지향 생활 체조 원칙에 대해서 알아보겠습니다.
public void setting(){
private String name;
if (name != null) {
if (name == "123"){ // 이 방식 X
throw new RuntimeException();
}
}
if (name != null){ // 이 방식 O
nameIs123(name);
}
}
이러한 리팩토링은 상위 클래스에 비대한 요구사항과 책임이 담기는 것을 막게 하고, 추가적인 요구사항에 대응하기가 편리해진다.
if(user.getMoney().getValue() > 100_000L) {
throw new IllegalArgumentException("소지금은 100_000원을 초과할 수 없습니다.");
}
디미터 법칙에서는 객체 그래프를 따라 멀리 떨어진 객체에게 메시지를 보내는 설계를 피해라고 한다. 이런 설계는 거의 모든 객체간 결합도가 생기게 되고 캡슐화가 깨지게 된다.
if(user.hasMoney(100_000L)) {
throw new IllegalArgumentException("소지금은 100_000원을 초과할 수 없습니다.");
}
아래 일급 컬렉션에 대한 정리를 해놓은 링크가 있으니 관심 있으면 꼭 보기를 추천한다!
일급 컬렉션 정리 링크
public Order {
private ShippingInfo shippingInfo;
public void changeshippingInfo(ShippingInfo newShippingInfo) {
verifyNotYetShipped();
setShippingInfo(newShippingInfo);
}
private void setShippingInfo(ShippingInfo newShippingInfo) {
this.shippingInfo = newShippingInfo;
}
}
도메인 모델의 엔티티나 벨류에 공개 set 메서드만 넣지 않아도 일관성이 깨질 가능성이 줄어든다.
공개 set 메서드를 사용하지 않으면 의미가 드러나는 메서드를 사용해서 구현할 가능성이 높아진다.
예를 들어 set 형식의 이름을 갖는 공개 메서드를 사용하지 않으면
자연스럽게 cancel이나 changePassword처럼 의미가 잘 드러나는 이름을 사용하는 빈도가 높아진다.
Q. 나는 그냥 단순 조회가 목적인데 그럼 어떻게 해야하는가??
A. 다른 형태의 자료구조를 통해서 변환하여서 반환해보자.
ex) list형태의 자료구조가 있다.
반드시 list형태로 반환해줘야하는가??
-> 아니다.
list에 포함된 메서드들이 너무 투머치해서 문제를 일으킬 수 있다.
그럼 조회라는 목적에 알맞는 자료구조는 뭐가 있을까? ->
hasNext(),next() 딱 2개 뿐인 iterator를 통해서 반환해보자.
이런 상황에 알맞은 자료구조가 자바에 있으니~
결국 객체의 내부가 조작되지 않도록 만드는 것이 중요한데,
이와 관련해서 자바에서는 불변 컬렉션을 제공한다!!
ImmutableCollections에서 제공하는
ImmutableList
ImmutableSet
ImmutableMap의 내부 구조를 보면 엉청난 특징이 숨겨져 있는데,
static abstract class AbstractImmutableList<E> extends AbstractImmutableCollection<E>
implements List<E>, RandomAccess {
// all mutating methods throw UnsupportedOperationException
@Override public void add(int index, E element) { throw uoe(); }
@Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
@Override public E remove(int index) { throw uoe(); }
@Override public void replaceAll(UnaryOperator<E> operator) { throw uoe(); }
@Override public E set(int index, E element) { throw uoe(); }
@Override public void sort(Comparator<? super E> c) { throw uoe();
}
...
자료구조의 내부를 수정하는 어떤 메서드를 호출하면 수정작업을 하지 않고 무조건 예외가 발생하게 구현이 되어있다!!
Collections.unmodifiableList(리스트) 를 통해서 불변 자료 구조를 반환 할 수 있다는 것이다.
이렇게 자료구조를 복사해 왔다고 해도, 자료구조 내부의 값들이 원본의 메모리 주소를 향하고 있으면 복사한 값에 변경이 일어났을때 원본도 함께 변경된다는 것이다.
그러기 위해서는 원본과의 연결 끊어야한다.
원본인 값인 객체 하나하나 까지 새로운 객체로 만들어 준 뒤, 리스트에 담는다면
원본과는 상관없는 새로운 조회용 값을 새로 받을 수 있을 것이다.
public List<Name> getStudentNames() {
return studentNames.stream()
.map(name -> new Name(name.getName()))
.collect(Collectors.toUnmodifiableList());
}
이렇게 제공 할 수가 있단 거다!
new Name()으로 새로 만든 객체이기 때문에 원본과 다른 메모리 값을 가지게 된다.
new생성자를 통해서 만드는 것이 귀찮다면,
복사하려는 객체가 Cloneable 인터페이스를 구현하게 만들어,
객체.clone() 메서드를 호출해 간단하게 깊은 복사를 하는 방법도 있습니다.
객체한테 일을 시킨다는 마인드로 코드를 작성해야한다!
지금까지 정리한 객체지향생활체조 원칙을 코드에 적용한다면 기존 코드에 비해 생산성과 유지보수성, 보안성등을 모두 향상 시킬 수 있을 것이다.
전부를 한 번에 적용해 보는 것은 어려울 수도 있으니 한두가지씩 나눠서 적용해보는 것을 추천해봅니다!
참조 링크
정보에 감사드립니다.