이 글은 최범균님의 Inflearn 강의를 학습한 내용을 정리하였습니다.
하나의 기능은 여러 하위 기능으로 분해할 수 있다.
맛보기로 암호 변경 프로세스를 통해 확인해보자.
암호를 변경 기능은 아래 2개의 기능으로 분해할 수 있다.
그리고 변경 대상 조회 기능은 변경 대상 존재 여부에 따라 변경 대상 반환과 오류 반환 기능으로 나눠질수 있다.
대상 암호 변경 기능은 또 아래 2개의 기능으로 분해할 수 있다.
기능을 분리하고 제공할 주체를 결정하는 것은 객체 지향 설계의 기본 과정이다.
위에서 분해한 기능들을 제공할 객체를 정해자.
결과는 아래와 같다.
암호 변경 전체 기능은 ChangePasswordService
가 제공한다.
변경 대상을 조회하는 기능은 MemberRepository
가 제공한다.
대상 암호를 변경하는 기능은 Member
가 제공한다.
위 설계를 바탕으로 코드를 작성해보자.
public class ChangePasswordService { public Result changePassword(String id, String oldPw, String newPw) { Member mem = memberRepository.findOne(id); if (isEmpty(mem)) return Result.NO_MEMBER; try { mem.changePassword(oldPw, newPw); return Result.SUCCESS; } catch (BadPasswordException ex) { return Result.BAD_PASSWORD; } } }
위 코드를 보면 ChangePasswordService 객체에서 암호 변경 기능을 제공하고
암호 변경 기능을 제공하기 위해 MemberRepository, Member 객체와 협력하는 것을 볼 수 있다.
주의사항
클래스 또는 메소드가 커지면 절차 지향 방식의 문제가 발생할 수 있다.
원인은 아래와 같다.
- 큰 클래스인 경우 많은 필드를 많은 메소드가 공유하게 된다.
- 큰 메소드인 경우 많은 변수를 많은 코드가 공유하게 된다.
해결방법
적절한 시기에 책임에 따라 코드 분리를 해야한다.
클래스 또는 메소드가 너무 커지지 않도록 분리하는 몇 가지 방법을 알아보겠다.
알아볼 첫 번째 방법은 패턴 적용이다.
이 방법은 전형적인 역할 분리를 사용한다.
아래의 예를 보자.
간단한 웹
간단한 웹의 경우 컨트롤러, 서비스, DAO의 계층 분리 방식을 사용할 수 있다.
복잡한 도메인
도메인이 복잡하다면 모델을 Entity, Value, Repository, Domain Service로 분리하여 사용할 수 있다.
AOP
여러 기능에 공통으로 포함된 기능은 Aspect를 사용해서 분리할 수 있다.
GoF
디자인 패턴을 사용해서 여러 기능을 분리할 수 있다.
두 번째 방법은 계산 분리이다.
상품 결제중 포인트 계산하는 코드의 예를 통해 알아보자.
아래의 코드를 보면
왼쪽의 빨간색 코드가 포인트를 계산하는 로직이다.
이를 오른쪽 하단의 PointCalculator 객체로 분리하고
기존의 코드는 PointCalculator의 기능을 사용하도록 한다.
세 번째 방법은 네트워크, 메세징, 파일 등의 외부 연동 코드를 분리하는 것이다.
HTTP API를 분리하는 예를 통해 알아보자.
HTTP 연동 코드를 별도의 클래스로 분리하여 사용한다.
마지막 방법은 if-else를 추상화하는 것이다.
연속적인 if-else의 로직이 비슷하다면 공통 기능을 뽑아 추상화한다.
기능과 책임을 분리할 때는
의도가 잘 드러나는 이름을 사용해야한다.
기능과 책임을 분리했을 때 얻게되는 여러 장점 중 하나는 테스트가 용이하다는 것이다.
이해를 돕기위해 계산 분리의 예제를 가져왔다.
포인트 계산 로직이 의도한 대로 동작하는지 테스트를 수행하려한다.
위 두개의 코드에서 테스트 수행 과정을 알아보자.
왼쪽의 코드에서 포인트를 구하려면 memberRepository.findOne(id);
와 productRepository.findOne(id);
코드를 수행해야만 한다.
반면에, 오른쪽 코드처럼 포인트 로직을 분리한 경우는 PointCalculator만 있다면 계산 가능하다.
이어서 분리 연습을 해보자.
준비한 예제는 아래의 과정을 수행하는 코드이다.
위 코드는 기능과 책임 분리를 위해 계산 분리, 외부 연동 분리 방법을 적용할 수 있다.
이 예제는 계산 분리를 적용해보자.
적용한 코드는 아래와 같다.
분리한 계산 기능(암호화, 복호화)은 Cryptor 클래스가 담당한다.
그리고 기존 코드에서 Cryptor를 조립하고 제공하는 기능(암호화, 복호화)을 사용하면 된다.
분리 연습 2 번째 예제는 조건별 분기 추상화를 해본다.
예제 코드는 아래와 같다.
위 코드는 캡슐화 예제 때 봤었다.
캡슐화를 적용할 때는 Movie 클래스에 if-else를 옮기고 결과만 받아오도록 하였다.
기능과 책임 분리에서는 아래와 같이 바꿀수 있다.
포인트를 제공하는 추상 기능을 갖는 Movie 클래스를 만든다.
그리고 각 하위 클래스가 포인트 기능을 제공하도록 한다.
이번 예제는 회원 가입을 아래 과정을 통해 분리하는 것이다.
그리고 회원 가입 절차는 아래와 같다.
위 내용을 바탕으로 하위 기능으로 나눠보자.
결과는 아래와 같다.
토큰 생성
,토큰 저장
같은 경우는 토큰 관리라는 하위 기능으로 분리할 수 있을 것이다.
이 예제에서는 인증 메일 발송에 포함시킨다.
그리고 각 하위 기능을 제공할 객체를 할당한다.
결과는 아래와 같다.
할당한 객체의 배경색 의미는 아래와 같다.
기능과 책임을 알맞게 객체에 나누는 것은 객체 지향 설계의 기본 과정이다.
이 글에서는 큰 기능을 여러 하위 기능으로 나누는 방법 4가지에 대해 알아보았다.
만약 하나의 클래스 또는 메소드가 커진것 같다면(하나 이상의 책임 또는 기능을 갖는다면) 위 4가지 분리 방식을 적용해보도록 하자.