Skip to content

Latest commit

 

History

History
200 lines (147 loc) · 7.71 KB

21_인터페이스는_구현하는_쪽을_생각해_설계하라_박창원.md

File metadata and controls

200 lines (147 loc) · 7.71 KB

아이템21. 인터페이스는 구현하는 쪽을 생각해 설계하라

디자인패턴에 관심이 있는 개발자라면 인터페이스의 중요성은 잘 알고 있을 것이다. 자바 8 이후로는 인터페이스의 하위 호환성을 지킬 수 있게 되었다.

  • 자바 8 이전에는 하위 호환성을 지키면서 인터페이스에 메서드를 추가하는 것이 불가능했다.
  • 자바 8 이후, default method가 추가되면서 새로운 인터페이스를 구현하지 않아 발생하는 컴파일 에러를 잡을 수 있게 되었다.

하지만, default 메서드에도 위험이 따른다. 아래 메서드를 살펴보자. java.util.Collection 의 removeIf 디폴트 메서드다.

default boolean removeIf(Predicate<? super E> filter) {
  Objects.requireNonNull(filter);
  boolean removed = false;
  final Iterator<E> each = iterator();
  while (each.hasNext()) {
    if (filter.test(each.next())) {
      each.remove();
      removed = true;
    }
  }
  return removed;
}

테스트!!!

자바 1.8 이후로 적용된 removeIf 메서드가 정말로 하위 호환성을 지켜줄까? apache commons-collections의 4.2 버전의 구현체를 보자. 이 라이브러리는 removeIf 메서드가 없다. 즉, 자바 1.8 이전에 만들어진 라이브러리다.

테스트를 위해 build.gradle에 아래와 같이 디펜던시를 추가하자.

dependencies {
    compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.2'
    compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
    testCompile group: 'org.assertj', name: 'assertj-core', version: '3.19.0'
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0'
}

다음과 같이 테스트 코드를 작성하자.

싱글 스레드 기반으로 SynchronizedCollection 라이브러리가 자바 1.8에서 컴파일에 성공함을 알 수 있다.

public class SingleThreadSynchronizedTest {
    private static final Logger log = LoggerFactory.getLogger(SingleThreadSynchronizedTest.class);
    
    @Test
    void testSingleThread() {
        List<String> list = new ArrayList<>();
        list.add("스위프트");
        list.add("자바");
        list.add("스터디");

        SynchronizedCollection<String> sc = SynchronizedCollection.synchronizedCollection(list);

        String a = "스터디";
        
        sc.forEach((s) -> {
            log.info("before={}", s);
        });
				// "스터디"만 리스트에서 지울것이다.
        sc.removeIf(p -> p.equals(a)); // 아파치 라이브러리에는 존재하지 않지만 자바 1.8의 디폴트 메서드로 구현됨

        sc.forEach((s)-> {
            log.info("after={}", s);
        });
    }
}

실행 결과, 결과에는 이상이 없다.

16 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=스위프트
16 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=자바
16 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=스터디
17 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - after=스위프트
17 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - after=자바

이번에는 멀티 스레드로 10개의 스레드를 생성하여 테스트해보자.

public class MultiThreadSynchronizedTest {
    private static final AtomicInteger counter = new AtomicInteger(0);
    private static final Logger log = LoggerFactory.getLogger(SingleThreadSynchronizedTest.class);

    @Test
    void testMultiThread() throws InterruptedException {
        List<String> list = new ArrayList<>();
        list.add("스위프트");
        list.add("자바");
        list.add("스터디");
        list.add("삼터디");
        list.add("사터디");

        SynchronizedCollection<String> sc = SynchronizedCollection.synchronizedCollection(list);

        String a = "스터디";
        sc.forEach((s) -> {
            log.info("before={}", s);
        });

        // 스레드 10개 생성
        int nThread =  10;
        ExecutorService executorService = Executors.newFixedThreadPool(nThread);

        for (int i = 0; i < nThread; i++) {
            executorService.submit(() -> {
               int idx = counter.addAndGet(1);

               log.info("idx = {} removeIf = {}", idx, sc.removeIf(p -> p.equals(a)));
               return null;
            });
        }

        executorService.awaitTermination(1, TimeUnit.SECONDS);

        sc.forEach((s)-> {
            log.info("after={}", s);
        });
    }
}

위 코드를 여러번 실행하면 결과가 제각각인 것을 확인할 수 있다. 즉 리스트 객체에 동기화 기능이 제대로 갖추어져 있지 않음을 알 수 있다.


15 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=스위프트
15 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=자바
15 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=스터디
16 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=삼터디
16 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - before=사터디
18 [pool-1-thread-3] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 3 removeIf = true
18 [pool-1-thread-4] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 4 removeIf = true
18 [pool-1-thread-2] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 2 removeIf = true
18 [pool-1-thread-1] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 1 removeIf = true
19 [pool-1-thread-5] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 5 removeIf = false
19 [pool-1-thread-7] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 7 removeIf = false
20 [pool-1-thread-6] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 6 removeIf = false
20 [pool-1-thread-8] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 8 removeIf = false
20 [pool-1-thread-9] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 9 removeIf = false
20 [pool-1-thread-10] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - idx = 10 removeIf = false
1026 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - after=스위프트
1026 [main] INFO me.cwpark.chapter4.item21.SingleThreadSynchronizedTest - after=자바

스위프트의 인터페이스는 default 메서드가 존재할까?

스위프트의 인터페이스는 프로토콜(protocol)로 불린다. 프로토콜에 구현 메서드를 추가하려면, extension을 통해 메서드 구현이 가능하다. 하지만 이 extension을 사용할 때 주의점이 있다.

protocol SampleProtocol {
    func foo()
}
extension SampleProtocol {
    func foo() {
        print("protocol foo")
    }
    func bar() {
        print("protocol bar")
    }
}
class SampleClass: SampleProtocol {
    func foo() {
        print("class foo")
    }
    func bar() {
        print("class bar")
    }
}
let sample: SampleProtocol = SampleClass()
sample.foo() // "class foo"
sample.bar() // "protocol bar", "class bar" 가 출력되지 않는다!!!

SampleClass 클래스는 SampleProtocol이라는 프로토콜을 구현하고 있다. 하지만 SampleProtocol은 foo() 메서드만을 명세하고 있으므로, extension의 bar() 메서드가 구현체인 SampleClass의 메서드보다 우선한다.


https://sejoung.github.io/2018/12/2018-12-17-Design_interfaces_for_posterity/#아이템-21-인터페이스를-구현하는-쪽을-생각해-설계하라

https://medium.com/better-programming/swift-why-you-should-avoid-using-default-implementations-in-protocols-eeffddbed46d