나는 계산기 과제에 강의숙제로 제출했던 코드를 재사용했다. 이유는 기초적인 틀이 마련되어 있기 때문이다. 사실 말이 재사용이지 전부 갈아엎긴 할 거다.
현재 코드는 그리 많은 기능을 담고 있지 않는데도 클래스가 많이 나뉘어져있어서 로직이 복잡하고 가독성이 떨어진다.
그래서 이 계산기에 새로 배운 기술들을 도입해서, 줄일 수 있는 코드를 최대한 간결하게 줄여서 재사용성을 높이고 추가 기능을 개발하는 것으로 노선을 잡았다.
본격적으로 구현하기 전에, 내 계산기에 어울리는 기술을 찾아야한다. 우선 현재 계산기의 로직은 이러하다.
있어보이려고 영어로 적었다. 하하하하
현재 구현되어 있는 것들 중 어떤 클래스가 새로운 기술로 구현될 수 있을까, 또한 어떤 추가 기능을 개발할 수 있을까를 고민해보았다.
우선, 강의를 듣고도 명확하게 와닿지 않았던 enum과 함수형 인터페이스, 람다, stream에 대한 자세한 내용부터 구글링을 통해 알아보았다.
스레드도 실습해보고싶긴 하지만 계산기에 과연 필요할까? 싶어서 일단은 접어두었다.
https://techblog.woowahan.com/2527/
배민 개발자님의 java enum 활용 경험이 담긴 글이고, enum을 완벽하게 이해할 수 있었다.
if, switch case를 사용해야 하는 경우가 생긴다면, enum을 쓰는 편이 좋겠다고 생각했다.
계산기에 enum을 도입한다면 기존의 추상 클래스로 구현했던 연산자 클래스들을 하나로 합칠 수 있을 것이다.
parser에서 잘못된 operation이 입력되면 걸러내는 과정도, enum에 등록되지 않은 연산자를 입력할 시 Exception을 발생시킬 수 있다.
또한 새로운 연산 기능을 도입할 때도 enum을 활용해 쉽게 업데이트 할 수 있을 것 같았다.
enum에 대해 찾아보다가 또 한가지 흥미로운 활용법을 알게 되었는데, enum과 함수형 인터페이스를 함께 사용하면 코드가 더욱 간결해지고 확장성이 높아진다고 한다.
1. 아무것도 사용하지 않는다면:
public static double add(double a, double b){
return a + b;
}
System.out.println("더하기: " + add(a, b));
각 연산을 메서드로 구현하기에 단순하고 직관적이지만 새로운 연산이 필요할 때마다 새로운 메서드를 작성해야 한다.
연산자에 대해 parser.java처럼 유효성 검사가 필요하다.
2. enum 자체에 추상 메서드를 정의해서 각 상수에서 구현한다면:
public enum Operation{
Add{
@Override
public double apply(double a, double b){
return a + b;
}
},
SubTRACT{
@Override
public double apply(double a, double b){
return a - b;
.
.
.
public abstract double apply(double a, double b);
}
System.out.println("더하기: " + Operation.ADD.apply(a,b));
이 또한 apply메서드를 직접 구현하기에 새로운 연산을 추가할 때 마다 새로운 메서드 구현이 필요하다.
하지만, 연산자에 대한 유효성 검사는 enum에서 처리할 수 있다.
3. enum과 함수형 인터페이스 BiFunction을 함께 사용한다면:
public enum Operation{
ADD((a, b) -> (a + b),
SUBTRACT((a, b) -> (a - b),
MULTIPLY((a, b) -> (a * b)
private final BiFunction<Double, Double, Double> operation;
Operation(BiFunction<Double, Double, Double> operation){
this.operation = operation;
}
public double apply(double a, double b){
return operation.apply(a, b);
}
}
System.out.println("더하기: " + Operation.ADD.apply(a,b));
람다식까지 사용하니 코드가 굉장히 간결하다!!! 새로운 연산을 추가할 때도 훨씬 쉽게 구현이 가능하다.
그럼 이제 연산자를 어떻게 처리할지 감을 잡았으니 구현할 차례다.
Operation.enum을 만들어주고, 추상 클래스들과 Parser.java를 모두 삭제했다.
import java.util.function.BiFunction;
public enum Operation {
ADD("+", (a, b) -> a + b),
SUBTRACT("-", (a, b) -> a - b),
MULTIPLY("*", (a, b) -> a * b),
DIVIDE("/", (a, b) -> a / b);
//멤버 변수 선언
private final String symbol;
private final BiFunction<Double, Double, Double> operator;
//생성자
Operation(String symbol, BiFunction<Double, Double, Double> operator){
this.symbol = symbol;
this.operator = operator;
}
public double apply(double a, double b){
return operator.apply(a, b);
}
public static Operation fromSymbol(String symbol){
for(Operation operation : Operation.values()){
if(operation.symbol.equals(symbol)){
return operation;
}
}
throw new IllegalArgumentException(symbol + "은 사용할 수 없는 연산자입니다.");
}
}
apply메서드와 fromSymbol메서드를 선언해줬다. freeSymbol 메서드는 enum전체와 직접적으로 관련된 작업을 수행하기 때문에 static으로 정의했다.
사용자가 입력한 값들을 변수 a, b, symbol에 저장한다. 입력값은 Double 타입, 출력값도 Double 타입이다.
Operation.values()는 enum의 모든 상수를 배열로 반환한다. 반복문을 통해 각 상수가 입력받은 symbol값과 일치하는지 확인하고,
일치하는 enum상수에 따라 다른 Bifunction을 사용해 enum에서 연산을 직접 처리하고 결과를 반환한다.
만약, 일치하는 symbol이 하나도 없다면 Exception을 throw 한다.
이를 통해 추상 메서드와, 추상 클래스들과, parser클래스의 역할을 한 개의 enum으로 축약시킬 수 있었다!
이제 다른 부분들을 enum에 맞게 구현해야 한다. 일을 크게 벌린 느낌이지만 화이팅해보자..
'개인 프로젝트 > calculator-project' 카테고리의 다른 글
Parser.java의 중요성을 깨달음 <4> (0) | 2024.11.15 |
---|---|
트러블슈팅: Reducing a Stream <3> (0) | 2024.11.15 |
Stream 공부하다 뇌에서 Steam 나옴😇 <2> (0) | 2024.11.14 |