오늘은 어제 회고록에 적힌 것 처럼,
1. 생성자 주입 사용 -> 내일 마무리 때 할 것
2. 싱글톤 또는 정적 팩토리 메서드 사용(필요하다면)
3. DTO 사용으로 Car 객체 캡슐화 지키기
4. 정규 표현식 캐싱하기(matches() 생성 비용 높음)
5. 재사용되어야 할 객체 파악해서 재사용하기
를 진행 했다. 이 중 5번은 재사용될만한 객체가 없다고 판단해서 진행할 수 없었다. 벌써 과제가 내일 하루 남았다니... 시간이 너무 빠르다. 하루종일 과제만 몰입해서 하는데도 지겹지가 않다.
정적 팩토리 메서드를 새로 학습했는데, 정적 팩토리 메서드는 해당 객체를 사용할 다른 객체에서 직접 객체를 생성하지 않아 의존성을 줄이고, 해당 객체의 캡슐화를 높이는 장점이 있어서 사용하고자 했다. 1, 2주차 과제 내내 제일 고민한게 패턴, SRP, 그리고 의존도였기 때문에, 매우 반가운 패턴이었다.
내가 사용한 곳은 같은 Validator
인터페이스를 구현하는 검증 부분이다. 이렇게 Validator
인터페이스의 구현체를 숨겨 서비스 클래스에 가지는 의존도를 제거해주고, 검증 메서드 내부를 숨겨 캡슐화와 정보 은닉성을 향상시켰다.
이 몇줄의 코드 추가만으로 검증 클래스들의 캡슐화가 높아지는게, 참 똑똑하고 멋진 방식이라고 생각했다.
싱글톤 패턴은 1주차에서 테스트 겸 하나의 검증 클래스에만 사용해봤는데, 이번에도 적용할지 고민이 됐다. 내가 적용하고자 생각했던 클래스는 상태를 가지지 않는 검증 클래스였는데, 생각을 더 해보니 싱글톤 패턴을 사용할 필요가 없겠다고 결론을 내렸다.
오히려 상태가 없기 때문에 여러 인스턴스들이 서로 영향을 주지 않아서 필요 없을 것 같았고, 싱글톤 패턴의 여러 단점을 가지고 가며 굳이 사용할 필요가 없겠다는 생각이었다.
어제 회고록의 정적 메서드에 관한 고민의 결론을 이용해서, 상태를 관리하지 않지만 공통으로 사용되지 않는 객체인 검증 객체들은 내부 메서드를 static
으로도 선언하지 않기로 했다.
싱글톤 사용에 맞게 서비스 클래스를 리팩토링 하다가, 좀 불편한 점을 발견했다.
| ⬆️ RacingService
클래스의 moveCar()
메서드
전체 자동차의 이동 시도가 끝날 때마다 한 줄씩 공백 칸을 줘서 요구사항을 충족시켜야 했는데, 이를 서비스 클래스에서 담당하고 있었다. | ⬆️ 이렇게 공백을 줘야 한다.
moveCar()
를 컨트롤러에 담당하려고 하니 클래스 분리를 한 의미가 없어지고, 출력 코드만 가져오려 하니 반복문 내에서 공백 출력을 출력해야돼서 난감했다. 이 공백 출력을 View 에서 담당하게 하고 싶었다.
그래서 공백 출력을 하는 System.out.println()
코드를 외부에서 주는 방법을 고민하다가, Runnable
인터페이스를 알게 됐다. | ⬆️ Runnable
함수형 인터페이스
Runnable
인터페이스는 딱 하나, run()
추상 메서드를 가지고 있다. 함수형 인터페이스므로 람다로 사용 가능하다.| ⬆️ Runnable
을 적용했다. 이제 컨트롤러에서 OutputView
의 공백 문자 출력 메서드를 argument로 줌으로서 출력의 역할은 View가 온전히 담당하게 되었다! 만세!
Runnable
인터페이스는 스레드가 작업을 실행할 때 사용하는 인터페이스라고 이전에 책에서 배웠었는데, 이렇게 활용할 수 있으니 신기했다. Runnable
이 단순히 스레드뿐만 아니라 이렇게 편하게도 이용할 수 있구나 생각했다.
1주차 과제 후 다른 분의 코드리뷰를 해주다가 해당 PR에 다른 분이 남기신 리뷰를 봤는데, matches()
메서드는 생성 비용이 높아서 캐싱해서 사용하는 걸 추천하신 분이 계셨다.
실제로 학습해보니 matches()
는 호출 될 때마다 매번 내부에서 Pattern
클래스의 인스턴스를 생성하고 있었다.| ⬆️ matches()
메서드 내부
개선 방법을 찾아보니, Pattern
객체를 상수로 선언해서 한번 생성한 Pattern
객체를 계속 사용하게 하는 방법이 있었다.
그래서 아래와 같이 matches()
를 사용하는 검증 클래스들에 Pattern
을 상수로 캐싱시켰다.
그리고 자동차 이름을 10000개, 전진 시도 횟수를 1000번으로 설정하여 자동차 이름 검증을 1천만번 시도했다.
| ⬆️ 캐싱 미적용
| ⬆️ 캐싱 적용
캐싱을 하지 않았을때는 최저 40초, 최대 60초 대가 나왔는데, 캐싱을 적용하니 큰 편차 없이 평균 30초대가 나온다. 성능 개선을 확실히 알 수 있다! 검증을 하는 횟수
가 많을수록 성능에 더 차이가 날 것이다.
옵저버 패턴에서 옵저버의 역할을 하는 RacingController
클래스에는 이런 메서드가 있다.
Car
객체에서 상태 변경을 통지할 때, Observer
인터페이스의 display()
를 구현한 메서드를 호출하는데, 이 때 RacingController
에서 View 에 argument로 Car
의 이름, 이동 정도를 직접 꺼내와서 준다.
이것이 정말 옳은 방법일까? 도메인 객체와 View가 강하게 결합하는 건 아닐까?
생각해보니, Car
의 내부 로직이 변화하거나 상태가 달라지면 이를 View 에서 알게되는 것 같았다. 그래서 CarDto
를 하나 만들어서 값을 넘겨주기로 했다.
이렇게 레코드를 사용해서 CarDto
클래스를 만들어주고, 이 **CarDto
를 사용해서 OutputView
가 Car
객체를 모르는 상태에서 Car
의 상태값들을 넘겨주게 했다.**
그런데 또 고민이 생겼다. 컨트롤러 내부에서, 그리고 display()
메서드 내부에서 CarDto
객체를 생성하는 책임이 과연 올바른 책임일까? SRP를 정면으로 위반하고 있는 것 같았다.
그렇다고 생성자 주입을 통해서 넣자니, Car
객체가 생성되지 않은 상태에서 해당하는 CarDto
객체를 생성할 순 없었다.
그래서 CarDto
객체를 생성하는 책임을 가진 클래스를 하나 만들었다.
클래스 이름을 CarDtoConverter
, CarDtoMapper
중에서 고민했는데, Converter
는 로직이 포함된 클래스 네이밍에 어울릴 것 같아서 CarDtoMapper
로 결정했다.
내용은 간단하다. Car
객체를 받아 CarDto
객체로 변환(매핑?) 해준다.이제 컨트롤러에서 CarDto
객체를 생성하지 않게 됐다.
오늘 하기로 마음먹었던
1. 생성자 주입 사용
2. 싱글톤 또는 정적 팩토리 메서드 사용(필요하다면)
3. DTO 사용으로 Car 객체 캡슐화 지키기
4. 정규 표현식 캐싱하기(matches() 생성 비용 높음)
5. 재사용되어야 할 객체 파악해서 재사용하기
중 불필요했던 5번, 마무리때 할 1번을 제외하고는 모든 개선 사항을 적용했다. 이제 리팩토링은 조금만 더 하고 기능을 테스트하는 테스트코드를 작성해야한다.
다른 분들이 코드 리뷰로 알려주신 개선 사항을 더 적용하고 싶지만, 시간을 보고 우선순위에 따라 진행 여부를 결정해야겠다.
기능 테스트 코드 작성은 TDD로 개발을 진행해서 그리 오랜 시간이 걸릴 것 같지는 않은데... 해봐야 알 듯 하다.
내일은 과제 제출날이다. 시간이 정말정말 빠르게 가는 것 같다. 코드 리뷰 시간도 너무 기대된다. 1주차 때 상호간에 코드 리뷰를 하며 내가 전혀 생각하지 못했던 점을 배우는게 재밌고 신선했었다!
1. 정적 팩토리 메서드 패턴
간단한 사용법을 알게 되었다. 여러가지로 활용할 수 있는 방법이 많던데, 더 학습해봐야 하는 것에 추가해놨다.
2. Runnable
인터페이스의 사용
책으로 배울 때는 스레드 관련해서만 배웠어서 스레드를 쓸 때만 사용하는 줄 알았는데, 람다식으로 사용하는 방법을 알게 되었다. 좋은 방법인 것 같다!
3. Pattern.matches()
의 캐싱
매번 matches()
를 사용하면 성능이 매우 좋지 않다는 것과, 그 개선 방법을 알게 됐다. 이것도 코드 리뷰에서 얻은 것이다!
1. 생성자 주입, 정적 팩토리 메서드, DTO를 사용해서 캡슐화를 높이려고 시도한 것
가장 신경 쓰이는게 의존도, 복잡도, 캡슐화 등등이다. 3주차 과제때는 더더더 캡슐화 시키고 싶다!
2. 새로 알게된 점이 많아진 점
어제 회고록처럼, 새로 학습할 수 있었던 게 적어서 아쉬웠는데, 오늘 새로운 패턴을 배우고 원하는 개선사항을 끝마칠 수 있어서 좋았다.
1. 중간에 커밋 하다가 revert를 했는데, 커밋 메시지를 복사해놓지 않아서 다시 주절주절 커밋 메시지를 적었다. 이건 조금 현타가 왔다...
| 정적 팩토리 메서드 패턴 (Static Factory Method)
| 정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?
-> 우테코 2기 선배님의 포스팅이다.
| 정적 팩토리 메서드(Static Factory Method)
| [Java] Thread와 Runnable에 대한 이해 및 사용법
우와,,, Runnable을 넘김으로서 View가 출력에 대한 온전한 책임을 가질 수 있게한 점이 굉장히 흥미롭네요,,, 잘 보았습니다!!