어제 리팩토링을 하고, 자기 전에 고민을 했다.
과연 내 계산기에서 인터페이스나 추상 클래스를 사용할 곳이 한 군데도 없을까? 진짜로?
사용할 수 있을 것 같은데?
해서, 인터페이스와 추상 클래스의 개념을 간단하게 다시 잡고, 방향성을 정했다.
추상 클래스는 "공통적인 로직" 을 기반으로 하고, 인터페이스는 "확장성" 에 초점을 맞춘다.
특징 | 추상 클래스 | 인터페이스 |
목적 | 코드 재사용, 공통 기능 제공 | 설계 강제, 다양한 구현체 허용 |
공통 로직 포함 여부 | 가능 (메서드 구현, 필드 포함) | 불가능 (Java 8부터 default 메서드 허용) |
다중 상속 | 불가능 | 가능 |
유연성 | 제한적 (계층 구조를 강제) | 매우 유연 (다양한 구현 가능) |
이보다 더 간단히 비유를 해 보자면,
인터페이스는 "레고 블록은 이렇게 생겨야 한다" 라는 규정만 하고, 조립 방법은 각자 알아서 구현한다.
추상 클래스는 레고 박스처럼 기본적인 틀과 조립된 블록을 제공한다. 조립 방법은 정해져 있고, 틀 안에서만 수정이 가능하다.
내 계산기에서 "확장성"이 필요한 것은 History의 검색 방식들이고, "공통적인 로직" 을 기반으로 한 것은 Operation이다.
HistorySearch Interface 를 구현한다면, 지금처럼 id만 검색 가능한 것이 아닌 특정 값보다 큰 결과값 불러오기, 특정 연산자를 활용한 계산만 불러오기 등의 기능을 쉽게 추가할 수 있다.
Operation Abstract Class를 구현한다면, 기존처럼 a와 b값을 입력받아서 연산하는 것은 동일하지만, 다양한 연산을 쉽게 추가할 수 있다. 일전에 추상 클래스들을 삭제하고 enum을 도입했는데, enum과 추상 클래스를 같이 쓸 생각을 이제야 하다니..
우선 인터페이스 먼저 손대는 걸로 방향을 잡고 interface 에 대해 조금 더 공부해보다가, Predicate 라는 것에 대해 알게되었다.
Predicate는 인수를 받아서 boolean 값을 반환하는 함수형 인터페이스다. stream의 filter는 인수로 predicate를 받는다.
@Test
public void whenFilterListWithCombinedPredicatesUsingAnd_thenSuccess(){
Predicate<String> predicate1 = str -> str.startsWith("A");
Predicate<String> predicate2 = str -> str.length() < 5;
List<String> result = names.stream()
.filter(predicate1.and(predicate2))
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Adam"));
}
그러니 이런 형태로 predicate를 통해 여러 조건을 선언하고 filter로 필요한 조건만 걸러낼 수 있다.
사용자 입력을 받아 처리하기도 쉬운 구조로 되어있다.
https://www.baeldung.com/java-predicate-chain
이제 내 코드에 적용해보자.
HistorySearch interface는 search 기능을 구현하도록 "블럭의 생김새" 즉 "메서드의 생김새" 를 정해준다.
구현체에서는, 역시나 Predicate<History>의 조건을 매개변수로 받고 그 조건에 따라 filtering 한 값을 return 한다.
구현체에게 매개변수를 보내주는 클래스다. Map을 사용해봤다.
그리고 코드 길이가 너무 길어지는 것 같아서 class를 나눠줬다.
이정도면 직관..적인가..?
배보다 배꼽이 더 커졌다 ㅋㅋ
사용자가 "h"를 입력하면 HistoryInteraction의 showhistory가 호출된다.
그곳에서 HistoryInput의 getUserInput을 통해 사용자의 입력값을 받고(id, r, o),
searchHandler에서 입력값에 따른 검색기를 호출한다
(id면 같은id 검색, r이면 입력값보다 큰 결과만 검색, o면 입력한 연산자를 사용한 기록 검색).
HistoryDataSearchManager가 알맞은 검색 조건을 골라주고,
HistorySearch를 상속받은 구현체 HistoryData에게 그 조건을 보내서 검색을 수행한다.
마지막으로 검색기록이 HistoryOutput을 통해 제공되고, 계산기로 돌아갈 것인지 검색을 더 할것인지를 입력받는다.
근데 어쩌다보니 HistorySearch를 상속받은 자식이 한명 뿐인데.. 이거 옳은 방법 맞나? 싶었지만,
확장성과 유연성을 고려하면 괜찮은 것 같다.
오늘 interface를 마무리 했으니, 내일은 operation을 추상 클래스로 바꿔봐야겠다~~
겸사겸사 제곱근이나 제곱 계산도 추가해야겠다 ㅎㅎ
'개인 공부용 프로젝트 > calculator' 카테고리의 다른 글
트러블슈팅 : 검색 결과..아아... <9> (0) | 2024.11.18 |
---|---|
<finish> 가 아니라 리팩토링.. (0) | 2024.11.17 |
트러블슈팅: 왜 저장이 두번 되니? <7> (8) | 2024.11.15 |