solid 원칙 준수하여 코드 짜보기 회고 및 주절주절
기술매니저님께서 solid 5원칙를 준수하는 코딩을 해보라고 과제를 내주셨다.
과제는 7조 인원들끼리 각자 만들어보고 서로 코드리뷰를 하는 방식으로 진행됐다.
사용자에게 입력을 받아 간단한 계산을 하는 계산기 기능을 만들었는데, 계산은 첫번째 숫자입력, 연산기호, 두번째 숫자입력 순으로 받아
연산해주는 간단한 기능을 구현하였다. 사실 간단하게 만드려면 간단하게 만들 수 있는 쉬운 예제지만 코드 리팩토링 경험과 solid 5원칙을 지키는지 스스로 검토해보는 경험을 시켜주시려고 내주신 과제같다.
우선 solid 5원칙의 단일책임원칙에 의해 클래스의 기능들을 최대한 나눠주었다.
일단 계산기가 실행되면 입력을 받아야하므로 사용자에게 입력받는 클래스를 따로 만들어주었다.
import java.util.Scanner;
public class ScanText {
static int ScanNum1;
static int ScanNum2;
static String scanSymbol;
public void scan () {
Scanner scan = new Scanner(System.in);
System.out.print("첫번째 숫자 입력 : ");
ScanNum1 = scan.nextInt();
scan.nextLine();
System.out.print("연산기호 입력 : ");
scanSymbol = scan.nextLine();
System.out.print("두번째 숫자 입력 : ");
ScanNum2 = scan.nextInt();
}
}
입력받은 각각의 값을 전역변수에 담았다. 입력된 값은 다른 클래스에서 인스턴스화 없이 사용이 가능해진다.
계산기의 덧셈, 뺄셈, 곱셈, 나눗셈을 operation 이라는 인터페이스로 관리하였다.
public interface Operation {
double operate (int firstNum, int secondNum);
}
public class AddOperation implements Operation {
@Override
public double operate(int firstNum, int secondNum) {
return firstNum + secondNum;
}
}
public class SubstractOperation implements Operation {
@Override
public double operate(int firstNum, int secondNum) {
return firstNum - secondNum;
}
}
public class MultiplyOperation implements Operation {
@Override
public double operate(int firstNum, int secondNum) {
return firstNum * secondNum;
}
}
public class DivideOperation implements Operation {
@Override
public double operate(int firstNum, int secondNum) {
return (double) firstNum / secondNum;
}
}
이는 하나의 클래스는 하나의 목적을 갖는 단일책임원칙을 준수하며 동시에 하위클래스가 상위 클래스를 대체할 수 있는 리스코프 치환원칙을 준수한다. 또한 해당 인터페이스는 operate라는 연산기능을 수행하는 메서드만 존재하기 때문에 인터페이스 분리원칙도 준수한다고 볼 수 있을 것이다.
import static calculator.Calculator.setOperation;
public abstract class OperationType {
static void operationTypeBySymbol() {
switch (ScanText.scanSymbol) {
case "+" :
setOperation(new AddOperation());
break;
case "-" :
setOperation(new SubstractOperation());
break;
case "*" :
setOperation(new MultiplyOperation());
break;
case "/" :
setOperation(new DivideOperation());
break;
}
}
}
scanText 클래스에서 사용자에게서 입력받은 값중의 scanSymbol을 연산기호들과 대조하여 각 연산 기능을 가진 operation 참조값을 setOperation에 할당했다.
계산기의 연산기능을 수행하는 Calculator 클래스는 위의 각 연산기능들이 있는 하나의 인터페이스로 구현된 Operation 클래스들에 의존하여 기능을 수행한다.
public class Calculator {
private static Operation operation;
public static void setOperation(Operation operation) {
Calculator.operation = operation;
}
public static int calculate(int firstNum, int secondNum) {
if (operation != null) {
return (int)operation.operate(firstNum, secondNum);
} else {
System.out.println("잘못된 입력입니다.");
throw new IllegalStateException("연산기호가 잘못 입력되었습니다.");
}
}
}
OperationType 클래스에서 할당된 operation 클래스 연산 기능을 수행한다.
만약 연산기호를 제대로 입력하지 않아 operation 참조값이 들어오지 않았다면 잘못된 입력을 알리고 오류에 주석을 붙여줬다.
import static calculator.Calculator.*;
import static calculator.OperationType.operationTypeBySymbol;
public class Main {
public static void main(String[] args) {
ScanText scanText = new ScanText();
System.out.println("=== 간단 계산기 ===");
scanText.scan();
operationTypeBySymbol();
System.out.println("계산 결과 : " + calculate(ScanText.ScanNum1, ScanText.ScanNum2));
}
}
코드 실행부분 순서는 입력값을 받고 -> 연산기호를 확인하여 -> 맞는 연산기능을 수행 -> 계산 결과를 출력했다.
조원들끼리 코드리뷰했을때 받은 코멘트로는 Calculator 클래스에 setOperation 메서드가 있어서 파트별로 개발자가 나뉘어 작업을 한다고 했을때 직관적이지 않을 수도 있다는 의견을 받았다. 각 기능별로 클래스를 나뉘었을 때는 더 직관적이게 설계를 했으면 좋겠다고 하셨다.
그렇겠구나라는 생각이 들며.. 아직 몰랐던부분이라 감사히 피드백을 받아들였다.
solid원칙이 뭔지 처음 접했을때는 읽어보면 뭔가 객체지향 프로그래밍을 하려면 유지보수를 위해 당연한 거라 느껴졌는데, 실제로 그것들을 염두하면서 리팩토링해보려니 어렵게 느껴지는 부분들도 있었다. 사실 제대로 과제를 수행한지도 모르겠고, 나중에 훨씬 더 공부를 하고 다시 읽어보다보면 어렸을때 낙서마냥 형편없을거다.
내일부터 시작할 Spring을 배우다보면 더 나아지겠지 라는 생각이다.