포스트

2023-06-17-TIL

2023-06-17-TIL

Today I Learned

Study

중재자 패턴

MVC의 컨트롤러와 유사하다. 직접 통신하는 객체가 많을수록 복잡해진다. 이때 중재자 패턴은 서로 독립적으로 동작해야하는 컴포넌트 간의 통신을 중재한다. 의존관계를 줄여서 중앙집중식으로 객체를 하나 두면 관리가 용이하다. 하지만 중재자 역할을 하는 객체의 복잡도가 증가할 수 있는 단점이 있다.

Refactoring.Guru는 중재자 패턴을 객체 사이의 혼란스러운 의존관계를 줄이는 행위 패턴으로 정의한다. 대표 예시는 입력 폼이다. 체크박스를 누르면 텍스트 필드가 나타나고, 제출 버튼은 모든 필드를 검증해야 한다. 컴포넌트끼리 서로를 직접 부르면 N개 컴포넌트 사이에 연결이 거미줄처럼 생긴다. 중재자를 두면 각 컴포넌트는 중재자에게만 알리고, 누구에게 무엇을 시킬지는 중재자(여기서는 다이얼로그)가 정한다. 위에서 적은 단점도 문서가 그대로 지적한다. 시간이 지나면 중재자가 모든 것을 아는 God Object가 될 수 있다(Refactoring.Guru: Mediator).

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Mediator {
    void notify(Component sender, String event);
}

class SignUpDialog implements Mediator {
    Checkbox agree; Button submit;

    public void notify(Component sender, String event) {
        if (sender == agree && event.equals("check")) {
            submit.setEnabled(agree.isChecked());
        }
    }
}

Mediator Pattern vs Pub/Sub

둘 다 객체 사이의 직접 결합을 없애지만 목적이 다르다. 같은 문서의 비교에 따르면, 중재자는 컴포넌트 사이의 상호 의존을 없애고 모두 하나의 중재자에 의존하게 만드는 것이 목적이다. 옵저버(pub/sub)는 객체 사이에 동적인 단방향 구독 관계를 만드는 것이 목적이다. 중재자를 구현할 때 옵저버를 쓰는 경우가 많아서 둘이 비슷해 보이지만, 중재자는 구독 없이 컴포넌트를 고정으로 연결해도 성립한다.

내 식으로 정리하면, 중재자는 “누가 누구에게 무엇을 시킬지”라는 조율 로직을 한곳에 모은다. pub/sub의 브로커는 메시지를 전달할 뿐 그 의미를 모르고, 발행자는 누가 구독하는지 알지 못한다.

  • https://stackoverflow.com/questions/3162291/mediator-pattern-vs-publish-subscribe
  • https://hoangnguyen.name/difference-between-observer-pubsub-and-mediator/

Iterator Pattern

이터레이터 패턴은 컬렉션의 내부 구조를 드러내지 않고 요소를 하나씩 순회하게 해 준다. 자바는 이를 표준 인터페이스로 둔다. Iterator는 컬렉션 프레임워크에서 예전의 Enumeration을 대신하며, 순회 중에 정해진 방식으로 요소를 삭제할 수 있다는 점과 메서드 이름이 개선됐다는 점이 다르다(Iterator). Iterable을 구현하면 그 객체를 for-each 문의 대상으로 쓸 수 있다(Iterable). Map은 키 집합, 값 컬렉션, 키-값 쌍 집합이라는 세 가지 컬렉션 뷰를 제공하므로 keySet(), values(), entrySet()으로 뷰를 얻어 순회한다(Map).

Kotlin도 같다. Iterable<T>를 상속한 List, Set은 iterator()로 이터레이터를 얻고, for 문은 이터레이터를 암묵적으로 쓴다. 순회 중 삭제가 필요하면 MutableIterator, 양방향 순회가 필요하면 ListIterator를 쓴다(Kotlin: Iterators).

  • https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html
  • https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html
  • https://docs.oracle.com/javase/8/docs/api/java/util/Map.html
  • https://kotlinlang.org/docs/iterators.html

Java Parallel Working Iterator

Iterator는 hasNext()와 next()로 한 번에 하나씩 순서대로 꺼내는 구조라서 여러 스레드가 나눠 처리하기에 맞지 않는다. Java 8의 Spliterator가 이 역할을 한다. Javadoc은 Spliterator가 순차 순회뿐 아니라 요소를 나누는(decomposition) 방식으로 효율적인 병렬 순회를 지원하도록 설계됐고, hasNext()와 next()를 따로 부를 때 생기는 경쟁 문제도 피한다고 설명한다. trySplit()은 남은 요소 일부를 떼어 새 Spliterator로 돌려준다(Spliterator).

병렬 스트림은 이 분할 위에서 동작한다. Collection.parallelStream()이나 stream().parallel()로 얻을 수 있다. 다만 스트림 연산에 넘기는 람다는 데이터 소스를 건드리지 않아야 하고 대부분 상태가 없어야 하며, 부수 효과는 스레드 안전 문제로 이어질 수 있다고 문서는 경고한다(java.util.stream).

1
2
3
long count = orders.parallelStream()
        .filter(o -> o.amount() > 10_000)
        .count();

병렬로 바꾼다고 항상 빨라지지는 않는다. 요소를 나누고 결과를 합치는 비용이 있으므로, 데이터가 충분히 많고 요소별 작업이 독립적일 때 효과가 있다. 두 번째 링크가 언제 병렬 스트림을 쓸지 다룬다.

  • https://stackoverflow.com/questions/1228433/java-parallel-work-iterator
  • https://www.baeldung.com/java-when-to-use-parallel-stream
참고한 자료외부 출처 6

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다