design

  • OOP와 함수형 스타일 시너지
  • 설계 아이디어 변화
  • 객체 대신에 경량의 함수형 스타일, 람다식을 사용한 로직과 함수 분리
  • 델리게이트 delegate > 전략패턴 stratage, 데코레이터 패턴 decorator
* seperate of concerns 문제, 관심사, 걱정거리의 분리
  • 기본 로직, 예외 처리, 비동기 프로그래밍, 메모리 할당/해제, 인자 전달 방식, 락(lock), 이벤트 > 인간의 인지능력 한계
  • 인지 심리학자인 George A. Miller은 The Magical Number Seven, Plus or Minus Two 한 번에 처리할 수 있는 정보가 7개 정도(혹은 1,2개 더)로 제한
  • 어렵고 복잡 > 걱정거리들을 분리, 작은 문제로 나누기 - 디자인패턴, OOP 그리고 람다!

관심사의 분리 - 메소드 레벨

  • 중복, copy&paste
    //자산 ASSET 처리하기
    public static int totalAssetValues(final List<Asset> assets) {
        return assets.stream().mapToInt(Asset::getValue).sum();
    }

    //채권 BOND 만 처리하기 
    public static int totalBondValues(final List<Asset> assets) {
        return assets.stream().mapToInt(asset -> asset.getType() == AssetType.BOND ? asset.getValue() : 0).sum();
    }

    //주식 STOCK 만 처리하기
    public static int totalStockValues(final List<Asset> assets) {
        return assets.stream().mapToInt(asset -> asset.getType() == AssetType.STOCK ? asset.getValue() : 0).sum();
    }

    // 람다식을 제외한 중복 - 이터레이션, 합계
    // mapToInt()에 들어가는 람다식만 다르다.
    // 변하지 않는 부분과 변하는 부분, 변하는 부분을 관심사로 지정 > OOP 전략패턴 적용 > 람다식으로 구현


    public static void main(final String[] args) {

        final List<Asset> assets = Arrays.asList(
                new Asset(Asset.AssetType.BOND, 1000),
                new Asset(Asset.AssetType.BOND, 2000), 
                new Asset(Asset.AssetType.STOCK, 3000),
                new Asset(Asset.AssetType.STOCK, 4000)
                );

        System.out.println("Total of all assets: " + totalAssetValues(assets));
        System.out.println("Total of bonds: " + totalBondValues(assets));
        System.out.println("Total of stocks: " + totalStockValues(assets));
    }
  • 관심사의 분리, 재사용성, 경량(객체불필요)

    //람다식을 적용한 메소드(고차함수) 추가, 파라미터 Predicate 함수형 인터페이스 재사용, 람다식을 전달 받을 수 있다.
    //람다식 > 변하는 부분, 고차함수 > 안변하는 부분 , 람다식을 사용한 전략패턴
    //별도 객체 불필요
    //open/closed principal 적용

    public static int totalAssetValues(final List<Asset> assets, final Predicate<Asset> assetSelector) {
        return assets.stream().filter(assetSelector).mapToInt(Asset::getValue).sum();
    }

    ...
    System.out.println("Total of all assets: " + totalAssetValues(assets, asset -> true));
    System.out.println("Total of bonds: " + totalAssetValues(assets, asset -> asset.getType() == AssetType.BOND));
    System.out.println("Total of stocks: " + totalAssetValues(assets, asset -> asset.getType() == AssetType.STOCK));

관심사의분리 두번째 - 클래스 레벨

  • 책임져야 할 부분을 다른 클레스 보다 람다식(메서드 레퍼런스)으로 델리게이트delegation
  • 클래스의 증가를 막아줌
  • 주식 값을 리턴하는 객체를 사용한 예제

    //스트링 값을 받으면 값을 리턴하는 함수형 인터페이스
    //주식명을 입력하면 가격을 리턴
    private Function<String, BigDecimal> priceFinder;

    public BigDecimal computeStockWorth(final String ticker, final int shares) {
        return priceFinder.apply(ticker).multiply(BigDecimal.valueOf(shares));
    }
    // ... other methods that use the priceFinder ...

    //생성자, 함수형 인터페이스인 Function - priceFinder를 입력받는다.
    //Junit에서 간단한 람다식으로 입력 final  CalculateNAV calculateNAV = new CalculateNAV(ticker -> new BigDecimal("6.01"));
    //정적 메소드 레퍼런스 형태로 입력 final  CalculateNAV calculateNav = new CalculateNAV(YahooFinance::getPrice);

    public CalculateNAV(final Function<String, BigDecimal> aPriceFinder) {
        priceFinder = aPriceFinder;
    }

    public static void main(String[] args) {

        //생성자 인젝션, DI
        //YahooFinance::getPrice는 야후에 웹서비스 방식으로 주식에 대한 가격을 리턴받는 정적 메소드
        final CalculateNAV calculateNav = new CalculateNAV(YahooFinance::getPrice); //정적 메소드 레퍼런스 형태로 입력

        System.out.println(String.format("100 shares of Google worth: $%.2f", calculateNav.computeStockWorth("GOOG", 100)));
    }
}

