본문 바로가기
Book/모던 자바 인 액션

[모던 자바 인 액션] Chapter 09. 리팩터링, 테스팅, 디버깅 (2)

by ghan2 2024. 11. 12.

 

9.2.3 옵저버

어떤 이벤트가 발생했을 때 한 객체가 다른 객체 리스트(옵저버라 불리는)에 자동으로 알림을 보내야 하는 상황에서 옵저버 디자인 패턴을 사용한다. GUI 애플리케이션에서 옵저버 패턴이 자주 등장한다. (사용자가 버튼을 클릭하면 옵저버에 알림이 전달되고 정해진 동작이 수행)

https://refactoring.guru/ko/design-patterns/observer

왼쪽이 주제 객체(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 팩토리

인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 디자인 패턴을 사용한다. 

https://refactoring.guru/ko/design-patterns/factory-method

 

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 메서드로 중간값을 확인할 수 있다.