9.2.3 옵저버
어떤 이벤트가 발생했을 때 한 객체가 다른 객체 리스트(옵저버라 불리는)에 자동으로 알림을 보내야 하는 상황에서 옵저버 디자인 패턴을 사용한다. GUI 애플리케이션에서 옵저버 패턴이 자주 등장한다. (사용자가 버튼을 클릭하면 옵저버에 알림이 전달되고 정해진 동작이 수행)
왼쪽이 주제 객체(Subject)이고 오른쪽 목록이 옵저버(Observer)라고 생각하면 된다.
// Observer
interface Observer {
void notify(String tweet);
}
// 다양한 신문 매체가 뉴스 트윗을 구독하고 있으며, 특정 키워드를 포함하는 트윗이 등록되면 알림을 받고 싶어 한다 !
// 뉴욕 타임즈
class NYTimes implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
}
}
// 가디언
class Guardian implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet more news from London... " + tweet);
}
}
}
interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o) {
this.observers.add(o);
}
public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.notifyObservers("The queen said her favvourit book is Modern Java!");
이때 뉴욕타임즈와 가디언을 구현했던 추상클래스를 람다로 바꾸어 쓸 수 있다.
9.2.4 의무 체인
작업 처리 객체의 체인(동작 체인 등)을 만들 때는 의무 체인 패턴을 사용한다. 한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 해야 할 작업을 처리한 다음에 또 다른 객체로 전달하는 식이다.
public abstract class ProcessingObject<T> {
protected ProcessingObject<T> successor;
public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}
public T handle(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handle(r);
}
return r;
}
abstract protected T handleWork(T input);
}
흐름은 이렇다. A작업(handleWork 메서드 구현) , B작업(handleWork 메서드 구현) -> A.setSuccessor(B)로 체인을 걸어줌 !
-> A.handle(넣어줄 값); 으로 호출하면 A를 실행한 후, 그 작업을 이어서 B가 받아 작업을 완료한다 !
9.2.5 팩토리
인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 디자인 패턴을 사용한다.
9.3 람다 테스팅
9.3.1 보이는 람다 표현식의 동작 테스팅
람다는 익명이므로 테스트 코드 이름을 호출할 수 없다. 따라서 필요하면 람다를 필드에 저장해서 재사용할 수 있으며 람다의 로직을 테스트할 수 있다.
public class Point {
public final static Comparator<Point> compareByXAndThenY =
comparing(Point::getX).thenComparing(Point::getY);
...
}
이제 이 변수를 이용해서 테스트를 할 수 있다 !
9.3.2 람다를 사용하는 메서드의 동작에 집중하라
람다의 목표는 정해진 동작을 다른 메서드에서 사용할 수 있도록 하나의 조각으로 캡슐화하는 것이다. 람다 표현식을 사용하는 메서드의 동작을 테스트함으로써 람다를 공개하지 않으면서도 람다 표현식을 검증할 수 있다.
@Test
public void testMoveAllPointsRightBy() throws Exception {
List<Point> points = Arrays.asList(new Point(5, 5), new Point(10, 5));
List<Point> expectedPoints = Arrays.asList(new Point(15, 5), new Point(20, 5));
List<Point> newPoints = Point.moveAllPointsRightBy(points, 10);
assertEquals(expectedPoints, newPoints);
}
9.3.3 복잡한 람다를 개별 메서드로 분할하기
결론부터 말하자면 복잡한 람다 표현식을 메서드 참조로 바꾸는 것이다. 그러면 일반 메서드를 테스트하듯이 람다 표현식을 테스트할 수 있다.
9.5 마치며
- 람다 표현식으로 가독성이 좋고 더 유연한 코드를 만들 수 있다.
- 익명 클래스는 람다 표현식으로 바꾸는 것이 좋다. 하지만 이때 this, 변수 섀도 등 미묘하게 의미상 다른 내용이 있음에 주의하자.
- 메서드 참조로 람다 표현식보다 더 가독성이 좋은 코드를 구현할 수 있다.
- 반복적으로 컬렉션을 처리하는 루틴은 스트림 API로 대체할 수 있을지 고려하는 것이 좋다.
- 람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴에서 발생하는 불필요한 코드를 제거할 수 있다.
- 람다 표현식도 단위 테스트를 수행할 수 있다. 하지만 람다 자체를 테스트 하는 것 보다는 람다 표현식이 사용되는 메서드의 동작을 테스트하는 것이 바람직하다.
- 복잡한 람다 표현식은 일반 메서드로 재구현할 수 있다.
- 람다 표현식을 사용하면 스택 트레이스를 이해하기 어려워진다.
- 스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값을 확인할 수 있다.
'Book > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] Chapter 11. null 대신 Optional 클래스 (1) (2) | 2024.11.15 |
---|---|
[모던 자바 인 액션] Chapter 10.람다를 이용한 도메인 전용 언어 (1) (1) | 2024.11.14 |
[모던 자바 인 액션] Chapter 09. 리팩터링, 테스팅, 디버깅 (1) (4) | 2024.11.11 |
[모던 자바 인 액션] Chapter 08. 컬렉션 API 개선 (2) (1) | 2024.11.09 |
[모던 자바 인 액션] Chapter 08. 컬렉션 API 개선 (1) (0) | 2024.11.07 |