데코레이팅 decorating

  • 행위(비헤이비어, 람다식)를 여러개를 체인으로 묶어서 사용
  • 데코레이터 패턴은 클래스와 인터페이스 계층이 복잡 > 덜 부담
  • 람다식을 사용하여 델리게이트를 조함

  • 카메라 필터(여러개)를 추가하는 예제


@SuppressWarnings("unchecked")
public class Camera {

    private Function<Color, Color> filter;

    public Color capture(final Color inputColor) {
        final Color processedColor = filter.apply(inputColor);
        // ... more processing of color...
        return processedColor;
    }

    //filter(람다식)를 여러개 입력하면 체인으로 하나로 연결한다.
    //여러 필터를 받아서 처리하도록 확장성이 높아짐
    //디폴트 메소드인 compose가 핵심코드이다. 여러 Function을 조합
    public void setFilters(final Function<Color, Color>... filters) {
        //필터가 없으면 reduce에서 리턴인 Optional이 empty를 리턴
        //orElse(color -> color);  > .orElseGet(Function::identity); 메소드 레퍼런스로 변환가능
        filter = Stream.of(filters).reduce((filter, next) -> filter.compose(next)).orElse(color -> color);
    }

    public Camera() {
        setFilters();
    }

    public static void main(final String[] args) {

        final Camera camera = new Camera();

        // 단순한 화면 출력 용도로 함수형인터페이스(Consumer)를 사용
        final Consumer<String> printCaptured = (filterInfo) -> 
            System.out.println(String.format("with %s: %s", filterInfo, camera.capture(new Color(200, 100, 200))));

        //printCaptured함수를 accept 메소드로 실행함

        //필터없음
        printCaptured.accept("no filter");

        camera.setFilters(Color::brighter);
        printCaptured.accept("brighter filter");

        camera.setFilters(Color::darker);
        printCaptured.accept("darker filter");

        //2개의 필터가 하나로 연결된다.
        camera.setFilters(Color::brighter, Color::darker);
        printCaptured.accept("brighter & darker filter");
    }

외부 인터페이스 만들기

  • 람다식을 활용한 외부 인터페이스 만들기

  • 기존 방법


     //메일 송신 객체 API를 사용

    public class Mailer {

    public void from(final String address) {/* ... */ }
    public void to(final String address) {/* ... */ }
    public void subject(final String line) {/* ... */ }
    public void body(final String message) {/* ... */ }
    public void send() {System.out.println("sending...");

    }

    // 메일 송신 객체 API를 통한 생성
    // 반복 mailer 레퍼런스, 모호성 send()으로 인해 API를 꼼꼼이 살펴야 함 > 직관성 필요
    // 명확하지 않는 객체의 라이프타임

    Mailer mailer = new Mailer();
    mailer.from("[email protected]");
    mailer.to("[email protected]");
    mailer.subject("build notification");
    mailer.body("...your code sucks...");
    mailer.send();

 - 메서드 체인 method chaining, 캐스케이드 메서드 패턴 cascade method pattern 

    //void 대신에 this를 리턴한다.
        public MailBuilder from(final String address) { /*... */; return this; }
    public MailBuilder to(final String address)   { /*... */; return this; }
    public MailBuilder subject(final String line) { /*... */; return this; }
    public MailBuilder body(final String message) { /*... */; return this; }
    public void send() { System.out.println("sending..."); }

    // 메소드 체이닝 패턴으로 작성한 코드
    public static void main(final String[] args) {
        new MailBuilder() // new 키워드가 눈에 가시...
                    .from("[email protected]") //한 번씩만 호출됨을 보장했으면...
            .to("[email protected]") 
            .subject("build notification")
            .body("...it sucks less...")
            .send();
    }
  • 람다식을 활용

  //생성자를 통한 직접 생성 불가로 제한
  private FluentMailer() {}

  //메소드 체이닝 패턴 유지
    public FluentMailer from(final String address) { /*... */; return this; }
    public FluentMailer to(final String address)   { /*... */; return this; }
    public FluentMailer subject(final String line) { /*... */; return this; }
    public FluentMailer body(final String message) { /*... */; return this; }

    // FluentMailer의 생성을 send 메소드 안에서 관리함 > 내부에서 인스턴스를 제어함
            // 함수형 인터페이스 Consumer를 사용
    public static void send(final Consumer<FluentMailer> block) {
         //인스턴스의 생성 > 리턴 후 gc됨 > 론 패턴 loan pattern (객체 라이프타임 관리)
        final FluentMailer mailer = new FluentMailer();
        block.accept(mailer); // 람다식을 수행
        System.out.println("sending...");
    }

    ...

    //사용자는 인스턴스 생성을 직접 하지 않음
    // 람다식(코드 블록)을 넘김
    FluentMailer.send(mailer -> mailer.from("[email protected]")
                                             .to("[email protected]")
                                             .subject("build notification")
                                             .body("...much better...")
                                             );
    }
}

정리

  • 람다식은 경량화 설계툴
  • 많고 복잡한 인터페이스와 클래스 구조 > 람다식/함수형 인터페이스로 경량화
  • 쉬운 전략 패턴, 데코레이터 패턴 구현
  • 단, 람다식에서의 예외 처리 주의!

results matching ""

    No results matching ""