이전 포스트에서 캡슐화를 알아보았으니, 실제 코드를 통해 캡슐화를 연습해보겠습니다.
public AuthResult authenticate(String id, String pw){
Member mem = findOne(id);
if(mem == null) return AuthResult.NO_MATCH;
if(mem.getVerificationEmailStatus() != 2){
return AuthResult.NO_EMAIL_VERIFIED;
}
if(passwordEncoder.isPasswordValid(mem.getPassword(), pw, mem.getId())){
return AuthResult.SUCCESS;
}
return AuthResult.NO_MATCH;
}
위 코드의 어떤 부분을 어떻게 캡슐화 할 수 있을까요?
Tell, Don't Ask
를 생각하면 mem.getVerificationEmailStatus() != 2
와 같이 직접적으로 데이터를 가져와서 비교하는 코드보다 객체의 기능으로 캡슐화하는 것이 좋습니다.
그래서 변경을 하면 아래와 같이 변경할 수 있습니다.
public AuthResult authenticate(String id, String pw){
Member mem = findOne(id);
if(mem == null) return AuthResult.NO_MATCH;
if(!mem.isEmailVerified()){ // 변경된 부분
return AuthResult.NO_EMAIL_VERIFIED;
}
if(passwordEncoder.isPasswordValid(mem.getPassword(), pw, mem.getId())){
return AuthResult.SUCCESS;
}
return AuthResult.NO_MATCH;
}
public class Member {
private int verificationEmailStatus;
public boolean isEmailVerified(){
return verificationEmailStatus == 2;
}
}
위와 같이 변경하게 된다면, isEmailVerified()
의 내부 구현이 변경되어도 문제가 생기지 않는다.
public class Rental {
private Movie movie;
private int daysRented;
public int getFrequentRenterPoints(){
if(movie.getPriceCode() == Movie.NEW_RELEASE &&
daysRented > 1)
return 2;
else
return 1;
}
}
public class Movie {
public static int REGULAR = 0;
public static int NEW_RELEASE = 1;
private int priceCode;
public int getPriceCode(){
return priceCode;
}
}
위 코드를 어떻게 캡슐화할까요?
연습1에서 처럼, movie.getPriceCode() == Movie.NEW_RELEASE
를 movie.isNewRelease()
와 같이 캡슐화를 할 수도 있지만 딱히 크게 변하지 않는 것 같습니다.
이번에는, if-else 구문을 전부 캡슐화해보겠습니다.
public class Rental {
private Movie movie;
private int daysRented;
public int getFrequentRenterPoints(){
return movie.getFrequentRenterPoints(daysRented);
}
}
public class Movie {
public static int REGULAR = 0;
public static int NEW_RELEASE = 1;
private int priceCode;
public int getFrequentRenterPoints(int daysRented){
if(priceCode == NEW_RELEASE &&
daysRented > 1)
return 2;
else
return 1;
}
}
위와 같이 캡슐화하게 되면, 대여 포인트를 얻게 되는 코드의 로직이 변경되어도 getFrequentRenterPoints
를 사용하는 다른 클래스의 코드는 수정할 필요가 없어지고 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;
//...
}
}
}
위와 같이 캡슐화를 하게 되면 직접 데이터를 가져와 사용하는 것이 사라졌고, 메소드를 호출함으로써 얻으려는 데이터를 얻게 되었습니다.
그리고 위 캡슐화에서 elapsedTime
의 내부 구현으로 Nano초 단위로 데이터를 얻는 기능을 추가한다해도 long time = t.elapsedTime(MILLISECOND);
은 변경 될 필요가 없다는 것을 통해 캡슐화의 장점을 다시 한 번 알게 될 수 있습니다.
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;
}
}
위와 같이 캡슐화를 하게 되면, verifyEmail
이라는 기능은 Member에서 내부 구현을 변경하더라도 여러 다른 클래스에서 영향을 받지 않습니다.