# 9장

\ <br>

### 1. 리팩터링

코드 가독성이 좋다는 건 처음 접하는 사람도 이해하기 쉽게 작성된 코드라는 말이다. 리팩터링은 코드를 간결하고 이해하기 쉽게 만드는 것이다. 람다, 메서드 참조, 스트림을 활용하면 코드 가독성을 높일 수 있다.<br>

<br>

#### 1.1. 익명 클래스를 람다 표현식으로 리팩터링하기

하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩터링 할 수 있다.<br>

```java
// 익명 클래스 활용
Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Hello");
    }
};

// 람다 표현식 활용
Runnable r2 = () -> System.out.println("Hello");
```

익명 클래스를 람다 표현식으로 변환할 때는 몇 가지 주의점이 있다.\
첫째로 this와 super는 익명 클래스와 람다 클래스에서 의미가 다르다. 익명 클래스에서 this는 자신을 가리키지만, 람다 표현식에서 this는 람다를 감싸는 클래스를 가리킨다.\
둘째로 익명 클래스는 감싸고 있는 변수를 가릴 수 있지만, 람다 표현식은 변수를 가릴 수 없다.<br>

```java
// 익명 클래스 활용
Runnable r1 = new Runnable() {
    public void run() {
        int a = 2;
        System.out.println(a);
    }
}

// 람다 표현식 활용
int a = 10;
Runnable r2 = () -> {
    int a = 2; // 컴파일 에러
    system.out.println(a);
}
```

셋쩨로 익명 클래스를 람다 표현식으로 바꾸면 컨텍스트 오버로딩에 따른 모호함이 일어날 수 있다. 익명 클래스는 인스턴스화 할 때 명시적으로 형식이 정해지는 반면 람다 표현식은 컨텍스트에 따라 달라지기 때문이다.<br>

```java
interface Task {
    public void execute();
}
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a) { r.execute(); }

// 익명 클래스 활용
doSomething(new Task() {
    public void execute() {
        System.out.println("Danger danger!!");
    }
});

// 람다 표현식 활용(모호함)
doSomething(() -> System.out.println("Danger danger!!"));

// 람다 표현식 활용(명시적 형변환 사용)
doSomething((Task)() -> System.out.println("Danger danger!!"));
```

위 예시처럼 람다 표현식은 doSomething(Runnable)과 doSomething(Task) 중 어느 것을 가리키는지 말 수 없다. 이를 해결하기 위해 명시적 형변환을 사용할 수 있다.<br>

<br>

#### 1.2. 람다 표현식을 메서드 참조로 리팩터링하기

람다 표현식을 메서드 참조로 리팩터링할 수 있다.<br>

```java
// 람다 표현식 활용
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
    menu.stream()
        .collect(groupingBy(dish -> {
            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
            else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
        }));

// 메서드 참조 활용
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
    menu.stream()
        .collect(groupingBy(Dish::getCaloricLevel)); // 메서드로 추출

// Dish 클래스에 getCaloricLevel 메서드 추가
public class Dish {
    /* ...생략... */
    public CaloricLevel getCaloricLevel() {
        if (dish.getCalories() <= 400) return CaloricLevel.DIET;
        else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
        else return CaloricLevel.FAT;
    }
}
```

comparing과 maxBy 같은 정적 헬퍼 메서드를 활용할 수도 있다. sum, maximum 등 자주 사용하는 리듀싱 연산은 메서드 참조와 함께 사용할 수 있는 내장 헬퍼 메서드를 제공한다.<br>

<br>

#### 1.3. 명령형 데이터 처리를 스트림으로 리팩터링하기

명령형 데이터 처리를 스트림으로 리팩터링할 수 있다.<br>

```java
// 명령형 데이터 처리
List<String> dishNames = new ArrayList<>();
for(Dish dish : menu) {
    if (dish.getCalories() > 300) {
        dishNames.add(dish.getName());
    }
}

// 스트림 API 활용
menu.parallelStream()
    .filter(d -> d.getCalories() > 300)
    .map(Dish::getName)
    .collect(toList());
```

\ <br>

### 2. 람다로 객체지향 디자인 패턴 리팩터링하기

디자인 패턴(design pattern)은 공통적인 소프트웨어 문제를 설계할 때 재사용할 수 있는 검증된 청사진을 말한다. 예를 들어 구조체와 동작하는 알고리즘을 분리하고 싶을 때 방문자 디자인 패턴(visitor design pattern)을 사용할 수 있다. 또한 클래스 인스턴스를 하나의 객체로 제한하기 위해 싱글톤 패턴(singleton pattern)을 사용할 수 있다.<br>

