JAVA8 부터 등장한 디폴트 메소드에 의해 interface 의 유연함은 좋아졌고, 그 말은 즉 변경에 대한 호환성이 좋아졌다는 의미로 생각할 수 있을 것 같습니다.


디폴트 메소드에 대한 이야기는 이 포스팅에서 참고! :-)



하지만 interface 에 행위를 추가함에 있어서 JAVA에서 등장하지 않았던 다중상속과 같은 기능이 생겼으며, 어떤 면에서는 편리하지만 이에 따른 충돌이 있음을 생각해 볼 수 있습니다.


아래와 같이 말이죠. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * 비행을 할 수 있는 인터페이스 명시.
 *
 * @author Doohyun
 *
 */
public interface IFlyable {
    /**
     * 날 수 있는 방법 명시.
     */
    void fly();
    
    /**
     * 음식으로 변환 
     * @return
     */
    default Object toFoodObject() {
        return new FoodByFlyAble();
    }
}
 
public interface IRunable {
    /**
     *  수 있는 방법 명시.
     */
    void run();
    
    /**
     * 음식으로 변환
     * @return
     */
    default Object toFoodObject() {
        return new FoodByFRunAble();
    }
}
 
/**
 * 닭 클래스 정의
 * 
 * @author Doohyun
 *
 */
public class Chicken implements IFlyable, IRunable{
 
    @Override
    public void fly() {
        System.out.println("30 m 점프");
    }
    
    @Override
    public void run() {
        System.out.println("열심히 뛰");
    }
}
cs


Chicken 클래스가 toFoodObject() 를 사용할 때, 어떤 interface 의 디폴트 메소드를 사용해야하는 것일까요? ㅡㅡ^


물론 이러한 모호성을 없애기 위해 JAVA8 in Action 에서는 해석 규칙을 명시하였습니다.


1. 클래스에서 디폴트 메소드를 재정의한다면, 클래스가 무조건 승리!


Chicken 클래스에서 아래와 같이 toFoodObject() 을 재정의 한다면, 일단 문제는 사라집니다. 

또한 interface 는 super 클래스가 아니니, super 키워드로 toFoodObject() 을 호출하는 모호성은 없다고 볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Chicken implements IFlyable, IRunable{
 
    @Override
    public void fly() {
        System.out.println("30 m 점프");
    }
    
    @Override
    public void    run() {
        System.out.println("열심히 뛰");
    }
    
    /**
     * toFoodObject 을 재정의!!
     */
    @Override
    public Object toFoodObject() {
        return new FrenchStyleChiken();
    }
}
cs



2. Sub interface 가 디폴트 메소드 재정의한다면, sub interface 가 승리!


interface 간 상속구조가 존재하고, sub interface 가 부모 interface 의 디폴트 메소드를 재정의한다면, 아래와 같이 sub interface 디폴트 메소드가 실행됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * 비행을 할 수 있는 인터페이스 명시.
 *
 * @author Doohyun
 *
 */
 interface IFlyable {
    /**
     * 날 수 있는 방법 명시.
     */
    void fly();
    
    /**
     * 음식으로 변환 
     * @return
     */
    default Object toFoodObject() {
        return new FoodByFlyAble();
    }
}
 
 interface IRunable extends IFlyable{
    /**
     *  수 있는 방법 명시.
     */
    void run();
    
    /**
     * 음식으로 변환 
     * @return
     */
    @Override
    default Object toFoodObject() {
        return new FoodByFRunAble();
    }
}
 
/**
 * 닭 클래스 정의
 * 
 * @author Doohyun
 *
 */
 class Chicken implements IRunable{
 
    @Override
    public void fly() {
        System.out.println("30 m 점프");
    }
    
    @Override
    public void run() {
        System.out.println("열심히 뛰");
    }
}
 
System.out.println(chicken.toString());
 
// Return
// FoodByRunnable 
cs



3. 디폴트 메소드의 우선순위가 결정되지 않았다면, 명시적 선언!


1, 2 번 규칙에 의해서 여전히 디폴트 메소드의 우선순위가 결정되지 않았다면 상속대상이 되는 클래스에서 어떤 메소드를 사용할지 명시적으로 선언해야 합니다.


JAVA8 에서는 인터페이스의 디폴트 메소드를 명시적으로 선언하기 위해서,


InterfaceName.super.method(...) 


형태의 문법을 제공합니다. 사용법은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * 닭 클래스 정의
 * 
 * @author Doohyun
 *
 */
 public class Chicken implements IRunable, IFlyable{
 
    @Override
    public void fly() {
        System.out.println("30 m 점프");
    }
    
    @Override
    public void run() {
        System.out.println("열심히 뛰");
    }
    
    @Override
    public Object toFoodObject() {
        return IRunable.super.toFoodObject();
    }
}
 
cs


디폴트 메소드에 의한 복잡한 상속구조에 의한 충돌은 위 세 가지 규칙만 따르면 해결 가능성이 존재합니다. 


