반응형

람다로 객체지향 디자인 패턴 리팩터링하기-지선학

디자인 패턴에 람다 표현식이 더해지면 색다른 기능을 발휘할 수 있다. 즉, 람다를 이용하면 이전에 디자인 패턴으로 해결하던 문제를 더 쉽고 간단하게 해결할 수 있다. 또한 람다 표현식으로 기존의 많은 객체지향 디자인 패턴을 제거하거나 간결하게 재구현할 수 있다.

람다 미적분학(Lambda Calculus)?

수학과 컴퓨터 과학에서 함수와 계산을 표현하고 연구하는 데 사용되는 형식 체계(formal system)입니다. 알론조 처치(Alonzo Church)가 1930년대에 개발했으며, 프로그래밍 언어 이론함수형 프로그래밍의 기초가 되는 이론입니다.

1급 객체(First-Class Citizen)*

사회학적 배경

  • 1급 객체 : 1급 객체(First-Class Citizen)라는 표현은 원래 법률 및 사회학적인 맥락에서 나온 용어로, 특정 대상이 동등한 권리와 특권을 가지는 것을 의미합니다.

예를 들어, 민주주의 사회에서는 모든 시민이 법 앞에서 평등하다는 개념을 의미하는데, "1급 시민"이라는 용어는 이런 평등을 강조하는 데 사용되었습니다.

결과적으로 함수를 1급 객체로 취급하면 유연성, 재사용성, 추상화 수준이 높아집니다. 함수가 변수처럼 자유롭게 전달되고 반환될 수 있기 때문에, 복잡한 계산이나 데이터 흐름을 더 간단하고 선언적으로 표현할 수 있습니다.

Lambda calculus

 

Lambda calculus - Wikipedia

From Wikipedia, the free encyclopedia Mathematical-logic system based on functions Lambda calculus (also written as λ-calculus) is a formal system in mathematical logic for expressing computation based on function abstraction and application using variabl

en.wikipedia.org

 

람다란 무엇인가?

람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다. 람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.

람다 표현식은 파라미터, 화살표, 바디로 이루어진다.

(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

자바에서는 왜 함수형 인터페이스가 필요한가?

  1. Java의 객체 중심 특성
    • Java는 철저히 객체 지향 언어로 설계되었습니다. 모든 메서드는 클래스나 객체의 일부로만 존재할 수 있습니다.
    • 람다식은 함수형 프로그래밍의 핵심 요소로, 함수 자체를 전달하거나 처리해야 합니다.
    • 그러나 Java에서는 함수 자체를 일급 객체로 지원하지 않기 때문에, 람다식을 객체 지향 모델과 통합할 방법이 필요했습니다.
  2. 인터페이스를 통한 함수 표현
    • Java 8에서는 람다식을 사용하여 함수를 표현하기 위해, 메서드 하나만 가진 인터페이스(즉, 함수형 인터페이스)를 사용했습니다.
    • 이 인터페이스를 사용하면, 람다식을 해당 인터페이스의 구현체로 취급할 수 있습니다. 이렇게 하면 Java의 기존 객체 지향 모델과 호환성을 유지할 수 있습니다.

자바에서 제공하는 기본 함수형 인터페이스

[Java] Lazy Evaluation (지연 연산)

 

[Java] Lazy Evaluation (지연 연산)

Lazy Evaluation는 불필요한 연산을 피하기 위해 연산을 지연 시켜 놓았다가 필요할 때 연산하는 방법이다.

velog.io

 

java vs javascript 함수형 인터페이스 비교

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

전략(Strategy)

https://ko.wikipedia.org/wiki/%EC%A0%84%EB%9E%B5_%ED%8C%A8%ED%84%B4

 

전략 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은 특정한 계열

ko.wikipedia.org

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)

https://ko.wikipedia.org/wiki/%ED%85%9C%ED%94%8C%EB%A6%BF_%EB%A9%94%EC%86%8C%EB%93%9C_%ED%8C%A8%ED%84%B4

 

템플릿 메소드 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 템플릿 메소드 패턴(template method pattern)은 소프트웨어 공학에서 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴이다.[1] 알고리즘의 구조를

ko.wikipedia.org

 

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

 

옵서버 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체

ko.wikipedia.org

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

 

책임 연쇄 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 객체 지향 디자인에서 책임 연쇄 패턴(chain-of-responsibility pattern)은 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를

ko.wikipedia.org

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)

https://ko.wikipedia.org/wiki/%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C_%ED%8C%A8%ED%84%B4

 

