우아한형제들/우아한테크코스

[우아한테크코스] 3주차 후기 - if 문 없이 개발하기

Jay22 2019. 5. 27. 16:05
반응형

우아한테크코스 3주차 후기

날짜 TODO 추가 공부
5/20 (월) 사다리 타기 리뷰 -
5/21 (화) 강의 (Git, ), 페어 프로그래밍 (좌표 계산기) 책-객체지향과 디자인패턴
5/22 (수) 페어 프로그래밍 (좌표 계산기)  
5/23 (목) 페어 프로그래밍 (좌표 계산기) 함수형 인터페이스
5/24 (금) 공부 (책-객체지향과디자인패턴), 강의 (페어프로그래밍 회고) 팩토리패턴
5/25 (토) 캡스톤 개발 -
5/26 (일) 피드백 (좌표 계산기) -

팩토리 패턴

이번 주차 실습과 과제에서는 팩토리 패턴을 이용하는 부분들이 자주 등장하였다.

팩토리 패턴이란?

어떤 클래스의 인스턴스를 만들지 서브클래스에서 결정하는 패턴

조건에 따른 객체 생성 부분을 자신이 직접하지 않고 팩토리 클래스에 위임하여 객체를 생성하는 방법이라고 할 수 있다. 이렇게 되면 객체간 결합도가 낮아져서 유지보수에 좋다.

private static final int LINE_VERTEX = 2;
private static final int TRIANGLE_VERTEX = 3;
private static final int RECTANGLE_VERTEX = 4;

public static Figure createFigure(Points points) {
    int size = points.getSize();

    if (size == LINE_VERTEX) {
        return new Line(points);
    }
    if (size == TRIANGLE_VERTEX) {
        return new Triangle(points);
    }
    if (size == RECTANGLE_VERTEX) {
        return new Rectangle(points);
    }
    throw new IllegalArgumentException("Points 형식이 잘못 되었습니다");
}

정의해본 팩토리 패턴이다. Points는 좌표의 배열을 가진 객체이다.

public class Points {
    private final List<Point> points;

    public Points(List<Point> points) {
        checkPointsIsDuplicate(points);
        this.points = points;
    }

    private void checkPointsIsDuplicate(List<Point> points) {
        Set<Point> pointSet = new HashSet<>(points);
        if (pointSet.size() != points.size()) {
            throw new IllegalArgumentException("중복된 좌표는 입력할 수 없습니다");
        }
    }
    // ... 중략
}

위 팩토리 패턴의 문제점

여러 중첩된 if 문으로 인한 보기 싫음.

Java 8 부터 사용되는 함수형 인터페이스를 사용하여 간결하게 리팩토링 할 수 있다. 람다식에 메서드 또는 생성자의 매개타입으로 사용된다.

패키지는 java.util.function에 있다.

함수형 인터페이스

Consumer

매개값이 있고 리턴 값이 없다.

Consumer<T> 
void accept(T t) // 객체 T를 받아서 소비한다

BiConsumer<T, U>
void accet(T t, U u) // 객체 T와 U를 받아서 소비한다

IntConsumer
void accapt(int value) // int 값을 받아서 소비한다

Supplier

매개 변수는 없고 리턴값이 있다. 단순 공급자의 역할을 한다.

Supplier<T>
T get() // T 객체를 리턴한다

BooleanSupplier
boolean getAsBoolean() // boolean 값을 리턴한다

IntSupplier
int getAsInt() // int 값을 리턴한다

Function

매개 변수와 리턴값이 둘 다 존재한다.

Function<T, R>
R apply(T t) // 객체 T를 객체 R로 매핑한다

BiFunction<T,U,R> 
R apply(T t, U u) // 객체 T와 U를 객체 R로 매핑한다

IntFunction<R>
R apply(int value) // int를 객체 R로 매핑한다

Predicate

매개 변수가 있고 boolean을 리턴한다.

Predicate<T>
boolean test(T t) // 객체 T를 판별

BiPredicate<T, U> 
boolean test(T t, U u) // 객체 T와 U를 판별

팩토리 클래스 리팩토링

기존의 if 문으로 도배된 팩토리 클래스를 리팩토링 한다.

 

Function의 사용

 

이런 식으로 Function을 정의한다. Function을 쓰는 이유는 Figure (도형) 은 Points (좌표의 집합) 의 갯수별로 각각 초기화 되어야 하기 때문이다. 예를들어 좌표가 2개라면 선 (Line), 3개라면 삼각형 (Triangle) 등등.

