백엔드 웹개발 (Java/Spring) 초격차 - 2
객체 지향 패러다임
객체 지향의 4가지 특징
- 객체 지향이 무엇인가? 에 대한 정답은 없을지라도, 객체지향이 무엇인가? 에 대한 자신의 답은 갖고 있어야한다.
- 추상화
- 불필요한 부분을 제거함으로써 필요한 핵심적인 부분을 나타내는 것
- 복잡성을 낮추기 위해 사용
- 다형성
- 다양한 형태를 갖는 것.
- 하나의 타입으로 여러 객체를 참조할 수 있다.
- 캡슐화
- 객체 내부의 세부사항을 외부로부터 감추는 것.
- 인터페이스만 공개함으로써 변경하기 쉬운 코드를 만드는 것.
- 상속
객체 지향의 5가지 설계 원칙(SOLID)
- SRP : 단일 책임 원칙, Single Resptonsibility Principle
- 하나의 코드는 하나의 기능과 권한, 책임을 가져야 함.
- OCP : 개방 폐쇄 원칙, Open/Closed Principle
- 확장에는 열려있고 변경에는 닫혀 있어야 한다.
- 기존 코드를 변경하지 않고 기능을 추가할 수 있어야한다.
- LSP : 리스코프 치환 원칙, Lisov`s Subsitution Principle
- 상위 타입의 객체를 하위 타입의 객체로 치환해도 동작에 문제가 없어야한다.
- ISP : 인터페이스 분리 원칙, Interface Segregation Principle
- 많은 기능을 갖은 인터페이스를 작은 단위로 분리시킴으로 클라이언트에게 필요한 인터페이스만 구현한다.
- 클라이언트가 사용하지 않는 기능에 의존하게되면 예상하지 못한 문제가 발생할 수 있는데, 이를 예방할 수 있음.
- DIP : 의존성 역전의 원칙, Dependency Inversion Principle
- 의존 관계를 맺을 때 자주 변경되는 쪽보다는 변경되지 않는 쪽에 의존하라.
- 자기보다 변하기 쉬운 것에 의존하면 변화의 영향을 많이 받기에, 추상화된 인터페이스나 상위 클래스에 의존해야 한다.
객체지향 패러다임
- 적절한 객체에게 적절한 책임을 할당, 서로 메시지를 주고 받으며 협력하도록 하는 것
- 점점 증가하는 SW 복잡도를 낮추기 위해 객체지향 패러다임이 대두.
- 중요 포인트 두가지
- 클래스가 아닌 객체에 초점을 두자.
- 객체들에게 얼마나 적절한 역할과 책임을 할당하는가?
절차지향 vs 객체지향
- 책임이 한 곳에 집중돼 있는 방식(gtter) > 절차지향
- 하나의 메서드에서 게터를 통해 값을 가져오고, 이것에 모든 처리가 집중된다면 절차지향.
- 책임이 여러 객체로 적절히 분산 > 객체지향
- 해당 책임을 가진 객체에게 메시지를 통해 협력하도록 구현하는 방식.
High cohesion, Loose coupling
- 높은 응집도, 낮은 결합도.
- 비슷한 것끼리는 모아둠. > 응집도를 높인다.
다른 것끼리는 분리함.
- 응집도가 높으면, 어떠한 변경이 생겼을 때 변경의 포인트가 하나로 집중될 수 있음. 한곳에 집중되어 있기 때문에 영향범위를 파악하는 것이 굉장히 쉽다.
- 객체지향에 있어 변경 요구사항이 들어왔을 때, 특정 부분만 수정해도 된다는 것은 응집도가 높다는 것을 의미한다.
어떠한 변경이 생겼을 때 변경이 다른 곳에 영향을 미치지 않는다면 그것은 낮은 결합도를 의미한다.
- 객체지향 설계는 결국 유지보수와 관련된 중요한 개념.
변경점이 생겼을 때 유연하게 대응할 수 있다.
설계 순서
- (설계에 앞서) 도메인을 구성하는 객체에는 어떤 것들이 있는지 고민
- 객체들 간의 관계 고민
- 동적인 객체를 정적인 타입으로 추상화, 도메인 모델링 하기
- 객체들이 어떤 상태와 행동을 갖는지 결정이 되면, 공통적인 상태와 행동을 갖는 객체들을 타입으로 분류. 타입을 기반으로 클래스를 구현한다.
클래스 : 공통적인 상태와 행동을 가지는 객체.
- 복잡성을 낮출 수 있다.
- 협력 설계
- 객체들을 포괄하는 타입에 적절한 책임 할당
- 클래스에 적절한 책임 할당. 클라이어언트와 협력할 수 있는 public 인터페이스를 정의.
- 구현
- 참고 : 객체지향 세계에서는 모든 객체가 능동적인 존재여야한다.
사칙연산 계산기 만들기
- 요구사항
- 간단한 사칙연산
- 양수로만 계산
- 나눗셈에서 0으로 나눌 경우 IllegalArgument 예외 발생
- MVC 패턴 (Model-View-Controller) 기반으로 구현
test code
피연산자 + 피연산자
를 Calculator에게 전달하며 작업 위임.
그 후 Calculator는 이에 대한 결과값을 전달해주는 형태로 구현하자.
- Calculator에게 해당 규약을 갖는 public 인터페이스를 만들어준다.
- 클래스와 메서드 생성
- 퍼블릭 인터페이스를 생성했다.
리턴값은 int
이니 int로 설정
- 일단 테스트 코드를 작성하자. 두 개의 피연산자가 필요하고, 하나의 연산자가 필요하며, 답이 반환된다.
리턴값은 int.
- 하지만 당연히 실패한다. 테스트 코드가 실패했다면, 성공시켜보자.
- 인터페이스를 수정해준다. 이것으로 일단 테스트코드는 성공시킬 수 있다.
- 테스트 코드가 성공했으니 심리적 안정감을 얻을 수 있고, 안심하고 리팩토링 할 수 있다.
+
가 operator
인 경우에 해당하는 코드를 짜넣었다. 테스트코드가 성공하였으니, 리팩토링이 잘 되었다고 생각할 수 있다.
-
인 경우의 테스트코드. 하지만 -
연산에 관한 부분이 없기 때문에 당연히 실패한다.
- 뺄셈에 대한 코드를 짜넣었고 테스트코드도 성공한다.
- 이런 사칙연산에 대한 테스트코드를 일일이 작성하지 않고 한번에 작성하는 방법은 없을까?
@ParameterizedTest
로 할 수 있다.
- 메서드 인자를 설정할 수 있다.
int operand1, String operator, int operand2, int result
메서드 인자들이 순환하며 메서드에 들어가 결과를 확인한다.
formulaAndResult
은 static 이어야 한다.
- 작성한
Calculator
클래스가 마음에 들지 않으니 리팩토링 해주자.
- enum을 새로 만들어준다.
- enum 마다 각각의 연산을 수행하고 싶다! > 추상메서드를 선언해주자.
- 추상메서드에 대한 오버라이드 메서드를 구현체로 선언해줄 수 있다.
Calculator
는 굳이 자신이 계산을 모두 수행하는 것이 아닌, 인자를 ArithmeticOperator
에 전달하고 작업을 위임하고 int
값을 리턴받게 할 수 있다.
calculate
메서드를 생성해준다.
- 추상메서드는 enum 각각에서 오버라이드를 함.
calculate
가 외부에 노출되는 퍼블릭 인터페이스
- Calculator는 작업 요청을 받으면 ArithmeticOperator에 작업을 위임.
ArithmeticOperator에서 연산자와 일치하는 enum 값을 가지고 오고 그 enum 값에 해당하는 메서드 중 하나를 실행함.
결과값을 int로 리턴해준다.
- 테스트 코드도 정상적으로 실행된다. 리팩토링이 잘 되었다는 것을 알 수 있다.
- 이 작업으로 얻는 효과.
1) 테스트코드를 성공시키면, 리팩토링할 때 안정감을 얻을 수 있다.
2) 테스트코드에서 calculate에게 작업 위임(메시지 전달).
객체지향답게 ArithmeticOperator라는 enum을 만들어 해당 enum에게 작업 위임.
그럼 최종적으로 enum이 작업을 처리하고 결과값을 다시 전달해줌.
- 객체들끼리 메시지를 주고받으며 협력해서 결과값을 전달하는 구조를 만들었다.