#### 2.1. 전략

전략 패턴(strategy pattern)은 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법이다. 전략 패턴은 세 부분으로 구성된다.<br>

* 전략 객체를 사용하는 한 개 이상의 클라이언트
* 알고리즘을 나타내는 전략 인터페이스
* 다양한 알고리즘을 나타내는 한 개 이상의 인터페이스 구현

![png](https://979546957-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXToj3GG8hYUuKGTWqvUm%2Fuploads%2Fgit-blob-1ba93341b2f9fe0066c740b9905dfcef5428d06a%2F9-1.png?alt=media)

```java
// 인터페이스
public interface ValidationStrategy {
    boolean execute(String s);
}

// 인터페이스 구현
public class IsAllLowerCase implements ValidationStrategy {
    public boolean execute(String s) {
        return s.matches("[a-z]+");
    }
}

public class IsNumeric implements ValidationStrategy {
    public boolean execute(String s) {
        return s.matcheds("\\d+");
    }
}

// 활용
public class Validator {
    private final ValidationStrategy strategy;
    public Validator(ValidationStrategy v) {
        this.strategy = v;
    }
    public boolean validate(String s) {
        return strategy.execute(s);
    }
}
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa");
```

람다 표현식을 활용하면 전략 디자인 패턴에서 발생하는 자잘한 코드를 제거할 수 있다.<br>

```java
Validator numericValidator = 
    new Validator((String s) -> s.matches("[a-z]="));
```

<br>

#### 2.2. 템플릿 메서드

템플릿 메서드 패턴(template method pattern)은 알고리즘의 개요를 만든 다음 알고리즘의 일부를 수정하는 기법이다.<br>

```java
abstract class OnlineBanking {
    public void processCustomer(int id) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy(c);
    }
    abstract void makeCustomerHappy(Customer c);
}
```

람다 표현식을 활용하면 템플릿 메서드 디자인 패턴에서 발생하는 자잘한 코드를 제거할 수 있다.<br>

```java
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
    Customer c = Database.getCustomerWithId(id);
    makeCustomerHappy.accept(c);
}

// 람다 표현식 활용
new OnlineBankingLambda()
    .processCustomer(1337, (Customer c) ->
        System.out.println("Hello " + c.getName()));
```

<br>

#### 2.3. 옵저버

옵저버 패턴(observer pattern)은 어떤 이벤트가 발생했을 때 한 객체(subject)가 다른 객체 리스트(observer)에 자동으로 알림을 보내는 기법이다. 옵저버 패턴으로 커스터마이즈 된 알림 시스템을 설계하고 구현할 수 있다.<br>

![png](https://979546957-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXToj3GG8hYUuKGTWqvUm%2Fuploads%2Fgit-blob-1d04b7177f8beac02bd98ec35ea5ae65b76060ba%2F9-2.png?alt=media)

```java
// 인터페이스 정의
interface Observer {
    void notify(String tweet);
}
interface Subject {
    void registerObserver(Observer o);
    void notifyObservers(String tweet);
}

// 옵저버 구현
class NYTimes impelments Observer {
    public void notify(String tweet) {
        if (tweet != null && tweet.contains("money")) {
            System.out.println("NYTimes! " + tweet);
        }
    }
}
class Guardian implements Observer {
    public void notify(String tweet) {
        if (tweet != null && tweet.contains("queen")) {
            System.out.println("Guardian! " + 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("Time is money.");
```

람다 표현식을 활용하면 옵저버 패턴에서 발생하는 자잘한 코드를 제거할 수 있다.<br>

```java
f.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("money")) {
        System.out.println("NYTimes! " + tweet);
    }
});
f.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("queen")) {
        System.out.println("Guardian! " + tweet);
    }
});
```

<br>

#### 2.4. 책임 연쇄

책임 연쇄 패턴(chain of responsibility pattern)은 작업 처리 객체의 체인을 만드는 기법이다. 한 객체가 어떤 작업을 처리한 다음 다른 객체로 결과를 전달하고, 다른 객체로 작업을 처리한 다음 또 다른 객체로 전달한다.<br>

![png](https://979546957-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXToj3GG8hYUuKGTWqvUm%2Fuploads%2Fgit-blob-6721433c8f2e61892ab5c3b24422172b28e64ed8%2F9-3.png?alt=media)

```java
// 작업 처리 객체 추상 클래스
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 sucessor.handle(r);
        }
        return r;
    }
    abstract protected T handleWork(T input);
}

// 추상 클래스 상속
public class HeaderTextProcessing extends ProcessingObject<String> {
    public String handleWork(String text) {
        return "Title: " + text;
    }
}
public class SpellCheckerProcessing extends ProcessingObject<String> {
    public String handleWork(String text) {
        return text.replaceAll("labda", "lambda");
    }
}

// 작업 체인
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
String result = p1.handle("Labdas are great.");
System.out.println(result); // "Title: Lambdas are great." 출력
```

람다 표현식을 활용하면 책임 연쇄 패턴에서 발생하는 자잘한 코드를 제거할 수 있다.<br>

```java
UnaryOperator<String> headerProcessing =
    (String text) -> "Title: " + text;
UnaryOperator<String> spellCheckerProcessing =
    (String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline =
    headerProcessing.andThen(spellCheckerProcessing);
String result = pipeline.apply("Labdas are great.");
```

<br>

#### 2.5. 팩토리

팩토리 패턴(factory pattern)은 인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만드는 기법이다.<br>

```java
public class ProductFactory {
    public static Product createProduct(String name) {
        switch (name) {
            case "loan": return new Loan();
            case "stock": return new Stock();
            case "bond": return new Bond();
            default: throw new RuntimeException("No such product " + name);
        }
    }
}
Product p = ProductFactory.createProduct("loan");
```

생성자 참조를 활용하면 팩토리 패턴에서 발생하는 자잘한 코드를 제거할 수 있다.<br>

```java
final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
    map.put("loan", Loan::new);
    map.put("stock", Stock::new);
    map.put("bond", Bond::new);
}
public static Product createProduct(String name) {
    Supplier<Product> p = map.get(name);
    if (p != null) return p.get();
    throw new IllegalArgumentException("No such product " + name);
}
```

\ <br>

### 3. 람다 테스팅

그래픽 애플리케이션의 일부인 Point 클래스가 있다고 생각해보자.<br>

```java
public class Point {
    private final int x;
    private final int y;

    private Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }

    public int getY() { return y; }

    public final static Comparator<Point> compareByXAndThenY =
        comparing(Point::getX).thenComparing(Point::getY);
}
```

람다는 익명 함수이므로 테스트 코드 이름을 호출할 수 없다. 그러나 람다 표현식은 함수형 인터페이스의 인스턴스를 생성하므로 생성된 인스턴스의 동작으로 람다 표현식을 테스트할 수 있다.<br>

```java
@Test
public void testComparingTwoPoints() throws Exception {
    Point p1 = new Point(10, 15);
    Point p2 = new Point(10, 20);
    int result = Point.compareByXAndThenY.compare(p1, p2);
    assertTrue(result < 0);
}
```

\ <br>

### 4. 디버깅

#### 4.1. 스택 트레이스

```java
public class Debugging {
    public static void main(String[] args) {
        List<Point> points = Arrays.asList(new Point(12, 2), null);
        points.stream().map(p -> p.getX()).forEach(System.out::println);
    }
}
```

코드를 실행하면 다음과 같은 스택 트레이스가 출력된다.<br>

```
Exception in thread "main" java.lang.NullPointerException
    at Debugging.lambda$main$0(Debugging.java:6)
    at Debugging$$Lambda$5/284720968.apply(Unknown Source)
```

람다 표현식은 이름이 없기 때문에 컴파일러가 람다를 참조하는 이름을 만들어낸다. 메서드 참조를 사용해도 스택 트레이스에는 메서드 명이 나타나지 않는다. 참조하는 메서드가 동일한 위치에 선언되어 있어야만 메서드 참조 이름이 스택 트레이스에 출력된다.<br>

<br>

#### 4.2. 로깅

forEach를 호출하면 전체 스트림이 소비된다.<br>

```java
numbers.stream()
        .map(x -> x + 17)
        .limit(3)
        .forEach(System.out::println);
```

peek 스트림 연산을 활용해서 소비 없이 스트림 파이프라인 연산을 디버깅할 수 있다. peek은 각 원소를 소비한 것처럼 동작하지만 실제로 스트림의 원소를 소비하지는 않는다. peek은 자신이 확인한 원소를 다음 파이프라인으로 전달한다.<br>

```java
numbers.stream()
        .peek(x -> System.out.println("from stream: " + x))
        .map(x -> x + 17)
        .peek(x -> System.out.println("after map: " + x))
        .limit(3)
        .peek(x -> System.out.println("after limit: " + x))
        .collect(toList());
```

\
\
참고 자료

* 모던 자바 인 액션 - 한빛미디어