팩토리 메서드 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. UML로 표현된 팩토리 메서드 LePUS3로 표현된 팩토리 메서드 팩토리 메서드 패턴(Factory method pattern)은 객체지향 디자인 패턴이다. Factory method는 부모(상위) 클래스

ko.wikipedia.org

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

 

모던 자바 인 액션 - 예스24

자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능과 문법, 디자인 패턴

www.yes24.com

https://m.blog.naver.com/zzang9ha/222087025042

 

[Java/자바] - Supplier<T> interface

Supplier<T> interface 안녕하세요, 이번시간에 알아볼 함수형 인터페이스는 Supplier<T>...

blog.naver.com

https://velog.io/@minseojo/Java-Lazy-Evaluation-%EC%A7%80%EC%97%B0-%EC%97%B0%EC%82%B0

 

[Java] Lazy Evaluation (지연 연산)

Lazy Evaluation는 불필요한 연산을 피하기 위해 연산을 지연 시켜 놓았다가 필요할 때 연산하는 방법이다.

velog.io

 

728x90
반응형

전략 패턴이란?

하나의 메시지와 책임을 정의하고, 이를 수행할 수 있는 다양한 전략을 만든 후, 다형성을 통해 전략을 선택해 구현을 실행하는 패턴.

전략 패턴은 GoF의 디자인패턴 중에서 행위 패턴 중에 하나에 해당함

전략 패턴 UML

Spring에서 사용하는 전략패턴 예시

package com.ji.behavioral_patterns.strategy.java;

import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;

public class StrategyInSpring {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext();
        ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext();
        ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext();

        BeanDefinitionParser parser;

        PlatformTransactionManager platformTransactionManager;

        CacheManager cacheManager;

    }
}

자바에서 사용하는 전략 패턴

https://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns-in-javas-core-libraries/2707195#2707195

 

Examples of GoF Design Patterns in Java's core libraries

I am learning GoF Java Design Patterns and I want to see some real life examples of them. What are some good examples of these Design Patterns in Java's core libraries?

stackoverflow.com

 

배경

전략 패턴의 핵심은 Context를 기반으로 해당 개발자가 필요에 따라서 원하는 기능을 다형성에 의해서 원하는 전략을 선택한 것에 있음

현재 코드에서 Context가 로그 조회 기능이고 요청에 따라서 구현체가 선택되어 기능이 동작하게 구현하면 되기 때문에 전략 패턴 적용하기에 용이하다고 판단함

전략 패턴 적용 전 예시

전략 패턴 적용 전 레거시 코드

각 API 별로 각 요청에 해당하는 메서드를 가져와서 return하는 구조였음

