[Java] 함수형 프로그래밍 디자인 패턴
Design Pattern
- 반복해서 등장하는 프로그래밍 문제들에 대한 해법들을 패턴화 해놓은 것
- 패턴들을 숙지해놓으면 비슷한 문제가 생겼을 때, 이정표가 됨
- 종류
- 생성 패턴 (Creational Pattern)
- 오브젝트 생성에 관련된 패턴
- 구조 패턴
- 상속을 이용해 클래스/오브젝트를 조합하여 더 발전된 구조로 만드는 패턴
- 행동 패턴
- 필요한 작업을 여러 객체에 분배하여 객체간 결합도를 줄이게 해주는 패턴
- 생성 패턴 (Creational Pattern)
- 디자인 패턴 구현에 많은 인터페이스/클래스/메서드를 필요로 함
- 함수형 프로그래밍을 이용해 몇몇 패턴들을 간소화
빌더 패턴
- 객체의 생성에 대한 로직과 표현에 대한 로직을 분리해줌
- 객체의 생성 과정을 정의하고 싶거나 필드가 많아 생성자가 복잡해질 때 유용
- ex) setter
- setter를 없애 객체가 바뀌지 않음을 보장 (immutable)
- 대신 생성자의 파라미터가 늘어남
- 생성자 호출 시 순서 복잡
- Builder를 이용해 생성하도록 함
- Builder도 결국 많아지면 복잡해짐
- 함수형 프로그래밍으로 개선
개선 구조 (Functional)
public static class Builder { private int id; private String name; private Builder() { } public Builder with(Consumer<Builder> consumer) { consumer.accept(this); return this; } public Object build() { return new Object(this); } }
public class BuilderExample { public static void main(String[] args) { Object obj = Object.builder() .with(builder -> { builder.id = 1; builder.name = "functional builder"; }).build(); } }
데코레이터 패턴
- 구조 패턴의 하나
- 용도에 따라 객체에 기능을 계속 추가(decorate)할 수 있게 된다
- 기존 구조
- 메서드를 새로 정의할 필요 없이 Price를 구현한 객체를 입력받아 상황에 맞게 골라서 적용 가능
- 이 경우 PriceProcessor가 모든 기능마다 생겨서 비효율적
- PriceProcessor를 만들지 않고 함수형으로 정의하여 바로 적용할 수 있음
public class Price { private final String price public Price(String price) { this.price = price; } public String getPrice() { return price; } }
// Functional Interface (메소드 하나) public interface PriceProcessor { Price process(Price price); // 다음에 실행될 프로세서를 받아와서 기존 프로세서를 처리하고 next 프로세서를 처리해서 새로운 Price를 만들어줌 default PriceProcessor andThen(PriceProcessor next) { return price -> next.process(process(price)); } }
전략 패턴
- 대표적인 행동 패턴
- 런타임에 어떤 전략(알고리즘)을 사용할 지 선택할 수 있게 해줌
- 전략들을 캡슐화하여 간단하게 교체 가능
사전 정의
-
public interface Provider { String getName(Object object); } public class Sender { private Provider provider; public Sender setProvider(Provider provider) { this.provider = provider; return this; } public void sendName(Object object) { String name = Provider.getName(object); } } public class ProviderImpl1 { @Override public String getName(Object object) { return "imple type1 " + object.getName(); }; } public class ProviderImpl2 { @Override public String getName(Object object) { return "imple type2 " + object.getName(); }; }
전략 선택 비교
public class MainClass { public static void main(String[] args) { Object obj1 = Object.builder("name1").build(); Object obj2 = Object.builder("name2").build(); Object obj3 = Object.builder("name3").build(); List<Object> objs = Arrays.asList(obj1, obj2, obj3); Sender sender = new Sender(); Provider provider1 = new ProviderImpl1(); Provider provider2 = new ProviderImpl2(); // 전략 선택 1 sender.setProvider(provider1); objs.stream() .forEach(sender::sendName); // 전략 선택 2 sender.setProvider(provider2); objs.stream() .forEach(sender::sendName); // 함수형 프로그래밍 개선 (바로 바로 생성해 사용 가능) sender.setProvider(object -> "functional " + object.getName()).stream() .forEach(sender::sendName); }; }
템플릿 메소드 패턴
- 대표 행동 패턴
- 상위 클래스는 알고리즘의 뼈대만을 정의하고 알고리즘의 각 단계는 하위 클래스에게 정의를 위임하는 패턴
- 새로운 클래스를 계속 만들어야함
- 알고리즘의 구조를 변경하지 않고 세부 단계들을 유연하게 변경 가능
기존 구조
-
public abstract class AbstractService { protected abstract boolean validateObject(Object object); protected abstract void writeToDB(Object object); public void createObject(Object object) { if(validateObject(object)) { writeToDB(object); } else { System.out.println("Cannot create"); } } } public class Service extends AbstractService { @Override protected abstract boolean validateObject(Object object) { return object.getName() != null && user.getFields().isPresent(); } @Override protected abstract void writeToDB(Object object) { System.out.println("write " + object.getName() + " to DB"); } } // 내부 서비스 public class InternalService extends AbstractService { @Override protected abstract boolean validateObject(Object object) { return true; } @Override protected abstract void writeToDB(Object object) { System.out.println("write " + object.getName() + " to internal DB"); } } public class MainClass { public static void main(String[] args) { Object object = Object.builder("name") .with(builder -> { builder.fields = "fields"; }).build(); } Service service = new Service(); InternalService internalService = new InternalService(); service.createUser(object); internalService.createUser(object); }
개선 구조 (Functional)
public class FunctionalService extends AbstractService { private final Predicate<Object> validateObject; private final Consumer<Object> writeToDB; public FunctionalService(Predicate<Object> validateObject, Consumer<Object> writeToDB) { this.validateObject = validateObject; this.writeToDB = writeToDB; } public void createObject(Object object) { if(validateObject.test(object)) { writeToDB.accept(object); } else { System.out.println("Cannot create"); } } } public class MainClass { public static void main(String[] args) { Object object = Object.builder("name") .with(builder -> { builder.fields = "fields"; }).build(); } FunctionalService fService = new FunctionalService( object -> { return object.getName().equal("functional"); }, object -> { System.out.println("functional writeToDB " + object.getName()); } ) fService.createObject(object); }
책임 연쇄 패턴 (Chain of Responsibility Pattern)
- 행동 패턴
- 명령과 명령을 각각의 방법으로 처리할 수 있는 처리 객체들이 있을 경우
- 처리 객체를 체인으로 엮음
- 체인의 앞부터 하나씩 처리
- 처리 가능하면 처리하고 넘김
- 불가능하면 바로 넘김
- 체인의 끝이면 종료
- 처리 객체를 추가하는 식으로 간단하게 처리 기능 추가 가능
- 링크드 리스트를 생각하면 쉬움
정의
public Class OrderProcessStep { private final Consumer<Order> processOrder; private OrderProcessStep next; public OrderProcessStep(Consumer<Order> processOrder) { this.processOrder = processOrder; } public OrderProcessStep setNext(OrderProcessStep next) { if (this.next == null) { this.next = next; } else { this.next.setNext(next); } return this; } public void process(Order order) { processOrder.accept(order); Optional.ofNullable(next) .ifPresent(nextStep -> nextStep.process(order)); } }
각 스텝 구현
public class MainClass { public static void main(String[] args) { OrderProcessStep initializeStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.CREATED) { logger.info("Start processing order " + order.getId()); order.setStatus(OrderStatus.IN_PROGRESS); } }); OrderProcessStep setOrderAmountStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.IN_PROGRESS) { logger.info("Setting amount of order " + order.getId()); order.setAmount( order.getOrderLines().stream() .map(OrderLine::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add) ); } } OrderProcessStep verifyOrderStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.IN_PROGRESS) { logger.info("Verifying order " + order.getId()); if(order.getAmount().compareTo(BigDecimal.ZERO) <= 0) { order.setStatus(OrderStatus.ERROR); } } } OrderProcessStep paymentStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.IN_PROGRESS) { logger.info("payment of order " + order.getId()); order.setStatus(OrderStatus.PROCESSED); } } OrderProcessStep handleErrorStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.ERROR) { logger.info("Error in order " + order.getId()); } } OrderProcessStep completeOrderStep = new OrderProcessStep(order -> { if(order.getStatus() == OrderStatus.PROCESSED) { logger.info("finished processing order " + order.getId()); } } // 체이닝 OrderProcessStep chainedOrderProcessSteps = initializeStep .setNext(setOrderAmountStep) .setNext(verifyOrderStep) .setNext(paymentStep) .setNext(handleErrorStep) .setNext(completeOrderStep); // 체인 실행 chainedOrderProcessSteps.process(order); } }
Author And Source
이 문제에 관하여([Java] 함수형 프로그래밍 디자인 패턴), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@fortice/Java-함수형-프로그래밍-디자인-패턴저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)