[기본 개념] 2 | (1.2) 클래스 간 관계

728x90

[기본 개념] 2 | (1.2) 클래스 간 관계

1 상속의 정의와 장점

2> 클래스간의 관계 - 포함관계

3> 클래스간의 관계 결정하기

4 단일상속(single inheritance)

5 Object클래스 - 모든 클래스의 조상

2. 클래스간의 관계 - 포함관계

상속이외에도 클래스를 재사용하는 또 다른 방법은 클래스간에 '포함(Composite)'관계를 맺어주는 것이다. 이는 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다.

 

 원(Circle)을 표현하기 위한 Circle이라는 클래스를 다음과 같이 작성하였다고 가정하자.

 

        class Circle {

            int x ;        // 원점의 x좌표

            int y ;        // 원점의 y좌표

            int r ;         // 반지름 (radius)

        }

 

그리고 좌표상의 한 점을 다루기 위한 Point클래스가 다음과 같이 작성되어 있다고 하자.

 

        class Point {

            int x ;        // x좌표

            int y ;        // y좌표

        }

 

Point클래스를 재사용해서 Circle클래스를 작성한다면 다음과 같이 할 수 있을 것이다.

 


    class Circle {

        int x ;        // 원점의 x좌표
        int y ;        // 원점의 y좌표
        int r ;         // 반지름 (radius)
    }

---->
    class Circle {

        Point c = new Point( ) :  // 원점
        int r ;
    }

 

 포함관계를 맺어주면, 클래스를 작성하는 것도 쉽고 코드도 간결해서 이해하기 쉽다. 그리고 단위클래스별로 코드가 작게 나뉘어 작성되어 있기 때문에 코드도 관리하는데 수월하다.

1.3 클래스간의 관계 결정하기

 상속관계를 맺어 줄 건지 포함관계를 맺어 줄건지 결정하는 것이 혼란스러울 수 있다. Circle클래스와 Point 클래스의 경우 '~은 ~이다(is-a)'와 '~은 ~을 가지고 있다(has-a)'를 넣어서 문장을 만들어 보면 관계가 명확해진다.

 

원(Circle)은 점(Point)이다. - Circle is a Point.

원(Circle)은 점(Point)을 가지고 있다. - Circle has a Point.

 

원은 원점(Point)과 반지름으로 구성되므로 두 번째 문장이 더 옳다는 것을 알 수 있다.

 

 이처럼 '~은 ~이다.'라는 문장이 성립하면 상속관계를 맺어 주고, '~은 ~을 가지고 있다.'라는 문장이 성립한다면 포함관계를 맺어주는 것이 더 옳다. 

 

상속관계 '~은 ~이다.(is-a)'

포함관계 '~은 ~을 가지고 있다.(has-a)'

 

 예를 들면, Card클래스와 Deck클래스는 'Deck은 Card를 가지고 있다.'라는 문장이 더 옳기 때문에 Deck클래스에 Card클래스를 포함시켜야 한다.

 

예제/DeckTest.java

public class DeckTest {
    public static void main(String[] args) {
        Deck d = new Deck();        // 카드 한벌(Deck)을 만든다.
        Card c = d.pick(0);   // 섞기 전에 제일 위의 카드를 뽑는다.
        System.out.println(c);      // System.out.println(c.toString());과 같다.

        d.shuffle();                // 카드를 섞는다.
        c = d.pick(0);        // 섞은 후에 제일 위의 카드를 뽑는다.
        System.out.println(c);
    }
}

class Deck {
    final int CARD_NUM = 52;   // 카드의 개수
    Card cardArr[] = new Card[CARD_NUM];   // Card객체 배열을 포함

    Deck() {   // Deck의 카드를 초기화한다.
        int i = 0;

        for(int k = Card.KIND_MAX; k > 0; k--)
            for(int n = 0; n < Card.NUM_MAX; n++)
                cardArr[i++] = new Card(k, n+1);
    }

    Card pick(int index) {   // 지정된 위치(index)에 있는 카드 하나를 꺼내서 반환
        return cardArr[index];
    }

    Card pick() {            // Deck에서 카드 하나를 선택한다.
        int index = (int)(Math.random() * CARD_NUM);
        return pick(index);
    }

    void shuffle() {   // 카드의 순서를 섞는다.
        for(int i = 0; i < cardArr.length; i++) {
            int r = (int) (Math.random() * CARD_NUM);

            Card temp = cardArr[i];
            cardArr[i] = cardArr[r];
            cardArr[r] = temp;
        }
    }
}

class Card {
    static final int KIND_MAX = 4;  // 카드 무늬의 수
    static final int NUM_MAX = 13;  // 무늬별 카드 수

    static final int SPADE = 4;
    static final int DIAMOND = 3;
    static final int HEART = 2;
    static final int CLOVER = 1;
    int kind;
    int number;

    Card() {
        this(SPADE, 1);
    }

    Card(int kind, int number) {
        this.kind = kind;
        this.number = number;
    }

    public String toString() {
        String[] kinds = {"", "CLOVER", "HEART", "DIAMOND", "SPADE"};
        String numbers = "0123456789XJQK";
        return  "kind : " + kinds[this.kind]
                + ", number : " + numbers.charAt(this.number);
    }
}
실행결과

kind : SPADE, number : 1
kind : SPADE, number : 9

 

 Deck클래스를 작성하는데 Card클래스를 재사용하여 포함관계로 작성하였다. 카드 한벌은 모두 52장의 카드로 이루어져 있으므로 Card클래스를 크기가 52인 배열로 처리하였다. shuffle( )은 카드 한 벌의 첫 번째 카드부터 순서대로 임의로 위치에 있는 카드와 위치를 서로 바꾸는 방식으로 카드를 섞는다. random( )을 사용했기 때문에 매 실행 시마다 결과가 다르게 나타날 것이다.

 

 아래의 문장에서 pick(0)을 호출하면, 매개변수 index의 값이 0이 되므로, cardArr[0]에 저장된 Card객체의 주소(객체가 아니라 객체의 주소)가 참조변수 c에 저장된다.

 

        Card c = d.pick(0) ;   // pick(int index)를 호출

     

        Card pick(int index) {

            return cardArr[index] ;

        }

 

 Card클래스에 정의된 toString( )은 인스턴스의 정보를 문자열로 반환할 목적으로 정의된 것이다. 이처럼 참조변수의 출력이나 덧셈연산자를 이용한 참조변수와 문자열의 결합에는 toStrinf( )이 자동적으로 호출되어 참조변수를 문자열로 대치한 후 처리한다.

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90