자바 공부를 하면서 계산기 프로그램을 만들어본적이 없었다.
이번에 부트캠프에서 과제로 주어져서 만들어보고 있는데,
객체지향 설계 능력, 코드 구현 능력이 굉장히 부족하다는걸 많이 느꼈다.
계산기 프로그램을 만들면서 맞닥뜨린 몇가지 문제점들을 적어보고
어떻게 해결했고 무엇을 배웠는지 적어보려 한다.
계산기 프로그램을 만들 때
요구사항이나, 코드를 어떻게 설계하고 구현하느냐에 따라서 난이도가 천차만별이 된다.
까다로운 요구사항 몇가지(실제 계산기처럼 연산 한번으로 기능이 끝나지 않고 연산된 값이 유지되면서 새로 입력하는 값으로 연산할 수 있는 기능)를 추가한다면 난이도가 많이 상승하게 된다.
기능들을 if문, switch문, for문, while문을 덕지 덕지 붙여나가다 보면 문제를 해결할 수는 있을 것이다.
하지만, 중요한건 객체지향 적으로 계산기 프로그램을 만들어보는 것이다.
객체지향적으로 설계를 어떻게 해야할지 감이오지 않아서 해결 방안을 참고했다.
포함관계를 사용해서 계산기를 만들 수 있다.
포함관계라는 개념은 has - a 관계라는거는 알고 있었는데,
코드로 어떻게 구현해야할지 감이 잡히지 않았다.
강의 다시 복습해보고, 구글링도 해봤는데 감이 오지 않았다.
해결 답안을 확인해봤는데 몇가지 의문점이 들었다.
왜 포함관계를 사용했는가?
포함관계를 사용하게 되면 main 메서드에서 인스턴스화를 한번만 선언해줘도 된다는 장점이 있다.
예를 들어서,
AddOperator라는 더하기 기능을 가지는 클래스와 SubstarctOperator라는 빼기 기능을 가지는 클래스가 있다고 가정해보자
main 메서드에서 아래와 같이 선언할 수 있다.
AddOperator add = new AddOperator();
SubstarctOperator sub = new SubstarctOperator();
add.operate();
sub.operate();
위처럼 인스턴스화를 두번을 해야 더하기 빼기 기능을 사용할 수 있다.
하지만 Calculator 클래스에 포함관계로 설정하게 되면 한번의 인스턴스화만 할 수 있다.
아래는 예시 코드이다.
Calculator calculator = new Calculator(new AddOperation(), new DivideOperation(), new MultiplyOperation(), new SubstractOperation());
포함관계로 설정할 때 왜 final로 설정했는가?
이건 아주 간단 명료한 이유인데, 계산기 더하기 뺄셈 나누기 곱하기 기능이 변할 일이 없으므로 final로 불변 상태로 선언해준 것이다.
아래는 예시 코드이다.
private final AddOperation addOperation;
private final DivideOperation divideOperation;
private final MultiplyOperation multiplyOperation;
private final SubstractOperation substractOperation;
public Calculator(AddOperation addOperation, DivideOperation divideOperation, MultiplyOperation multiplyOperation, SubstractOperation substractOperation){
this.addOperation = addOperation;
this.divideOperation = divideOperation;
this.multiplyOperation = multiplyOperation;
this.substractOperation = substractOperation;
}
처음에 생성자에서 모든 사칙연산 클래스들을 초기화 시켜주는 이유에 대해 이해하지 못했었다.
그래서 필요한 사칙연산만 가져다가 쓸 수 있을 것이라는 생각에 아래와 같이 생성자를 각 사칙연산 클래스별로 단독으로 선언해줬다.
public Calculator(AddOperation addOperation){
this.addOperation = addOperation;
}
public Calculator(DivideOperation divideOperation){
this.divideOperation = divideOperation;
}
public Calculator(MultiplyOperation multiplyOperation){
this.multiplyOperation = multiplyOperation;
}
public Calculator(SubstractOperation substractOperation){
this.substractOperation = substractOperation;
}
일단 위 코드처럼 선언하면 두가지 문제가 있다.
첫째. final 키워드를 사용하여 사칙연산 클래스를 포함할 수 없다.
그 이유는 final 필드는 처음 초기화할 때 값을 반드시 입력해주거나 생성자로 초기화 시켜줘야 하는데,
단독으로 생성자를 초기화 시킬 시에 사칙연산 모두가 초기화되지 않으므로 final 변수가 초기화되지 않았다는 컴파일 에러가 발생한다.
둘째. 포함관계의 장점이 사라진다.
final 키워드를 사용하지 않고 어찌저찌 위처럼 단독으로 생성자를 초기화 시키고 main 메서드에서 사용한다 가정해보자.
더하기와 나누기 연산을 하기 위해 아래와 같이 초기화해줘야 한다.
Calculator calculator = new Calculator(new AddOperation());
Calculator calculator2 = new Calculator(new DivideOperation());
위 코드와 아래 코드는 다를게없다.
AddOperator add = new AddOperator();
SubstarctOperator sub = new SubstarctOperator();
포함관계를 사용하는 이유는 한번의 인스턴스화로 사칙연산 기능을 모두 사용하는 것인데, 그러한 장점이 사라져버린다.
왜 abstract를 사용해야할까?
계산기에서는 '계산'이라는 공통된 행동을 하는 메서드가 존재한다.
위에서는 operate() 메서드가 그 예시이다.
이 공통된 메서드를 추상화한 클래스를 생성하면
첫째. 객체간의 결합도가 낮아진다.
둘째. 다형성을 만족시킨다.
셋째. 유지보수하기 좋다.
abstract를 사용하면 코드가 아래와 같이 아름다워(?)진다
private AbstractCalculator operation;
public Calculator(AbstractCalculator operation){
this.operation = operation;
}
public void setOperation(AbstractCalculator operation){
this.operation = operation;
}
public int calculate(int a, int b){
int answer = 0;
answer = operation.operate(a, b);
return answer;
}
Calculator 클래스에서는 사칙연산 클래스와 결합도가 완전히 사라지고,
operation.operate() 메서드만 호출하여 값을 반환해줄 수 있다.
그저 main 메서드에서 어떤 사칙연산을 사용할지 초기화만 시켜주면 된다.
아래는 그 예시 코드이다.
Calculator calculator = new Calculator(new AddOperation());
System.out.println(calculator.calculate(10,20));
calculator.setOperation(new MultiplyOperation());
System.out.println(calculator.calculate(10,20));
setOperation() 메서드가 사용되는 이유는 새로운 사칙연산을 갈아끼우기 위해서다.
객체지향 설계로 리팩토링 했을 때 코드가 아름다워진다.
if-else 또는 switch문을 덕지덕지 사용한다거나,
포함관계를 사용해서 각 클래스간의 결합도(의존성)가 높아졌을 때 코드는 원시적이라고 볼 수 있다.
하지만 abstract를 사용하여 다형성을 만족시켰을 떄 딱봐도 아름다운 코드가 완성된다.
아직은 감이 안온다. 중요한건 계속해서 객체지향적으로 설계하려고 노력하자
객체지향적으로 설계한다는 것이 어떤 것인지 이제 감이 좀 잡힐 것 같다.
중요한건 결합도를 낮추고 의존성을 높이는것!