SOLID 원칙 - SRP (단일 책임 원칙)

한 클래스는 하나의 책임만 가져야 한다.
클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다.
클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다.

로버트 마틴은 책임을 변경하려는 이유로 정의하고, 어떤 클래스나 모듈은 변경하려는 단 하나 이유만을 가져야 한다고 결론 짓는다.
예를 들어서 보고서를 편집하고 출력하는 모듈을 생각해 보자. 이 모듈은 두 가지 이유로 변경될 수 있다.
첫 번째로 보고서의 내용 때문에 변경될 수 있다. 두 번째로 보고서의 형식 때문에 변경될 수 있다.
이 두 가지 변경은 하나는 실질적이고 다른 하나는 꾸미기 위한 매우 다른 원인에 기인한다.
단일 책임 원칙에 의하면 이 문제의 두 측면이 실제로 분리된 두 책임 때문이며, 따라서 분리된 클래스나 모듈로 나누어야 한다.
다른 시기에 다른 이유로 변경되어야 하는 두 가지를 묶는 것은 나쁜 설계일 수 있다.

알아보기

회원정보가 있다. 회원 정보에는 name,email 을 가진다. name 은 문자열이고, email 은 Email 형식을 가져야 한다.

img.png

  • Account
import java.security.InvalidParameterException;

public class Account {
  private final String name;
  private final String email;

  public Account(String name, String email) {
    validEmail(email);
    this.name = name;
    this.email = email;
  }

  private void validEmail(String email) {
    String ePattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$";
    java.util.regex.Pattern p = java.util.regex.Pattern.compile(ePattern);
    java.util.regex.Matcher m = p.matcher(email);
    System.out.println(m.matches());
    if(!m.matches()) {
      throw new InvalidParameterException("이메일 형식이 아닙니다.");
    }
  }

}

Account 는 회원정보를 담는 객체이다. 하지만, validEmail() 의 이메일 검증 행동이 추가 되었다.
그렇다면 Account 객체가 이메일 검증 행동과 관계가 있는가?
Account 정보는 email 이라는 문자열(String) 타입을 가지고있다. String 은 문자열 일뿐 Email 포맷을 증명하지 못한다.
그래서 그것을 증명하기 위해 validEmail() 메소드가 추가 되었다. Account 는 스스로 email 포맷까지 증명하고 있다는 것이다.
생각해보면 emailvalidEmail() 은 관계성을 가진다. validEmail() 에 의해 통과 되어야 email 을 가질 수 있는것이다.
그렇다면 Email 이라는 타입을 정의하고 스스로 이메일을 검증한다면 단일 책임 원칙을 가지는게 아닐까?
그리고 Account 는 Email 객체를 가진다면, 단일 책임을 행할 수 있는게 아닐까?


다음은 Account 와 Email 사이의 책임을 분리시켜보았다.

img.png

  • Account
import java.security.InvalidParameterException;

public class Account {
  private final String name;
  private final Email email;
  
  public Account(String name, Email email) {
    this.name = name;
    this.email = email;
  }
  
  public String getName() {
   return name;
  }
  
  public Email getEmail() {
    return email;
  }
}
  • Email
import java.security.InvalidParameterException;

public class Email {
  private final String email;
  
  public Email(String email) {
    validEmail(email);
    this.email = email;
  }
  
  public void validEmail(String email) {
    String ePattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$";
    java.util.regex.Pattern p = java.util.regex.Pattern.compile(ePattern);
    java.util.regex.Matcher m = p.matcher(email);
    System.out.println(m.matches());
    if(!m.matches()) {
      throw new InvalidParameterException("이메일 형식이 아닙니다.");
    }
  }
}

이제 Account 는 Email 객체를 가지게 되어 이메일 검증을 통과된 Email 타입 객체를 가질 수 있게 되었다.
추후 Email 검증 로직이 변경 되더라도, Account 는 이메일 검증로직 변화에 좀 더 자유로워 질 수 있다.
하지만, Account 는 스스로 완전한 상태가 아닌 Email 과의 관계성이 추가되었다.


정리

처음에는 혼란스러웠다. 단일 책임이라… Account 와 Email 을 굳이 분리를 해야하나…라는 생각이었다.
확실히 분리 하고보니 코드의 안정감이 생겼다. 이게 맞다 틀리다라고 생각하기 보단 객체 지향 관점에서 사고 할려고 노력하는게 맞는것 같다.
무조건 저렇게 해야되 라기 보단 어떻게 보면 방법론이니까 그것을 지향하는 쪽으로 사고 훈련을 해야 될 것 같다.

참고링크

SOLID 객체지향 설계 위키백과