앞서 객체와 절차 지향, 객체 지향을 보고 오셨다면 객체를 통해서 객체 지향을 활용한 첫 번째, 캡슐화에 대해 설명하겠습니다.
캡슐화
- 데이터 + 관련 기능 묶기
- 객체가 기능을 어떻게 구현했는지 외부에 감추는 것
- 구현에 사용된 데이터의 상세 내용은 외부에 감춤
- 실제 구현에 사용된 데이터의 타입, 값을 어떻게 사용하는지 구현과 관련된 상세 내용을 감춘다.
캡슐화하지않은 예시
// 초창기 서비스
// REGULAR 상태이며 만료일이 지나지 않았다면
if(acc.getMembership() == REGULAR && acc.getExpDate().isAfter(now())) {
.. 정회원 기능
}
// 서비스 런칭 이후 (5년 이상 장수했음)
// 5년 이상 사용자 일부 기능 정회원 혜택 1개월 무상 제공
if(acc.getMembership() == REGULAR && (
(acc.getServiceDate().isAfter(fiveYearAgo) && acc.getExpDate().isAfter(now())) ||
(acc.getServiceDate().isBefore(fiveYearAgo) && addMonth(acc.getExpDate()).isAfter(now()))
)) {
.. 정회원 기능
}
- 요구사항의 변화가 데이터 구조/사용에 변화를 발생시켰다
(초창기 : 멤버십 상태 + 만료일자 → 멤버십 + 만료 일자 + 회원가입 날짜)로 요구사항이 추가 된다.- 절차 지향의 데이터를 공유하는 방식의 문제점 → 요구사항 변화에 따라 데이터의 사용이 변화한다. → 수정해야 하는 코드가 연쇄적으로 발생
- 초창기 Account는 오직 멤버십의 상태 그리고 만료일만 갖고 판단하였다.
- 요구 사항 변경 예시
- 장기 사용자에게 특정 기능 실행 권한을 연장(단 유효 일자는 그대로 유지)
- 계정을 차단하면 모든 실행 권한 없음
- 자바 7의 Date를 자바 8의 LocalDateTime으로 변경
유저의 회원가입 날짜(getServiceDate)를 사용해야 하는 요구사항의 변화가 데이터를 사용하는 코드의 구조에 변경을 발생시켰다.
이는 다시 말해 요구사항의 변화가 데이터 구조/사용에 변화를 발생시켰고 수정을 위해서는 기존 데이터를 사용하는 코드를 위와 같이 전부 수정해야 한다
캡슐화를 했다면
- 캡슐화란? 데이터 + 기능
- 기능은 그대로 제공하고 구현 상세를 감춘다.
아래 초창기 코드와 요구사항에 따라 변경된 코드를 외부/ 내부에서 볼 때에 맞춰 작성했다. 다음과 같이 확인해 보자.
초창기 코드
<초창기 코드>
// 외부에서 볼 때의 모습
if(acc.hasRegularPermission()) {..정회원 기능 }
// 내부에서 볼 때
// 필드에 맴버 변수들의 데이터와 hasRegularPermission() 기능 제공
public class Account{
private Membership membership; // Data
private Date expDate; // Data
public boolean hasRegularPermission() { // 기능
return membership == REGULAR && expDate.isAfter(now());
}
}
위 코드는 초창기 유저 상태를 "멤버십 상태 + 만료일자" 정보를 사용하여 확인하는 코드이다.
기존 절차지향적인 코드와는 다르게 Account 객체 내부에서 hasRegularPermission 메서드를 사용했고 메소드 내부(메서드의 블랙박스)에서 "멤버십 상태 + 만료일자" 요구사항을 정의했고 그 결괏값을 리턴한다.
요구사항이 변경된 코드
<요구사항이 적용된 코드>
// 외부에서 볼 때의 모습(변경 없음)
if(acc.hasRegularPermission()) {..정회원 기능 }
// 내부에서 볼 때
// Account 클래스의 내부 기능 구현만 변경되고 그 기능을 사용하는 다른 코드는 변경하지 않는다.(위와 같이)
public boolean hasRegularPermission() {
return membership == REGULAR &&
(expDate.isAfter(now()) ||
(
serviceDate.isBefore(fiveYearAgo()) &&
addMonth(expDate).isAfter(now())
)
);
}
놀랍게도 5년이 지나 요구사항의 변경이 생겼지만 외부에서 보는 acc.hasRegularPermission()은 변경되지 않았다.
오직 Account 클래스의 hasRegularPermission 메서드 내부의 요구사항만이 변경되었다.
위와 같이 캡슐화는 연쇄적인 변경 전파를 최소화할 수 있다.
캡슐화와 기능
- 캡슐화 시도 → 기능에 대한 (의도) 이해를 높일 수 있다.
if(acc.getMembership() == REGULAR) { } // 이 사람이 왜 멤버십이 REGULAR와 같은지 검사하는 실제 이유는 뭘까
-> 캡슐화 이후
if(acc.hasRegularPermission()) {..} // Account(계정)클래스에서 계정이 REGULAR 권한을 가졌는지 확인하기 위함
public class Account {
public boolean hasRegularPermission() {..}
}
캡슐화를 위한 규칙
- Tell, Don’t Ask
- 데이터를 요청하지 말고 객체 내부에서 처리해서 리턴 받기
- 사용자가 유효한지 검사하는 조건이 여러 서비스에 구현되어 있다면, 해당 조건이 변경될 때마다 사용자를 검사하는 모든 서비스의 코드를 수정해야 할 것이다. 따라서 이런 조건은 객체 내부 메서드에서 직접 작성한다.
if(acc.getMembership() == REGULAR) {..정회원 기능} // 데이터를 가져와서 판단하는 것이 아니라
-> if(acc.hasRegularPermission()) {..정회원 기능} // 객체에서 기능으로 지원하기
- Demeter’s Law
- 메서드에서 생성한 객체의 메서드 하나만 호출
- 파라미터로 받은 객체의 메서드 하나만 호출
- 필드로 참조하는 객체의 메서드 하나만 호출
acc.getExpDate().isAfter(now()); Account에서 연속적으로 부르는 방식(X) -> acc.isExpired();
Date date = acc.getExpDate(); Account에서 연속적으로 부르는 방식(X) -> acc.isValid(now());
date.isAfter(now()); Good!
정리
- 기능명세를 외부에 감춘다. (메서드의 블랙박스)
- 캡슐화를 잘할수록 연쇄적인 변경 전파가 줄어들어 수정 비용이 낮아진다.
- 캡슐화를 통해 기능을 사용하는 코드에 영향을 주지 않고 (또는 최소화) 내부 구현을 변경할 수 있는 유연함
캡슐화 예제
예제 1번
다음 중 캡슐화를 통해 리팩토링이 가능한 라인은?
public AuthResult authenticate(String id, String pw) {
Member mem = findOne(id);
if(mem == null) return AuthResult.NO_MATCH; // 매칭 되지 않음
if(mem.getVerificationEmailStatus() != 2) { // 상태코드가 2일 경우 이메일 인증 되지 않음
return AuthResult.NO_EMAIL_VERIFIED;
}
if(passwordEncoder.isPasswordValid(mem.getPassword(), pw, mem.getId())) {
return AuthResult.SUCCESS; // 성공
}
return AuthResult.NO_MATCH; // 매칭되지 않음
}
if(mem.getVerificationEmailStatus()!= 2) {
return AuthResult.NO_EMAIL_VERIFIED;
}
정답 : 위 코드는 TDA (Tell, Don’t Ask)의 적용을 통해 리팩토링을 할 수 있는 것으로 보인다. 데이터를 요청받고 요청받은 Status가 ≠ 2인지 판단하는 행위는 추후 조건(status의 상태코드가 3으로 변경 등)의 변경이 발생할 경우 모든 조건을 변경해야 할 수도 있기 때문에 판단 자체를 메서드 내부의 기능으로 구현하여 요청받는 객체 내부에서 요청과 동시에 기능으로 처리한다.
'OOP' 카테고리의 다른 글
1장) 객체, 그리고 객체 지향 (0) | 2023.05.05 |
---|---|
intro) 왜 객체지향을 배워야 할까? (0) | 2023.05.05 |