람다로 객체지향 디자인 패턴 리팩터링하기-지선학
디자인 패턴에 람다 표현식이 더해지면 색다른 기능을 발휘할 수 있다. 즉, 람다를 이용하면 이전에 디자인 패턴으로 해결하던 문제를 더 쉽고 간단하게 해결할 수 있다. 또한 람다 표현식으로 기존의 많은 객체지향 디자인 패턴을 제거하거나 간결하게 재구현할 수 있다.
람다 미적분학(Lambda Calculus)?
수학과 컴퓨터 과학에서 함수와 계산을 표현하고 연구하는 데 사용되는 형식 체계(formal system)입니다. 알론조 처치(Alonzo Church)가 1930년대에 개발했으며, 프로그래밍 언어 이론과 함수형 프로그래밍의 기초가 되는 이론입니다.
1급 객체(First-Class Citizen)*
사회학적 배경
- 1급 객체 : 1급 객체(First-Class Citizen)라는 표현은 원래 법률 및 사회학적인 맥락에서 나온 용어로, 특정 대상이 동등한 권리와 특권을 가지는 것을 의미합니다.
예를 들어, 민주주의 사회에서는 모든 시민이 법 앞에서 평등하다는 개념을 의미하는데, "1급 시민"이라는 용어는 이런 평등을 강조하는 데 사용되었습니다.
결과적으로 함수를 1급 객체로 취급하면 유연성, 재사용성, 추상화 수준이 높아집니다. 함수가 변수처럼 자유롭게 전달되고 반환될 수 있기 때문에, 복잡한 계산이나 데이터 흐름을 더 간단하고 선언적으로 표현할 수 있습니다.
람다란 무엇인가?
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다. 람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.
람다 표현식은 파라미터, 화살표, 바디로 이루어진다.
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
사용 사례 | 람다 예제 |
---|---|
불리언 표현식 | (List list) → list.isEmpty() |
객체 생성 | () → new Apple(10) |
객체에서 소비 | (Apple a) → { System.out.println(a.getWeight)); |
객체에서 선택/추출 | (String s) → s.length() |
두 값을 조합 | (int a, int b) → a * b |
함수형 인터페이스(Functional Interface)
하나의 추상 메서드를 가지는 인터페이스를 의미합니다.
@FunctionalInterface
interface Calculator {
int calculate(int x, int y);
}
Calculator add = (x, y) -> x + y;
System.out.println(add.calculate(5, 3)); // 출력: 8
자바에서는 왜 함수형 인터페이스가 필요한가?
- Java의 객체 중심 특성
- Java는 철저히 객체 지향 언어로 설계되었습니다. 모든 메서드는 클래스나 객체의 일부로만 존재할 수 있습니다.
- 람다식은 함수형 프로그래밍의 핵심 요소로, 함수 자체를 전달하거나 처리해야 합니다.
- 그러나 Java에서는 함수 자체를 일급 객체로 지원하지 않기 때문에, 람다식을 객체 지향 모델과 통합할 방법이 필요했습니다.
- 인터페이스를 통한 함수 표현
- Java 8에서는 람다식을 사용하여 함수를 표현하기 위해, 메서드 하나만 가진 인터페이스(즉, 함수형 인터페이스)를 사용했습니다.
- 이 인터페이스를 사용하면, 람다식을 해당 인터페이스의 구현체로 취급할 수 있습니다. 이렇게 하면 Java의 기존 객체 지향 모델과 호환성을 유지할 수 있습니다.
자바에서 제공하는 기본 함수형 인터페이스
[Java] Lazy Evaluation (지연 연산)
java vs javascript 함수형 인터페이스 비교
람다로 객체지향 디자인 패턴 리팩터링하기
전략(Strategy)
https://ko.wikipedia.org/wiki/%EC%A0%84%EB%9E%B5_%ED%8C%A8%ED%84%B4
Legacy 예제
public interface ValidationStrategy {
boolean execute(String s);
}
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class IsNumeric implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("\\d+");
}
}
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v) {
this.strategy = v;
}
public boolean validate(String s){
return strategy.execute(s);
}
}
public class LegacyMain {
public static void main(String[] args) {
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaa");
System.out.println(b1);
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("hello");
System.out.println(b2);
}
}
Lambda로 리팩터링 예제
public class LambdaMain {
public static void main(String[] args) {
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaaa");
System.out.println(b1);
Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb");
System.out.println(b2);
}
}
- ValidationStrategy 는 함수형 인터페이스며 Predicate과 같은 함수 디스크립터를 갖고 있음을 파악할 수 있음.
템플릿 메서드(Template Method)
Legacy 예제
public class Customer {
private int id;
private String name;
public Customer(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
}
public class Database {
private static final Map<Integer, String> TEMP_DATA = new HashMap<>();
static {
TEMP_DATA.put(1, "철수");
TEMP_DATA.put(2, "영희");
TEMP_DATA.put(3, "민수");
TEMP_DATA.put(4, "지영");
}
public static Customer getCustomerWithId(int id) throws Exception {
if (TEMP_DATA.get(id) == null) {
throw new Exception("db 에 해당 값 없음");
}
return new Customer(id, TEMP_DATA.get(id));
}
}
interface OnlineBanking {
default void processCustomer(int id) throws Exception {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
void makeCustomerHappy(Customer c);
}
public class Kbank implements OnlineBanking{
@Override
public void makeCustomerHappy(Customer c) {
System.out.println(c.getName() + "케이뱅크 오버라이딩");
}
}
public class NH implements OnlineBanking{
@Override
public void makeCustomerHappy(Customer c) {
System.out.println(c.getName() + "농협 오버라이딩");
}
}
public class LagacyMain {
public static void main(String[] args) throws Exception {
OnlineBanking kBanking = new Kbank();
kBanking.processCustomer(1);
kBanking.processCustomer(2);
OnlineBanking nhBanking = new NH();
nhBanking.processCustomer(1);
nhBanking.processCustomer(2);
}
}
Lambda로 리팩터링 예제
public class OnlineBankingLambda {
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) throws Exception {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
}
public class LambdaMain {
public static void main(String[] args) throws Exception {
new OnlineBankingLambda().processCustomer(1, (Customer c) -> {
System.out.println(c.getName() + "케이뱅크 오버라이딩");
});
new OnlineBankingLambda().processCustomer(2, (Customer c) -> {
System.out.println(c.getName() + "케이뱅크 오버라이딩");
});
new OnlineBankingLambda().processCustomer(1, (Customer c) -> {
System.out.println(c.getName() + "농협 오버라이딩");
});
new OnlineBankingLambda().processCustomer(2, (Customer c) -> {
System.out.println(c.getName() + "농협 오버라이딩");
});
}
}
옵저버(Observer)
https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4
Legacy 예제
public interface Observer {
void notify(String tweet);
}
public class Guardian implements Observer{
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet more news from London..." + tweet);
}
}
}
public class LeMonde implements Observer{
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine and news!" + tweet);
}
}
}
public class NYTimes implements Observer {
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY!" + tweet);
}
}
}
public interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
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) {
observers.forEach(o -> o.notify(tweet));
}
}
public class LagacyMain {
public static void main(String[] args) {
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("The queen said her favourite book is Modern Java in Action!");
}
}
Lambda로 리팩터링 예제
public class LambdaMain {
public static void main(String[] args) {
Feed f = new Feed();
f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY!" + tweet);
}
});
f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet more news from London..." + tweet);
}
});
f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine and news!" + tweet);
}
});
f.notifyObservers("The queen said her favourite book is Modern Java in Action!");
}
}
의무 체인(Chain of Responsibility), 책임 연쇄
https://ko.wikipedia.org/wiki/%EC%B1%85%EC%9E%84_%EC%97%B0%EC%87%84_%ED%8C%A8%ED%84%B4
Legacy 예제
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);
}
public class HeaderTextProcessing extends ProcessingObject<String> {
@Override
protected String handleWork(String text) {
return "From Raoul, Mario and Alan : "+ text;
}
}
public class SpellCheckerProcessing extends ProcessingObject<String>{
@Override
protected String handleWork(String text) {
return text.replaceAll("labda", "lambda");
}
}
public class LagacyMain {
public static void main(String[] args) {
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2); //두 작업 처리 객체를 연결
String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
Lambda로 리팩터링 예제
public class LambdaMain {
public static void main(String[] args) {
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);
String result = pipeline.apply("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
팩토리(Factory)
Legacy 예제
public class Product {
String name;
}
public class Loan extends Product {
}
public class Bond extends Product {
}
public class Stock extends Product {
}
public class ProductFactory {
public static Product createProduct(String name){
return switch (name) {
case "loan" -> new Loan();
case "stock" -> new Stock();
case "bond" -> new Bond();
default -> throw new RuntimeException("No such product" + name);
};
}
}
public class LagacyMain {
public static void main(String[] args) {
Product p = ProductFactory.createProduct("loan");
System.out.println(p.getClass());
}
}
Lambda로 리팩터링 예제
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);
}
}
public class LambdaMain {
public static void main(String[] args) {
Product p = LambdaProductFactory.createProduct("loan");
System.out.println(p.getClass());
}
}
참고자료
https://www.yes24.com/Product/Goods/77125987?pid=123487&cosemkid=go15646485055614872&utm_source=google_pc&utm_medium=cpc&utm_campaign=book_pc&utm_content=ys_240530_google_pc_cc_book_pc_11906%EB%8F%84%EC%84%9C&utm_term=%EB%AA%A8%EB%8D%98%EC%9E%90%EB%B0%94%EC%9D%B8%EC%95%A1%EC%85%98&gad_source=1&gclid=Cj0KCQiAkJO8BhCGARIsAMkswyhABf_O0DVcohWq0DPlyqkn9RKmtvRNYz_zEfI8vbG7c7jJEJh8-UkaAoF5EALw_wcB
https://m.blog.naver.com/zzang9ha/222087025042
https://velog.io/@minseojo/Java-Lazy-Evaluation-%EC%A7%80%EC%97%B0-%EC%97%B0%EC%82%B0
'[개발관련] > 디자인패턴' 카테고리의 다른 글
[디자인패턴] 로그 조회 API에 전략(Strategy) 패턴 적용하기 (0) | 2023.08.29 |
---|