저런식으로 Map의 두 번째 인자 (Value) 에 Function 인터페이스를 넣어 준다. 그리고 static에 put을 하여 원하는 동작을 정의한다.

map.put(2, (Points points) -> new Line(points));

아까 위에서 Function<T, R>은 T를 이용하여 R을 반환하는 인터페이스라고 하였다. 즉 T 인 인풋이 필요한데 이 인풋은 Point이다. 그래서 Points를 받는 람다식을 정의하고 결과값은 Line이 초기화 되는 내용이다. Line은 좌표집합을 생성자 인자로 받는다.

 

Function으로 if 문 없애기

 

완성된 모습!

map.put(2, Line::new);

위의 코드는 아래와같이 줄여쓸 수 있다. points가 알아서 Line의 생성자로 들어가게 추론되는데 이것은 내가 Function인터페이스로 정의했기 때문이다. 생성자 인수가 두 개 라면 BiFunction을 쓰면 되겠다.

그리고 getShape 함수를 외부로 노출 시켜준다. 그러면 포인트의 집합인 Points를 받게 되면 맵에서 원하는 도형을 꺼낼 수 가 있다. 저 apply를 해줘야 반환형인 Figure가 나오게 된다!

이로써 if 문 없이 팩토리 패턴을 구현하였다.

강의

팩토리 패턴의 이유에 대해 다시 언급하셨다. 기본적으로 외부에서 new를 하는 생성자의 단점은 생성자의 인수가 여러개일 때 밖에서 의미를 알기 힘들다. 그래서 각각의 생성자를 구분하기 위해서 팩토리 메서드를 사용하면 유용하다. 그렇게 되면 생성자는 private이고 객체 생성 메서드만 public으로 노출시킨다.

 

cf> 팩토리 메서드에서 이름 컨벤션 (of 등등) create, getInstance를 많이 쓰는데 기니까 of처럼 그냥 짧게 쓴 것

 

인터페이스 사용: 인퍼테이스만 노출시키는 기법은 엔진의 버전을 교체할 때 유용하다. 기존 것을 건드리지 않고 새로 만들 수 있다. 새로운 구현체를 만들 때 외부에서 어떠한 변경이 없다. 다시 말해 밖에서 인터페이스의 구현체를 몰라도 된다. 그렇게 되면 테스트 코드를 작성할 때도 인터페이스의 public 메서드를 통해서 테스트를 진행하면 된다.

If문 제거하는 두 번째 방법 - Enum

위에 언급한 것 처럼 조건문을 쓰지 않고 프로그래밍을 할 수 있다. 그렇게 되면 복잡한 조건 분기식을 없앨 수 있다. 위에서는 Map을 썼지만 Enum을 활용할 수 있다.

일전에는 Function을 이용한 Map으로 if문을 없앴지만 아래와 같이 진행하면 Enum으로도 활용 가능하다.

public enum FigureFactory {
    LINE(2, Line::new),
    TRIANGLE(3, Triangle::new),
    RECTANGLE(4, Rectangle::new);

    int pointCount;
    Function<Points, Figure> function;

    FigureFactory(int pointCount, Function<Points, Figure> function) {
        this.pointCount = pointCount;
        this.function = function;
    }

    public static Figure getFigure(Points points) {
        int pointCount = points.getSize();

        FigureFactory figureFactory = Arrays.stream(FigureFactory.values())
                .filter(shape -> shape.pointCount == pointCount)
                .findFirst()
                .orElseThrow(IllegalArgumentException::new);

        return figureFactory.function.apply(points);
    }
}

회고: 페어 프로그래밍

강의 시작전에 페어 프로그래밍에 대한 회고를 진행하였다. 3주간 페어 프로그래밍을 하면서 좋았던 점, 안 좋았던 점에 대해서 주변 페어들과 논의하는 시간을 가졌다.

 

그런 의미에서 페어 프로그래밍에 대해 정리를 해보았다.

 

https://pjh3749.tistory.com/247

 

[우아한테크코스] 페어프로그래밍 회고

페어프로그래밍 회고 우아한테크코스에서는 기본적으로 이러한 페어 프로그래밍을 실천한다. 처음에 매번 자주 페어가 바뀌는 것에 의문을 품었지만 사실을 페어프로그래밍 아이디어가 모든 구성원들과 동적으로..

pjh3749.tistory.com

반응형