[Java] 함수형 프로그래밍 디자인 패턴

Design 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);
        }
    }

좋은 웹페이지 즐겨찾기