디폴트 메소드가 추가됨에 따라 interface 변경에 대한 호환성 뿐만 아니라, 여러 기능을 가진 모듈체(디폴트 메소드에 의한 기능)를 쉽게 붙일 수 있다는 것은 꽤 매력적일 수 있습니다. :-)


자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기


반응형
Posted by N'

JAVA 를 조금이라도 사용해본 프로그래머라면, interface 의 중요성을 알고 있을 것이라 생각합니다.


interface 위주의 개발은 작성자(인터페이스를 사용하여 기능 개발)의 입장에서는 인터페이스에 정의된 기능 외에는 고려하지 않아도 되며, 사용자(인터페이스로 작성된 기능 사용) 입장에서는 인터페이스가 원하는 시그니처만 충족해주면 모듈을 사용할 수 있습니다.


interface 는 모듈 개발 시 생각을 단순화시킬 수 있으며, 모듈 간의 유연성을 높일 수 있으므로 자주 애용하고 있으리라 믿어 의심치 않습니다. 

(블로그 주인장도 그러한가? ㅡㅡ^) 


라이브러리 설계자들은 이러한 interface 위주로 모듈을 설계하여 배포를 하며, 개발자들은 해당 라이브러리를 사용하여 보다 쾌적한 개발을 할 수 있습니다. 


그러나 이러한 interface 의 설계는 양날의 검과 같습니다. 

interface 의 변경 시, 이를 구현하고 있는 class 를 모두 수정해야하기 때문이죠. 

물론 interface 의 설계자와 개발자가 동일하여 해당 수정작업에 대해 커버를 할 수 있으면 다행이지만, 이미 배포를 받아 인터페이스를 해당 모듈을 사용하여 구현한 개발자들이 많다면 그것만큼 재앙도 없을 것입니다.


JAVA8 에서는 이를 위해 default, static 메소드가 등장하였습니다. 

이 개념을 사용하여 인터페이스 내부에 공통적으로 수행해야할 일을 정의할 수 있으며, 이를 통해 인터페이스의 변경 작업 시 호환성 문제를 어느정도 해결할 수 있습니다. 


아래는 디폴트 메소드를 사용한 예제입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * 비행이 가는하다는 것을 정의한 인터페이스.
 * 
 * @author Doohyun
 *
 */
public interface IFlyAble {
    /**
     * 이륙하는 방법에 대한 구현.
     */
    void fly();
    
    /**
     * 이륙할 수 있는 여부 구현.
     * 
     * @return
     */
    Boolean flyAbleYn();
    
    /**
     * 착지를 위한 기능은 fly able 일 때 가능..
     */
    default void landing() {
        if (flyAbleYn()) {
            System.out.println("착륙을 시도합니다.");
        }
    }
}
cs


처음 interface 설계에서는 [이륙을 할 수 있는가?(flyAbleYn)], [이륙(fly)] 에 대한 설계를 하여 구현하였지만, 이륙할 수 있는 클래스군 한정 착륙 기능을 수행할 수 있어야 했습니다.


이 기능은 JAVA8 에 새로 나온 개념인 디폴트 메소드로 해결을 하였으며, 위와 같이 [착륙(landing)] 기능이 구현된 것을 볼 수 있습니다.


JAVA8 이전 버전에서는 아마 이런식의 구현이 있었을 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * 비행이 가는하다는 것을 정의한 인터페이스.
 * 
 * @author Doohyun
 *
 */
public interface IFlyAble {
    /**
     * 이륙하는 방법에 대한 구현.
     */
    void fly();
    
    /**
     * 이륙할 수 있는 여부 구현.
     * 
     * @return
     */
    Boolean flyAbleYn();
}
 
/**
 * 비행이 가능한 객체에 대한 유틸 클래스
 * 
 * @author Doohyun
 *
 */
public class FlayAbleUtil {
    
    /**
     * 착지를 위한 기능은 fly able 일 때 가능..
     * 
     * <pre>
     *     default 메소드의 기능이 이러한 유틸로 제작되었을 것!
     * </pre>
     */
    public static void landing(IFlyAble flyable) {
        if (flyable.flyAbleYn()) {
            System.out.println("착륙을 시도합니다.");
        }
    }
}
cs


디폴트 메소드의 기능을 사용할 수 없으니, 공통적으로 사용가능성이 있는 모듈을 위와 같이 Util 클래스의 형태로 제작했을 것입니다.


이러한 패턴을 우리는 많이 본 적이 있습니다. 맞습니다. Collections!


1
2
3
4
5
6
7
8
9
10
11
List<Integer> numberList = IntStream.range(0100).boxed().collect(Collectors.toList());
 
/**
 * java8 이전의 정렬.
 */
Collections.sort(numberList, (a, b) -> a.compareTo(b));
 
/**
 * java8 에서 추가된 디폴트 메소
 */
