본문 바로가기
개발 관련/java

캡슐화(encapsulation)

by lazysnack 2022. 7. 14.

캡슐화(encapsulation)

  • 객체가 내부적으로 기능을 어떻게 구현하는지를 감추는 것
  • 내부의 기능 구현이 변경되더라도 그 기능을 사용하는 코드는 영향을 받지 않음
    • 내부 구현 변경의 유연함을 줌

1. 절차 지향 방식과의 비교

  • 회원의 서비스 만료 날짜 여부에 따른 처리를 하는 로직
1. 절차 지향 방식 코드
@Getter
public class Member {
    ...
    private Date expireDate;
    private boolean male;
}
if(member.getExpireDate() != null 
   && member.getExpireDate().getTime() < System.currentTimeMillis()) {
    // 만료 시 처리 로직
}
  • Member 의 expireDate 를 현재 시간과 비교해서 만료 여부를 체크한다. 그리고 이와 비슷한 로직은 여러 군데에서 쓰일 것 이다.

  • 살아있는 서비스는 시간이 지남에 따라 크기가 커질 것이고, 제약 사항 또한 생길 것이다. 그리고 다음과 같이 정책이 변경되었다고 하면...

    여성 회원인 경우 만료 기간이 지났어도 30일 간은 서비스를 이용 가능하게 정책 변경

  long days30 = 1000 * 60 * 60 * 24 * 30; // 30 days
  if((member.isMale() && member.getExpireDate() != null
      && member.getExireDate().getTime() < System.currentTimeMillis()) 
     || (!member.isMale() && member.getExpireDate() != null
      && member.getExireDate().getTime() < System.currentTimeMillis() -  days30)) {
      // 만료 시 처리 로직
  }
  • 비슷한 로직이 여러 군데에서 쓰일 테니, 일일히 변경해줘야 한다.
  • 게다가, 현실과 같이 정책이 수시로 변경된다면..? (ㅁㄴㅇㅁ)
  • 이 부분은 What 과 How 에 대해서도 접근해볼 수 있다고 생각되는데, 지나치게 How 중심적으로(혹은 데이터 중심적으로) 했기 때문에 이런 문제가 발생한 것이다. 그러면 이번엔 객체 지향 방식으로 변경해보자.
2. 객체 지향 방식 코드 (캡슐화 진행 코드)
  • 캡슐화는 기능을 내부적으로 어떻게 구현되었는지 숨기는
@Getter
public class Member {
    ...
    private Date expireDate;
    private boolean male;

    public boolean isExpired() { // 만료 여부를 Member 객체 내부에 캡슐화
        return expireDate != null 
            && expireDate.getTime() < System.currentTimeMills();
    }
}
if(member.isExpired()) {
    // 만료에 따른 처리
}
  • 다른 클래스에서는 isExpired() 가 어떻게 구현되었는지 알 수 없고, 알 필요도 없다. 그냥 완료되었는지 안 되었는지에 대한 결과만 알면 된다.

  • 절차 지향 때와 마찬가지로 정책이 변경된다면?

    @Getter
    public class Member {
        private static final long DAYS30 = 1000 * 60 * 60 * 24 * 30; // 30 days
        ...
        private Date expireDate;
        private boolean male;
    
        public boolean isExpired() { // 만료 여부 수정
            if(male) {
                return expireDate != null 
                    && expireDate.getTime() < System.currentTimeMills();
            }
            return expireDate != null 
                && expireDate.getTime() < System.currentTimeMills() - DAYS30;
        }
    }
    • 다음과 같이 isExpired() 를 수정해주면 된다.
    • 그리고 isExpired() 를 사용하는 곳은 수정해줄 필요가 없다. (메소드 내부가 변경되었지, 메소드 이름이 바뀌거나 파라미터가 생긴 것은 아니니까)

2. 캡슐화의 결과

  • 기능을 캡슐화하면 내부 구현이 변경되더라도, 기능을 사용하는 곳의 영향을 최소화할 수 있다.
  • 변경의 유연함을 얻게 되어, 쉽게 구현을 변경할 수 있게 된다.

3. 캡슐화를 위한 2개의 규칙

1. Tell, Don't Ask

  • 데이터를 물어보지 않고, 기능을 실행해 달라고 말하는 것
  • 절차 지향에서는 데이터를 직접 확인했었으나 (member.expireDate()), 캡슐화로 변경했을 시에는 기능 실행을 요청하면 된다. (member.isExpired())

2. 데미테르의 법칙 (Law of Demeter)

  • 메소드에서 생성한 객체의 메소드만 호출

  • 파라미터로 받은 객체의 메소드만 호출

  • 필드로 참조하는 객체의 메소드만 호출

    member.getExpireDate().getTime() 
    // getExpireDate()가 리턴한 Date 객체의 
    // getTime() 메소드를 호출했기에 데미테르의 법칙 위반
  • 데미테르의 법칙을 지키지 않는 전형적인 증상

    1. 연속된 get 메소드 호출

      value = object.getA().getB().getValue();
    2. 임시 변수의 get 호출이 많음

      A a = object.getA();
      B b = a.getB();
      value = b.getValue();
    • 두 가지 증상이 보인다면 데미테르의 법칙을 어기고 있을 가능성이 높고, 캡슐화를 약화시켜 코드의 변경을 어렵게 만드는 원인이 된다.
    • 따라서 위 두 증상이 보인다면 확인 후 적극적으로 캡슐화 하도록 노력하자.



  • 참고 : 개발자가 반드시 정복해야할 객체 지향과 디자인 패턴 - 최범균

'개발 관련 > java' 카테고리의 다른 글

자바 비동기 (2)  (0) 2022.07.14
자바 비동기 (1)  (0) 2022.07.14
Transactional 정리  (0) 2022.07.14
멀티쓰레드에서 Thread-Safe 방법  (0) 2022.07.14
orElse 와 orElseGet 무슨 차이가 있을까?  (0) 2022.07.14