@ApiOperation(value = "계정 관련 로그")
@PostMapping("/list/account")
public ResultVO getAccountLog(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/account] :" + vo.toString());
    SearchResultVO result;

    try {

        result = logService.getAccountLogList(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "계정 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "계정 로그 조회가 완료되었습니다.", result);

}

@ApiOperation(value = "파일 보호 이벤트 로그")
@PostMapping("/list/file-protect")
public ResultVO getFileProtect(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/file-protect] :" + vo.toString());
    SearchResultVO result;

    try {
        result = logService.getClientFileProtectLog(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "파일 보호 이벤트 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "파일 보호 이벤트 로그 조회가 완료되었습니다..", result);

}

@ApiOperation(value = "서버 상태 로그")
@PostMapping("/list/server-status")
public ResultVO getServerStatusLog(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/server-status] :" + vo.toString());
    SearchResultVO result;

    try {

        result = logService.getServerStatusLog(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "서버 상태 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "서버 상태 로그 조회가 완료되었습니다..", result);

}
public interface LogService {

	public SearchResultVO getAccountLogList(SearchListVO vo) throws Exception;

	public SearchResultVO getClientFileProtectLog(SearchListVO vo) throws Exception;

	public SearchResultVO getServerStatusLog(SearchListVO vo) throws Exception;

}
@Override
public SearchResultVO getAccountLogList(SearchListVO vo) throws Exception {
	List<AccountLogDto> result = new ArrayList<AccountLogDto>();
	
	SearchResultVO daoVO = logDao.getAccoutLogList(vo);
	
	//date 포맷 변경
	List<AccountLogDto> searchedList = (List<AccountLogDto>) daoVO.getSearchedList();
	for(AccountLogDto dto : searchedList) {
		dto.set_logTime(DateUtils.parseDateFormatHHMMSSss(dto.getLogTime()));
		result.add(dto);
	}
	
	return new SearchResultVO(daoVO.getTotal(), result);
}

 

위 구조의 레거시 코드를 전략패턴을 적용하여 리팩터링 하였음.

전략 패턴 적용  예시

다음 UML과 같의 LogStrategyService라는 strategy 클래스를 선언하였고, 각 로그 유형에 해당하는 cocreate class가 동일한 인터페이스를 상속하여 구현하고 있음

전략 패턴 적용 후 UML

코드를 자세이 보도록 하자

strategy 클래스에 해당하는 LogStrategyService 인터페이스

public interface LogStrategyService {

    SearchResultVO getLogList(SearchListVO searchListVO) throws Exception;

    SearchResultVO getLogList(SearchListVO searchListVO, String lang) throws Exception;

    boolean isTarget(String logType);

}

ConcreateStrategy1~ 클래스에 해당하는 AccountLogServiceImpl 클래스 구현체

package com.smt.service.log;

import com.smt.dao.log.LogDao;
import com.smt.dto.AccountLogDto;
import com.smt.util.DateUtils;
import com.smt.util.enums.LogType;
import com.smt.vo.common.SearchListVO;
import com.smt.vo.common.SearchResultVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class AccountLogServiceImpl implements LogStrategyService {

   private final LogDao logDao;

    @Override
    public SearchResultVO getLogList(SearchListVO searchListVO) throws Exception {
        List<AccountLogDto> result = new ArrayList<AccountLogDto>();

        SearchResultVO daoVO = logDao.getAccoutLogList(searchListVO);

        //date 포맷 변경
        List<AccountLogDto> searchedList = (List<AccountLogDto>) daoVO.getSearchedList();
        for (AccountLogDto dto : searchedList) {
            dto.set_logTime(DateUtils.parseDateFormatHHMMSSss(dto.getLogTime()));
            result.add(dto);
        }

        return new SearchResultVO(daoVO.getTotal(), result);
    }

    @Override
    public SearchResultVO getLogList(SearchListVO searchListVO, String lang) throws Exception {
        return null;
    }

    @Override
    public boolean isTarget(String logTypeUrl) {
        return logTypeUrl.equals(LogType.ACCOUNT.getUrl());
    }
}

Context에 해당하는 Controller 클래스

또한 다음 Controller에서 @PathVariable를 활용해서 기존에 URL에 요청하는 Controller 메서드가 동작하는 구조였지만, 하나는 Controller 메서드에서 로그 type에 해당하는 상속 클래스를 Spring에 singleton으로 주입된 해당 클래스를 찾아서 초기화 하도록 구현함에 따라서 코드를 간략하게 리팩토링 하였음.

참고로, 현재는 List를 활용해서 해당 하위 클래스를 접근하는 방식이지만, 현재는 Map과 Spring에서 제공하는 @Service("account") 속성을 활용해서 클래스를 더욱 빠른 속도로 찾을 수 있게 개선해 놓은 상태임

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/log")
@Validated
@Api(value = "LogController", description = "로그 관련(참고문서 : 구글 공유 문서 독스토리 서버 ReturnCode)")
public class LogController {

  private final LogService logService;

  private final List<LogStrategyService> logStrategyServices;

  @ApiOperation(value = "통합 로그 조회 API-정책 설정 로그 제외")
  @PostMapping("/list/{type}")
  public ResultVO getLogStrategy(@PathVariable("type") String logType, @Valid @RequestBody SearchListVO searchListVO) throws Exception {
      log.info("[/list/{type}] :" + searchListVO.toString() + "type : " + logType);

      SearchResultVO searchResult =  logStrategyServices.stream()
                                                                .filter(logService -> logService.isTarget(logType))
                                                                .findFirst()
                                                                .get()
                                                                .getLogList(searchListVO);

      return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "통합 로그 조회가 완료되었습니다.", searchResult);
  }

결론

만약 PathVariable을 이용하고 전략 패턴을 적용하지 않았다면 path Type에 따라서 if문을 통해서 해당 메서드를 호출되었을 되었을 것임

하지만, 전략 패턴을 적용함으로써 if문이 아닌 반복문(다형성)을 이용하게됨

다른 유형의 로그 유형을 구현(추가)해야할 경우 Strategy 인터페이스를 상속 받아서 ConcreateStrategy~ 클래스를 구현하면됨

즉, 전략패턴을 적용함에 따라서 SOLID의 원칙 중 OCP(Open-Closed Principle)를 준수하게 됨

(확장에 열려있고, 변경에는 닫힘)

728x90

+ Recent posts