numberList.sort((a, b) -> a.compareTo(b));
cs


JAVA8의 설계자는 위와 같이 유틸클래스에 있던 기능을 디폴트 메소드로 구현함으로써, 각 하위클래스들이 직관적으로 기능을 수행할 수 있도록 하였습니다. 


Collection interface 는 변경되었지만, 호환성에서는 아무런 문제가 없었습니다.


또한 JAVA8 부터 기본적으로 제공되는 함수형 인터페이스들도 람다조합 등의 기능을 동작하게 하기 위하여 default method 를 사용하고 있습니다.



default 메소드는 이외에도 선택적으로 구현해야하는 사항에 대한 stub 을 더이상 만들 필요가 없어졌습니다. 

예를들어 특정 인터페이스에서 일부 시그니처의 기능을 사용하는 때가 있고 아닐 때가 있다 가정하면 다음과 같이 선택적으로 시그니처를 구현할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
 * 비행을 할 수 있는 인터페이스 명시.
 *
 * @author Doohyun
 *
 */
public interface IFlyable {
    /**
     * 날 수 있는 방법 명시.
     */
    void fly();
    
    /**
     * 음식으로 변환
     * @return
     */
    default Object toFoodObject() {
        throw new UnsupportedOperationException("해당 제품은 먹는 것을 지원할 수 없습니다.");
    }
}
 
/**
 * 닭 클래스 정의
 * 
 * @author Doohyun
 *
 */
public class Chicken implements IFlyable{
 
    @Override
    public void fly() {
        System.out.println("30 m 점");
    }
 
    @Override
    public Object toFoodObject() {
        System.out.println("닭을 튀기자!!!");
        ..  
        return obj;
    }
}
 
/**
 * 형 오리 정의
 * 
 * <pre>
 *     형 오리는 먹을 수 없음.
 * </pre>
 * 
 * @author Doohyun
 *
 */
public class BroDuck implements IFlyable{
    @Override
    public void fly() {
        System.out.println("10cm 점프");
    }
}
cs


디폴트 메소드는 위와 같이 인터페이스의 수정에 있어서 유연성을 줄 수 있는 도구입니다. 


더이상 interface 의 수정에 있어서 호환성에 대한 문제를 잠시 잊게 해주며, 어떤 면에서보면 단순히 틀이 아닌 모듈로써의 역할까지 수행할 수 있어 보입니다.


그러나 편의적인 면과 더불에 작은 문제가 생겼습니다. 

각 interface 들간 깊은 상속관계가 구축되어 있으며, 모듈화된 디폴트 메소드에 의해 다중상속 문제가 등장하였습니다.


이에 대한 풀이를 다음 포스팅에서 하고자 합니다.




자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기


반응형
Posted by N'

포스트의 제목은 현재 읽고 있는 책인 "JAVA8 IN ACTION"의 첫 목차입니다.


"JAVA8 을 눈여겨봐야 하는 이유 세번째 포스트" 로는 JAVA8 에 봐야할 추가된 개념 몇가지를 더 소개하고자 합니다.


1. 디폴트 메소드 (default method)


JAVA8 에서는 기존의 interface에 디폴트 메소드란 개념을 추가했습니다. "JAVA8 IN ACTION"의 내용에 따르면, 더 쉽게 변화할 수 있는 인터페이스를 만들 수 있도록 메소드를 추가했다고 합니다. (그러나 아마 stream의 개념을 기존 만들어진 collection 클래스들에 모두 추가할 수 없었던 문제가 가장 크기 않았을까요? interface에 메소드를 추가하면 모두 구현하던지 해야하니깐? ㅡㅡ^)


즉 이 메소드로 인하여, 미래의 변화에 유연하게 대처할 수 있게 되었습니다. (변화를 사랑하는 개발자가 됩시다.^^)  


사용방법은 interface에 default란 키워드를 붙여 사용할 수 있습니다. 이 default 메소드는 계속 함수형 프로그래밍에서 언급하던 동시실행에 대해서 안전합니다. 즉 이 개념을 사용하여 코드로 넘기는 함수로 많이 이용할 수 있을 것으로 보이네요.


다중상속의 문제가 있어보이지만 이를 피할 수 있는 방법이 있다고 합니다




2. Optional<T>


Optional<T>의 개념을 사용하여 NULL 예외를 피할 수 있다고 합니다. 이 객체는 값을 가지거나 안가질 수 있는 컨테이너 객체(이를테면 Collection 등..)로 값이 없는 상황을 명시적으로 처리 가능합니다. 


(즉 해당 기능으로 인하여, null에 대한 방어코드를 없앨 수 있기를 바랍니다.)




3. 구조적 패턴 매칭


객체지향 프로그래밍에서 극혐으로 하는 if-else-then 문제를 수학적으로 쉽게 명시할 수 있다고 합니다. (포스트 시, 업데이트 할 것)





자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기




반응형
Posted by N'