해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.
좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!
8.1 가독성과 유연성을 개선하는 리팩토링
Entity
Dish.java
package Part3.Chapter8.Chapter8_8_1.entity;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return this.name;
}
public int getCalories() {
return this.calories;
}
public Type getType() {
return this.type;
}
public boolean isVegetarian() {
return vegetarian;
}
public CaloricLevel getCaloricLevel() {
if(this.getCalories() <= 400) {
return Dish.CaloricLevel.DIET;
} else if(this.getCalories() <= 700) {
return Dish.CaloricLevel.NORMAL;
} else {
return Dish.CaloricLevel.FAT;
}
}
@Override
public String toString() {
return this.name;
}
public enum Type {
MEAT, FISH, OTHER
}
public enum CaloricLevel {
DIET, NORMAL, FAT
}
}
Process
Task.java
package Part3.Chapter8.Chapter8_8_1.task;
public interface Task {
public void execute();
public static void doSomthing(Runnable r) {
r.run();
}
public static void doSomthing(Task a) {
a.execute();
}
}
Main
package Part3.Chapter8.Chapter8_8_1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import Part3.Chapter8.Chapter8_8_1.entity.Dish;
import Part3.Chapter8.Chapter8_8_1.task.Task;
/*
* 8.1 가독성과 유연성을 개선하는 리팩토링
*
* 람다 표현식은 익명 클래스보다 코드를 좀 더 간결하게 만든다.
* 동작 파라미터화의 형식을 지원하므로 람다 표현식을 이용한 코드는 더 큰 유연성을 갖출 수 있다.
* 즉, 람다 표현식을 이용한 코드는 다양한 요구사항 변화에 대응할 수 있도록 파라미터화한다.
*/
public class Main_8_1 {
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT)
, new Dish("beef", false, 700, Dish.Type.MEAT)
, new Dish("chicken", false, 400, Dish.Type.MEAT)
, new Dish("french fries", true, 530, Dish.Type.OTHER)
, new Dish("rice", true, 350, Dish.Type.OTHER)
, new Dish("season fruit", true, 120, Dish.Type.OTHER)
, new Dish("pizza", true, 550, Dish.Type.OTHER)
, new Dish("prawns", false, 300, Dish.Type.FISH)
, new Dish("salmon", false, 450, Dish.Type.FISH));
/*
* 8.1.1 코드 가독성 개선
*
* 일반적으로 코드 가독성이 좋다는 것은 '어떤 코드를 다른 사람도 쉽게 이해할 수 있음'을 의미한다.
* 즉, 코드 가독성을 개선한다는 것은 우리가 구현한 코드를 다른 사람이 쉽게 이해하고 유지보수할 수 있게
* 만드는 것을 의미한다.
* 코드 가독성을 높이려면 코드의 문서화를 잘하고, 표준 코딩 규칙을 준수하는 등의 노력을 기울여야 한다.
*
* 자바 8에서는 코드 가독성에 도움을 주는 다음과 같은 기능을 새롭게 제공한다.
* - 코드의 장황함을 줄여 쉽게 이해할 수 있는 코드를 구현할 수 있다.
* - 메서드 레퍼런스의 스트림 API를 이용해서 코드의 의도(무엇을 수행하려는 것인지)를 쉽게 표현할 수 있다.
*
* 8.1.2 익명 클래스를 람다 표현식으로 리팩토링하기
*
* 하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩토링할 수 있다.
* 익명 클래스는 코드를 장황하게 만들고 쉽게 에러를 일으킨다.
* 람다 표현식을 이용해서 간결하고, 가독성이 좋은 코드를 구현할 수 있다.
*
* - 익명 클래스를 사용한 이전 코드
* Runnable r1 = new Runnable() {
* public void run() {
* System.out.println("Hello");
* }
* }
*
* - 람다 표현식을 사용한 최신 코드
* Runnable r2 = () -> System.out.println("Hello!!");
*
* 하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는것은 아니다.
* 1. 익명 클래스에서 사용한 this와 super는 람다 표현식에서 다른 의미를 갖는다.
* 익명 클래스에서 this는 익명 클래스 자신을 가리키지만 람다에서 this는 람다를 감싸는 클래스를 가리킨다.
* 2. 익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다(섀도 변수-shadow variable).
* 하지만 다음 코드에서 처럼 람다 표현식으로는 변수를 가릴 수 없다.
*
* int a = 10;
* Runnable r1 = () -> {
* int a = 2; // 컴파일 에러.
* System.out.println(a);
* }
*
* Runnabel r2 = new Runnable() {
* public void run() {
* int a = 2; // 모든 것이 잘 작동한다.
* System.out.println(a);
* }
* }
* 익명 클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함이 초래될 수 있다.
* 익명 클래스는 인스턴스화할 때 명시적으로 형식이 정해지는 반명 람다의 형식은 콘텍스트에 따라 달라지기 때문이다.
*/
// Task를 구현하는 익명 클래스르 전달할 수 있다.
Task.doSomthing(new Task() {
@Override
public void execute() {
System.out.println("익명 클래스 - Task Danger danger!!!");
}
});
Task.doSomthing(new Runnable() {
@Override
public void run() {
System.out.println("익명 클래스 - Runabel Danger danber!!");
}
});
/*
* 위 익명 클래스를 람다 표현식으로 바꾸면 메서드를 호출할 때 Runnable와 Task 모두 대상 형식이 될 수 있으므로 모호함이 발생한다.
*/
// 모호함 때문에 아래 주석을 해제하면 에러가 발생한다.
// Task.doSomthing(() -> System.out.println("Danger danger"));
// 명시적 형변환을 이용해서 모호함을 제거할 수 있다.
Task.doSomthing((Task)() -> System.out.println("람다 표현식 - Task Danger danger"));
Task.doSomthing((Runnable)() -> System.out.println("람다 표현식 - Runnable Danger danger"));
/*
* 8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기
*
* 람다 표현식 대신 메서드 레퍼런스를 이용하면 가독성을 높일 수 있다.
* 메서드 레퍼런스의 메서드명으로 코드의 의도를 명확하게 알릴 수 있기 때문이다.
*/
Map<Dish.CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
.collect(Collectors.groupingBy(dish -> {
if(dish.getCalories() <= 400) {
return Dish.CaloricLevel.DIET;
} else if(dish.getCalories() <= 700) {
return Dish.CaloricLevel.NORMAL;
} else {
return Dish.CaloricLevel.FAT;
}
}));
System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 람다 표현식 : " + dishesByCaloricLevel);
/*
* 람다 표현식을 별도의 메서드로 추출한 다음에 groupingBy에 인수로 전달할 수 있다.
*/
dishesByCaloricLevel = menu.stream()
.collect(Collectors.groupingBy(Dish::getCaloricLevel));
System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 메서드 레퍼런스 : " + dishesByCaloricLevel);
/*
* sum, maximum 등 자주 사용하는 리듀싱 연산은 메서드 레퍼런스와 함께 사용할 수 있는 내장 헬퍼 메서드를 제공한다.
* 최대값이나 합계를 계산할 때 람다 표현식과 저수준 리듀싱 연산을 조합하는것 보다 Collectors API를 사용하면 코드의 의도가
* 더 명확해진다.
*/
// 저수준 리듀싱 조합 코드.
int totalCalories = menu.stream()
.map(Dish::getCalories)
.reduce(0, (c1, c2) -> c1 + c2);
System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 저수준 리듀싱 조합 : " + totalCalories);
// Collectors API 코드.
totalCalories = menu.stream()
.collect(Collectors.summingInt(Dish::getCalories));
System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - Collectors API : " + totalCalories);
/*
* 8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기
*
* 이론적으로는 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API로 바꿔야 한다.
* 스트림 API는 데이터 처리 파이프라인의 의도를 더 명확하게 보여주고 쇼트서킷과 게으름이라는
* 강력한 최적화뿐 아니라 멀티코어 아키텍처를 활용할 수 있는 지름길을 제공한다.
*/
/*
* 아래 명령형 코드는 필터링과 추출이란 두 가지 패턴으로 엉킨 코드다.
* 이런 코드를 접한 다른 개발자는 전체 구현을 자세히 살펴본 이후에야 전체 코드의 의도를 파악할 수 있을것이다.
*/
List<String> dishNames = new ArrayList<>();
for(Dish dish : menu) {
if(dish.getCalories() > 300) {
dishNames.add(dish.getName());
}
}
System.out.println("8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기 - 명령형 코드 : " + dishNames);
/*
* 스트림 API를 이용하면 문제를 좀 더 직접적으로 기술할 수 있을 뿐 아니라 쉽게 병렬화할 수 있다.
*/
dishNames = menu.stream()
.filter((dish) -> dish.getCalories() > 300)
.map(Dish::getName)
.collect(Collectors.toList());
System.out.println("8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기 - 스트림 코드 : " + dishNames);
/*
* 8.1.5 코드 유연성 개선
*
* 람다 표현식을 이용하면 "동작 파라미터화(behaviour parameterization)"를 쉽게 구현할 수 있다.
* 즉, 다양한 람다를 전달해서 다양한 동작을 표현할 수 있다. 따라서 변화하는 요구사항에 대응할 수 있는 코드를
* 구현할 수 있다.
*/
}
}
8.2 람다로 객체지향 디자인 패턴 리팩토링하기
Design pattern
Chain
Abstract
package Part3.Chapter8.Chapter8_8_2.chain.abs;
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(this.successor != null) {
return this.successor.handle(r);
}
return r;
}
abstract protected T handleWork(T input);
}
Implement
HeaderTextProcessing.java
package Part3.Chapter8.Chapter8_8_2.chain.impl;
import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
public class HeaderTextProcessing extends ProcessingObject<String>{
@Override
protected String handleWork(String input) {
return "From Raoul. Mario and Alan : " + input;
}
}
SpellCheckerProcessing.java
package Part3.Chapter8.Chapter8_8_2.chain.impl;
import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
public class SpellCheckerProcessing extends ProcessingObject<String>{
@Override
protected String handleWork(String input) {
return input.replaceAll("labda", "lambda");
}
}
Factory
Entity
Product.java
package Part3.Chapter8.Chapter8_8_2.factory.product;
public class Product {
protected String name;
public Product() { }
public Product(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return "{"
+ "name : " + this.name
+ "}";
}
}
Stock.java
package Part3.Chapter8.Chapter8_8_2.factory.product;
public class Stock extends Product{
public Stock() {
super.name = "stock";
}
}
Bond.java
package Part3.Chapter8.Chapter8_8_2.factory.product;
public class Bond extends Product {
public Bond() {
super.name = "bond";
}
}
Loan.java
package Part3.Chapter8.Chapter8_8_2.factory.product;
public class Loan extends Product {
public Loan() {
super.name = "loan";
}
}
Implement
LambdaProductFactory.java
package Part3.Chapter8.Chapter8_8_2.factory;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import Part3.Chapter8.Chapter8_8_2.factory.product.Bond;
import Part3.Chapter8.Chapter8_8_2.factory.product.Loan;
import Part3.Chapter8.Chapter8_8_2.factory.product.Product;
import Part3.Chapter8.Chapter8_8_2.factory.product.Stock;
public class LambdaProductFactory {
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);
}
}
ProductFactory.java
package Part3.Chapter8.Chapter8_8_2.factory;
import Part3.Chapter8.Chapter8_8_2.factory.product.Bond;
import Part3.Chapter8.Chapter8_8_2.factory.product.Loan;
import Part3.Chapter8.Chapter8_8_2.factory.product.Product;
import Part3.Chapter8.Chapter8_8_2.factory.product.Stock;
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);
}
}
}
Observer
Interface
Observer.java
package Part3.Chapter8.Chapter8_8_2.observer.inter;
public interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
Subject.java
package Part3.Chapter8.Chapter8_8_2.observer.inter;
public interface Observer {
void notify(String tweet);
}
Implement
Guardian.java
package Part3.Chapter8.Chapter8_8_2.observer.impl;
import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
public class Guardian implements Observer {
@Override
public void notify(String tweet) {
if(tweet != null && "queen".equals(tweet)) {
System.out.println("Yet another news in London... " + tweet);
}
}
}
LeMonde.java
package Part3.Chapter8.Chapter8_8_2.observer.impl;
import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
public class LeMonde implements Observer {
@Override
public void notify(String tweet) {
if(tweet != null && "wine".equals(tweet)) {
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
NYTimes.java
package Part3.Chapter8.Chapter8_8_2.observer.impl;
import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
public class NYTimes implements Observer {
@Override
public void notify(String tweet) {
if(tweet != null && "money".equals(tweet)) {
System.out.println("Breaking new in NY! " + tweet);
}
}
}
Feed.java
package Part3.Chapter8.Chapter8_8_2.observer.impl;
import java.util.ArrayList;
import java.util.List;
import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
import Part3.Chapter8.Chapter8_8_2.observer.inter.Subject;
public class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer o) {
this.observers.add(o);
}
@Override
public void notifyObservers(String tweet) {
this.observers.forEach(o -> o.notify(tweet));
}
}
Strategy
Interface
ValidationStrategy.java
package Part3.Chapter8.Chapter8_8_2.strategy.inter;
public interface ValidationStrategy {
boolean execute(String s);
}
Implement
IsNumeric.java
package Part3.Chapter8.Chapter8_8_2.strategy.inter;
public class IsNumeric implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("\\\\d+");
}
}
IsAllLowerCase.java
package Part3.Chapter8.Chapter8_8_2.strategy.inter;
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
Validator.java
package Part3.Chapter8.Chapter8_8_2.strategy;
import Part3.Chapter8.Chapter8_8_2.strategy.inter.ValidationStrategy;
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy strategy) {
this.strategy = strategy;
}
public boolean validate(String s) {
return this.strategy.execute(s);
}
}
TemplateMethod
Entity
Customer.java
package Part3.Chapter8.Chapter8_8_2.templateMethod.customer;
public class Customer {
private final String name;
private final int id;
public Customer(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return this.name;
}
public int getId() {
return this.id;
}
@Override
public String toString() {
return "{"
+ "name : " + this.name
+ ", id : " + this.id
+ "}";
}
}
Abstract
OnlineBanking.java
package Part3.Chapter8.Chapter8_8_2.templateMethod.abs;
import java.util.function.Consumer;
import java.util.function.Function;
import Part3.Chapter8.Chapter8_8_2.templateMethod.Database;
import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
public abstract class OnlineBanking {
Function<Integer, Customer> getCustomerWithId = (id) -> Database.getCustomerWithId(id);
public void processCustomer(int id) {
Customer c = this.getCustomerWithId.apply(id);
this.makeCustomerHappy(c);
}
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
Customer c = this.getCustomerWithId.apply(id);
makeCustomerHappy.accept(c);
}
public Function<Integer, Customer> rtnCustomerWithId() {
return this.getCustomerWithId;
}
public abstract void makeCustomerHappy(Customer c);
}
Implement
OnlineBankingLambda.java
package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
public class OnlineBankingLambda extends OnlineBanking {
@Override
public void makeCustomerHappy(Customer c) { }
}
OnlineDepositBanking.java
package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
public class OnlineDepositBanking extends OnlineBanking {
@Override
public void makeCustomerHappy(Customer c) {
System.out.println(c.getName() + "님 입금이 완료 되었습니다.");
}
}
OnlineWithdrawBanking.java
package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
public class OnlineWithdrawBanking extends OnlineBanking {
@Override
public void makeCustomerHappy(Customer c) {
System.out.println(c.getId() + " 아이디를 가지신 " + c.getName() + "님 출금이 완료 되었습니다.");
}
}
Database.java
package Part3.Chapter8.Chapter8_8_2.templateMethod;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
public class Database {
final static List<Customer> customers = Arrays.asList(
new Customer("kim", 1)
, new Customer("sung", 2)
, new Customer("wook", 3) );
public static Customer getCustomerWithId(int id) {
return Database.customers.stream()
.filter((customer) -> customer.getId() == id)
.limit(1)
.collect(Collectors.toList())
.get(0);
}
}
Main
package Part3.Chapter8.Chapter8_8_2;
import java.util.function.UnaryOperator;
import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
import Part3.Chapter8.Chapter8_8_2.chain.impl.HeaderTextProcessing;
import Part3.Chapter8.Chapter8_8_2.chain.impl.SpellCheckerProcessing;
import Part3.Chapter8.Chapter8_8_2.factory.LambdaProductFactory;
import Part3.Chapter8.Chapter8_8_2.factory.ProductFactory;
import Part3.Chapter8.Chapter8_8_2.observer.impl.Feed;
import Part3.Chapter8.Chapter8_8_2.observer.impl.Guardian;
import Part3.Chapter8.Chapter8_8_2.observer.impl.LeMonde;
import Part3.Chapter8.Chapter8_8_2.observer.impl.NYTimes;
import Part3.Chapter8.Chapter8_8_2.strategy.Validator;
import Part3.Chapter8.Chapter8_8_2.strategy.inter.IsAllLowerCase;
import Part3.Chapter8.Chapter8_8_2.strategy.inter.IsNumeric;
import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineBankingLambda;
import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineDepositBanking;
import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineWithdrawBanking;
/*
* 8.2 람다로 객체지향 디자인 패턴 리팩토링하기
*/
public class Main_8_2 {
public static void main(String[] args) {
/*
* 8.2.1 전략 패턴
*
* 전략 패턴은 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법이다.
*/
Validator numericValidator = new Validator(new IsNumeric());
System.out.println("8.2.1 전략 디자인 패턴 - IsNumeric 객체 : " + numericValidator.validate("aaaa"));
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
System.out.println("8.2.1 전략 디자인 패턴 - IsAllLowerCase 객체 : " + lowerCaseValidator.validate("bbbb"));
/*
* ValidationStrategy는 함수형 인터페이스 Predicate<String>과 같은 함수 디스크립터를 갖고 있으므로
* 다양한 전략을 새로운 클래스 구현할 필요 없이 람다 표현식으로 구현할 수 있다.
*/
numericValidator = new Validator((s) -> s.matches("\\\\d+"));
System.out.println("8.2.1 전략 디자인 패턴 - IsNumeric의 람다 표현식 : " + numericValidator.validate("aaaa"));
lowerCaseValidator = new Validator((s) -> s.matches("[a-z]+"));
System.out.println("8.2.1 전략 디자인 패턴 - IsAllLowerCase의 람다 표현식 : " + lowerCaseValidator.validate("bbbb"));
/*
* 8.2.2 템플릿 메서드 패턴
*
* 알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 유연함을 제공해야 할 때 템플릿 메서드 패턴을 사용한다.
* 다시 말해 '이 알고리즘을 사용하고 싶은데 그대로는 안 되고 조금 고쳐야 하는'상황에 적합하다.
*/
System.out.println("8.2.2 템플릿 메서드 패턴 : ");
OnlineDepositBanking onlineDepositBanking = new OnlineDepositBanking();
onlineDepositBanking.processCustomer(1);
OnlineWithdrawBanking onlineWithdrawBanking = new OnlineWithdrawBanking();
onlineWithdrawBanking.processCustomer(2);
/*
* 인제 OnlineBanking 클래스를 상속받지 않고 직접 람다 표현식을 전달해서 다양한 동작을 추가할 수 있다.
*/
System.out.println("8.2.2 템플릿 메서드 패턴 - 람다 표현식 : ");
new OnlineBankingLambda().processCustomer(3, (customer)-> {
System.out.println(customer.getName() + "님의 찾으신 ID는 " + customer.getId() + " 입니다.");
});
new OnlineBankingLambda().processCustomer(1, (customer)-> {
System.out.println(customer.toString());
});
new OnlineBankingLambda().processCustomer(2, onlineDepositBanking::makeCustomerHappy);
new OnlineBankingLambda().processCustomer(1, onlineWithdrawBanking::makeCustomerHappy);
/*
* 8.2.3 옵저버
*
* 어떤 이벤트가 발생했을 때 한 객체(주체-subject라 불리는)가 다른 객체 리스트(옵저버 observer)에 자동으로 알림을
* 보내야 하는 상황에서 옵저버 디자인 패턴을 사용한다.
* 예를 들어 주식의 가격(주체) 변동에 반응하는 다수의 거래자(옵저버)에서도 옵저버 패턴을 사용할 수 있다.
*/
System.out.println("8.2.3 옵저버 패턴 : ");
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("money");
f.notifyObservers("queen");
f.notifyObservers("wine");;
System.out.println("8.2.3 옵저버 패턴 - 람다 표현식 : ");
f.registerObserver((tweet) -> {
if(tweet != null && "moenyLambda".equals(tweet)) {
System.out.println("Lambda Breaking new in NY! " + tweet);
}
});
f.registerObserver((tweet) -> {
if(tweet != null && "wineLambda".equals(tweet)) {
System.out.println("Lambda Today cheese, wine and news! " + tweet);
}
});
f.registerObserver((tweet) -> {
if(tweet != null && "queenLambda".equals(tweet)) {
System.out.println("Lambda Yet another news in London... " + tweet);
}
});
f.notifyObservers("moenyLambda");
f.notifyObservers("wineLambda");
f.notifyObservers("queenLambda");
/*
* 8.1.4 의무 체인
*
* 작업처리 객체의 체인(동작 체인 등)을 만들 때는 의무 체인 패턴을 사용한다.
* 한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 해야 할 작업을
* 처리한 다음에 또 다른 객체로 전달하는 식이다.
*/
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
System.out.println("8.2.4 의무 체인 : " + p1.handle("Aren't labdas really sexy?!!"));
UnaryOperator<String> headerProcessing = (text) -> "From raoul, Mario and Alan : " + text;
UnaryOperator<String> spellCheckerProcessing = (text) -> text.replaceAll("labda", "lambda");
System.out.println("8.2.4 의무 체인 - 람다 표현식 : " + headerProcessing
.andThen(spellCheckerProcessing)
.apply("Aren't labdas really sexy?!!"));
/*
* 8.2.5 팩토리
*
* 인스턴화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 디자인 패턴을 사용한다.
* createProduct 메서드는 생성자와 설정을 외부로 노출하지 않음으로써 클라이언트가 단순하게
* 상품을 생산할 수 있다.
*/
System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("loan"));
System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("stock"));
System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("bond"));
/*
* 여러 인수를 전달하는 상황에서는 단순한 Supplier로는 사용하기 힘들다.
* 만약 생성자 인수가 3개가 필요하다고 한다면 세 인수를 지원하는 새로운 함수형 인터페이스를 만들어야 한다.
*
* public interface TriFunction<T, U, V, R> {
* R apply(T t, U u, V v);
* }
*
* Map<String, TriFunction<Integer, Integer, String, Product>> map = new HashMap<>();
*/
System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("loan"));
System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("stock"));
System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("bond"));
}
}
요약
- 람다 표현식으로 가독성이 좋고 더 유연한 코드를 만들 수 있다.
- 익명 클래스는 람다 표현식으로 바꾸는 것이 좋다. 하지만 이때 this, 변수 섀도 등 미묘하게 의미상 다른 내용이 있음을 주의하자. 메서드 레퍼런스로 람다 표현식보다 더 가독성이 좋은 코드를 구현할 수 있다.
- 반복적으로 컬렉션을 처리하는 루틴은 스트림 API로 대체할 수 있을지 고려하는것이 좋다.
- 람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴에서 발생하는 불필요한 코드를 제거할 수 있다.
- 람다 표현식도 단위 테스트를 수행할 수 있다. 하지만 람다 표현식 자체를 테스트하는 것보다는 람다 표현식이 사용되는 메서드의 동작을 테스트하는 것이 바람직하다.
- 복잡한 람다 표현식은 일반 메서드로 재구현할 수 있다.
- 람다 표현식을 사용하면 스택 트레이스를 이해하기 어려워진다.
- 스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값을 확인할 수 있다.