클래스가 다른 클래스를 참조하거나, 다른 클래스의 인스턴스를 생성하거나, 의존성을 주입 받는다거나 할 때 관계를 맺는다고 한다고 한다.
관계를 맺은 부분은 이니셜라이저 부분인데,
Calculator
인스턴스를 만들 때 어떤 연산을 수행할지 지정하기 위해 Calculator
클래스를 초기화 했다.
그렇게 Calculator
가 특정한 Operation
객체를 받을 것이고,
그에 따라 덧셈, 뺄셈, 곱셈, 나눗셈 등 다양한 연산을 유연하게 처리할 수 있다.
의존성 주입
초기화 메서드를 통해 Calculator
가 operation
을 외부에서 주입할 수 있게 한다. 이로 인해 Calculator
클래스는 특정 연산 클래스에 종속되지 않고, 초기화 시점에 원하는 연산 클래스를 전달하여 동적으로 사용할 수 있다.
유연한 사용
init
을 통해 연산 종류를 설정하면, 동일한 Calculator
구조를 유지하면서도 다양한 연산을 수행할 수 있다. 덧셈 연산을 원하면 AddOperation
을, 나눗셈 연산을 원하면 DivOperation
을 인자로 전달해 사용할 수 있다.
강제 설정
init
에서 operation
을 설정하게 함으로써, Calculator
가 반드시 연산 객체를 가지고 초기화되도록 보장한다. 이를 통해 Calculator
객체가 생성될 때 operation
이 nil
인 상태를 방지하고, 초기 설정이 되어 있는 상태에서만 사용될 수 있다.
내부코드 변경 전)
내부코드 변경 후)
레벨 1,2랑 비교하면 클래스가 달라진 부분을 볼 수 있는데,
전과 달리 Calculator
가지고 있던 책임이 분산되어 연산을 혼자서 책임지지 않고,
각 클래스가 단일책임을 지고 있다는 걸 알 수 있다.
calculator
메서드는 반환 타입에 ?가 붙어 있기 때문에 이 메서드는 nil을 반환할 수 있다.
이 부분은 사용자가 더하기나 빼기 이 외에 다른 $
같은 문구를 작성했을 시, error
코드를 반환하기 위해서 옵셔널로 지정했다.
operation?.operate(firstNum: firstNum, secondNum: secondNum)
부분에서
operation
이 nil
이 아닌 경우에만 operate
메서드를 호출하여 연산을 수행하고,
operation
이 nil
이면, 연산을 수행하지 않고 메서드 자체가 nil
을 반환 하게 된다.
예를 들자면 operation
이 AddOperation
으로 설정되어 있고,
calculator(firstNum: 3, secondNum: 4)
를 호출하면 AddOperation
객체의 operate
메서드가 호출되어 3 + 4
의 결과인 7
이 반환 될 것이다.
반대로, operation
이 nil
일 경우 calculator
메서드는 nil
을 반환하게 된다.
모든 연산을 Calculator 클래스 안의 관리하기 때문에 코드가 한 곳에 모여 있어 좀 더 간단했고,
switch 문을 사용해서 보다 직관적이게 연산작업을 확인할 수 있었다.
단일 책임 원칙을 준수 하여 각 연산이 독립적인 클래스로 분리되어 있다.
각 클래스가 특정 연산에만 집중하므로 코드의 책임이 명확하게 분리된다.
이로서 단일 책임 원칙이라는 개념에 대해 이해할 수 있었고,
이 덕분에 각 연산 클래스가 독립적이니 개별적인 연산 로직을 수정하기 편해졌다.
코드가 여러 클래스로 나뉘어 있어 좀 더 복잡한 감이 있었지만, 좀 더 안정적이게 변했다.
사실 레벨 4 까지는 아예 도전하지 않으려했는데,
열심히 도와주신 진홍님 덕에 추상화에도 한 번 도전해볼 수 있었다.... !!
나는 swift의 protocol
을 이용하여 추상화하였다. ( 이 말을 이렇게 쓰는게 맞는지 모르겠지만)
Operation
프로토콜 덕분에 Calculator
클래스는 연산 클래스가 무엇인지 신경 쓰지 않아도 되고, 하나의 객체에 여러 가지 타입을 대입하는 동작에도 일관성이 유지가 가능하다.
Calculator
에서 operate
가 어떻게 작동하는지 알 필요 없이 사용하는 것도 추상화이다.
예를 들어 제곱근이나 제곱을 추가할 경우, 프로토콜로 전달만 해주면 되고, 그 Operation
을 따르는 클래스를 만들 수 있기 때문이다.
그렇게 다 만들고 결과값을 안 올렸다.
결과값은 result
라는 변수와 sonCalculator
라는 변수를 만들었는데, Calculator
클래스 안에 operation을 통해서 연산자를 변경하는 변수를 만든 것이다.
그리고 위에서 값이 nil
일 경우 error
메세지를 뜨게 하기 위해서 if let
을 사용해 error
메세지 처리를 했는데,
다시 좀 수정할 때는 error
를 print
로 출력하는 대신 예외 상황을 더 명확하게 처리할 수 있는 클래스를 따로 만드는 게 좋을 것 같다는 피드백 또한 받았다.
연산들을 독립적인 클래스로 구현할 수 있는 부분을 진행한 후,
프로토콜을 도입하는 덕분에 새로운 연산을 추가하게 될 때마다 기존 코드에는 영향을 주지 않고
손쉽게 확장할 수 있다는 부분이 배우면서 알듯 말듯 처음에는 이해가 되지 않고 정말 어려웠다.
그리고 각 연산을 각 클래스에 구현했으니 단일 책임 원칙도 지킬 수 있었는데,
혹시라도 나중에 수정이 필요할 때 해당 클래스만 신경 쓰면 되니 편리할 것 같았다.
이렇게 단일 책임 원칙을 따르면 코드는 좀 길어져도 관리가 더 수월해진다고 한다.
이 과정에서 객체 지향 프로그래밍에 대해 좀 더 공부해야겠다는 생각이 들었다.
튜터님에게 들은 바로 프로토콜과 객체지향은 관계가 밀접하니 공부하면 좋다고 당부하셨기 때문이다.
뭔가 이번 프로젝트를 통해 단순히 기능을 구현하는 것에 그치지 않고,
구조적이고 유지 보수하기 쉬운 코드를 작성하는 것이 얼마나 중요하고 또 어려운 일인지 깨달았다.
하면서 스스로 많이 부족하다는 것도 알게 된 과제였다. 남은 이번 주 동안은 이렇게 만든 계산기를 다시 갈아엎고 처음부터 만들어볼 예정이다.
추가로 제가 공부해보니 같은 operate라는 이름의 메서드가 객체마다 서로 다른 동작을 수행하는 걸 다형성이라 하고,
Calculator에서 operate가 어떻게 작동하는지 알 필요 없이 사용하는 것도 추상화입니다