캡슐화 하지 않으면
ex) 정회원을 판별하는 로직
if(acc.getMembership() == REGULAR && acc.getExpDate().isAfter(now())){
// 정회원 기능
}
근데 이벤트로 5년이상 사용자한테 한달동안 정회원 혜택을 줘야함
if(acc.getMembership() == REGULAR &&
(
(acc.getExpDate().isAfter(fiveYearAgo) && acc.getExpDate().isAfter(now())) ||
(acc.getExpDate().isBefore(fiveYearAgo) && addMonth(acc.getExpDate()).isAfter(now()))
)){
// 정회원 기능
}
그럼 이와같이 변경할수있는데, 저렇게 되어있는곳을 다 바꿔야된다.
그렇기 떄문에 우리는 캡슐화를 시도한다. 캡슐화를 하면
캡슐화를 하면
if(acc.hasRegularPermission()){
// 정회원 기능
}
public class Account {
private Membership membership;
private Date expDate;
public boolean hasRegularPermission(){
return membership == REGULAR && expDate.isAfter(now())
}
}
정회원 판별하는 로직을 hasRegularPermission안으로 감췄기때문에 if구문에 메소드를 호출하는코드는 저 안에 뭐가 들어있는지 알수가없고 알 필요도 없다. 또한 로직 변경이 일어나도 hasRegualrPermission만 고치면된다
또한 hasRegularPermission 이라는 이름으로 아 이 메소드는 정회원판별을 해주는 메소드구나 의도를 짐작할수 있다.
-> 기능에 대한 의도 이해를 높임
TELL, DON'T ASK
판단을 해달라고 메시지를 보낸다
Demeter's Law
메서드에서 생성한 객체의 메서드만 호출
파라미터로 받은 객체의 메서드만 호출
필드로 참조하는 객체의 메서드만 호출
-> 메서드 하나만 호출
public AuthResult authenticate(String id, String pw){
Member member = findOne(id);
if(member==null) return AuthResult.No_MATCH;
//1. 캡슐화 전 -> 데이터를 가져와서 자기가 판단
if(member.getVerificationEmailStatus() !=2){
return AuthResult.No_EMAIL_VERIFIED;
}
//2. 캡슐화 후 -> 판단을 isEmailVerfied()메소드에 맡김
if(member.isEmailVerified()){
return AuthResult.No_EMAIL_VERIFIED;
}
if(passwordEncoder.isPasswordValid(mem.getPassword(), pw, mem.getId())){
return AuthResult.SUCCESS;
}
return AuthResult.No_MATCH;
}
규칙1번 관점에서 보면 주석1번은 member에서 데이터를 가져와가지고 자기가 판단을 하는 코드다, 하지만 규칙1번은 이런 판단 자체를 해달라고 메시지를 보내자는 거기 때문에 이부분을 멤버가 제공하는 기능으로 바꿔서 2번 주석처럼 만들수 있고 의도도 더 확실하게 할 수 있다.
public class Rental {
private Movie movie;
private int daysRented;
public int getFrequentRenterPoints(){
//1. 데이터 가져와서 자기가 판단
if(movie.getPriceCode() == Movie.NEW_RELEASE &&
daysRented > 1)
return 2;
else
return 1;
}
}
//2. 데이터 판단 메시지
public int getFrequentRenterPoints(){
if(movie.isNewRelease() &&
daysRented > 1)
return 2;
else
return 1;
}
//4. 전체 계산 기능 불러오기
public int getFrequentRenterPoints(){
movie.getFrequentRenterPoints(daysRented);
}
public class Movie {
public static int REGULAR = 0;
public static int NEW_RELEASE = 1;
private int priceCode;
public int getPriceCode(){
return priceCode;
}
//3. 전체계산기능 추가
public int getFrequentRenterPoints(int daysRented){
if(priceCode == NEW_RELEASE &&
daysRented > 1)
return 2;
else
return 1;
}
규칙 1번 관점에 따라서 주석1번을 주석2번으로 바꿨지만 그런데 1번주석이랑 2번주석이랑 별 차이가 없다 그래서 좀더 적극적으로 RenterPoint구하는 전체 계산을 캡슐화 해보자 새로운 주석 3번 메소드는 계산하는데 필요한 값을 파라미터로 넘겨줬기때문에 추후에 daysRented의 값이 바뀌어도 movie쪽 코드만 수정하면 된다.
Timer t = new Timer();
t.startTime = System.currentTimeMillis();
//...
t.stopTime = System.currentTimeMillis();
long elapsedTime = t.stopTime - t.startTime;
public class Timer {
public long startTime;
public long stopTime;
}
현재는 타이머의 데이터를 직접 사용하고있다.
이 부분을 캡슐화 하자
Timer t = new Timer();
t.start()
//...
t.stop();
long time = t.elapsedTime(MILLISECOND);
public class Timer {
private long startTime;
private long stopTime;
public void start(){
startTime = System.currentTimeMillis();
}
public void stop(){
stopTime = System.currentTimeMillis();
}
public long elapsedTime(TimeUnit unit){
switch(unit){
case MILLISECOND:
return stopTime - startTime;
//...
}
}
}
Millis 지만 추후 정밀한 시간계산이 필요해서 Nano로 바뀌게 된다면? start와 stop 부분을 nano초로 바꾸고 elapsedTime의 case 구문을 추가해주면 된다
이 예시도 역시 캡슐화 성공후에는 기능을 사용하는 코드는 바뀌지 않는다.
public void verifyEmail(String token){
Member mem = findByToken(token);
if(mem == null) throw new BadTokenException();
if(mem.getVerificationEmailStatus() == 2){
throw new AlreadyVerifiedException();
} else {
mem.setVerificationEmailStatus(2);
}
// 수정 사항 DB 반영
}
이 코드는 조건을 판단한다음 판단한 결과로 다시 그 데이터를 바꾼다 이런 패턴의 코드는 통으로 캡슐화하면 좋다
public void verifyEmail(String token){
Member mem = findByToken(token);
if(mem == null) throw new BadTokenException();
mem.verifyEmail();
// 수정 사항 DB 반영
}
public class Member {
private int verificationEmailStatus;
public void verifyEmail(){
if(isEmailVerified()){
throw new AlreadyVerifiedException();
} else {
verificationEmailStatus = 2;
}
}
public boolean isEmailVerified(){
return verificationEmailStatus == 2;
}
}