백엔드 웹개발 (Java/Spring) 초격차 - 3
- 인터페이스를 생성해준다.
supports
메서드로 operator 를 받아 boolean
값을 반환하고
calculate
메서드로 operand 를 받아 int
값을 반환한다.
NewArithmeticOperator
를 상속받은 클래스가 나머지를 구현한다.
operator가 "+" 와 같다면
두 operand를 +해서 반환한다는 의미.
SubtractionOperator
MultiplicationOperator
DivisionOperator
도 구현해주자.
- 각각의 구현체들을 상위 인터페이스인
NewArithmeticOperator
을 통해 받는다.
- enum대신
.filter
오퍼레이터에 해당하는 실제 구현체를 필터링.
.map
선택받은 오퍼레이터를 calculate할 때 operand 둘을 전달.
.findFirst
.orElseThrow
아니면 예외 반환
- 인터페이스를 하나 주고 인터페이스를 구현한 네개의 구현체를 받음.
들어온 오퍼레이터에 맞는 구현체를 찾고,
구현체에게 calculate 작업 위임. 인자로 들어온 opernad 1, 2, 전달.
해당하는 값이 int
니까 int
로 받기 위한 map.
.findFirst
로 첫 값을 받지만
연산자에 해당하는 구현체가 없다면 .orElseThrow
로 예외 반환.
- 0으로 나누는 경우 예외를 발생시키는지 테스트 코드를 작성하자.
테스트는 실패할텐데, DivisionOperator
클래스에 해당 상황에 대한 부분이 없기 때문이다. 작성해주자.
DivisionOperator
클래스에 operand2 가 0인 상황에 대한 코드를 작성한다.
IllegalArgumentException
으로 예외를 발생시킨다.
- 테스트 코드도 그에 맞춰 수정해 줄 수 있다.
- 그렇다면 operand가 양수라는 조건은 어디서 만들어줘야할까?
만약 각 클래스나 인터페이스에서 operand가 양수라는 조건이 만족하는지 테스트해주어야 한다면, operand가 있는 곳마다 validation을 체크해주어야 한다. 굉장히 비효율적
- 따라서 value 오브젝트를 만드는 편이 더 좋다.
if(isNegativeNumber(value))
value가 음수라면,
throw new IllegalArgumentException
argumnet 예외를 반환하도록 한다.
- 해당 값을 전달했을 때 value가 음수면 예외가 발생하고
양수면 객체가 만들어진다.
객체지향
- 에러가 발생했을 때, 혹은 어떠한 기능을 추가하고 싶을 때 응집도가 높다면, 해당 기능이 구현되어 있는 곳에 가서 문제를 해결할 수 있을 것이다. 응집도가 낮다면 어디에서 문제가 발생했는지를 찾는 데에만도 꽤나 시간이 필요할 것.
- 결합도를 낮추기 위해 외부 클래스를 작성해서 확장하는 방법을 사용하는 것이 더 좋다.
- 메소드를 호출한다 = 메시지를 주고 받는다.
학점 계산기 만들기
- 도메인을 구성하는 객체에는 어떤 것들이 있는지 고민해보자.
- 학점계산기 도메인 : 이수한 과목, 학점 계산기
- 이 단계에서 모든 객체를 다 도출할 수는 없다.
생각나는 데까지만 하자.
- 객체들 간의 관계 고민
- 학점 계산기는 이수한 과목을 갖고 학점을 계산하지 않을까?
학점 계산기가 이수한 과목을 인스턴스 변수로 갖으면서 평균학점을 계산할 수 있어야겠다.
- 동적인 객체를 정적인 타입으로 추상화, 도메인 모델링 하기
- 이수한 과목 예시를 상상해보자.
객체지향 프로그래밍, 자료구조, 중국어 회화 등..
객체는 세가지이다.
- 동적인 세가지 객체를 정적인 타입으로 추상화.
과목(코스) 클래스로 표현할 수 있다.
- 협력 설계
- 작은 객체부터 만들어본다.
- 학점 계산기를 만들기 위해서는 이수한 과목이 있어야 한다.
- 정보를 받을 수 있게 클래스를 생성해준다.
- 다음은 이수한 과목 정보를 이용할 수 있는 평균학점 계산기 테스트를 작성해준다.
- 다음은
calculateGrade
구현부 작성
학점수X교과목 평점 을 해주어야하니 for문을 사용한다.
(학점수 * 교과목 평점)의 합계
인 multipliedCreditAndCourseGrade
변수를 선언, for 문 안에 넣어준다.
- course의
credit
값과 문자(A+, A, ..)인 grade
를 숫자로 변환한 값인 gradeToNumber
을 곱해준다.
getCredit
과 getGradeToNumber
구현해준다.
- 수강신청 총학점 수인
totalCompletedCredit
를 구현해주고,
둘을 나누어주면 학점 계산기를 만들 수 있다.
- 테스트 코드도 잘 작동된다.
- 이제 리팩토링을 통한 개선을 해보자.
- 문제점을 생각해본다.
- course가 직접 자신의 학점과 성적을 갖고 있지만, 학점X성적 정보를 해당 값을 가진 course가 계산하는 것이 아닌, 이들의 정보를 getter를 통해 가져와 gradeCalculator가 계산하고 있다.
- 만약 학점 계산, 예를 들어
학점X교과목 평점의 합계
부분이 여러 군데에서 사용하게 된다면, 한 곳에서 수정할 때 모든 곳에서 수정해주어야한다는 문제가 있다.
→ 응집도가 낮다.
- 하지만 이것을 course에서 수행해준다면,
게터를 이용하는 것이 아닌, 메서드를 호출해서 해당 객체에게 작업을 위임해준다면 수정 소요가 발생했을 때 해당 부분만 수정해주면 된다.
→ 응집도가 높기 때문에 변화가 발생했을 때 한 군데만 수정해주면 된다.
- 게터를 통해 정보를 갖고와서 처리하는 것이 아닌, 해당 데이터를 가진 객체에게 메시지를 던져 작업을 처리해주게 된다면 변화에 유연한 코드를 작성할 수 있을 것이다.
일급 콜렉션 사용
GradeCalculator
에 있던 정보를 모두 Courses
의 multiplyCreditAndCourseGrade
로 이동시켰다.
- 마찬가지로
calculateTotalCompletedCredit
를 만들어 이동시켰다.
- 리팩토링 된
GradeCalculator
- 1급 콜렉션이란
리스트 형태로 된 Course 정보만 인스턴스 변수로 갖는 클래스.
변수는 오직 하나여야한다.
- 여러개의 Course 정보를 갖는 클래스
- Course를 갖고 처리할 수 있는 책임들이 해당 1급 컬렉션 밑으로 이동하게 된다. 어떠한 수정이 발생했을 때
해당 메서드만 수정하면 되겠다.
하는 판단이 서게 됨.
- 객체들끼리 메시지의 전달을 통해, 자신이 모두 처리하는 것이 아닌 해당 값을 가진 객체에게 메시지를 통해 작업을 위임 결과값을 가져오고 이를 통해 최종적인 결과를 얻어내는 객체지향적인 프로그램을 설계하자.
음식 주문