C++에서 중요하게 여기는 것중 하나가 코드의 재활용성을 높이는 것인데, 그 방법중 컴포지션(Composition) or 레이어링(layering) or 컨테인먼트(containment)라고 불리는 패턴을 포스팅하고자 한다. 흔히 코드의 재활용성을 높이기 위해서 기초클래스를 상속하는 is-a 방식으로 많이 표현하지만, 컴포지션(Composition) 은 자신의 클래스에 다른 클래스 객체를 멤버로 가져와서 has-a 관계를 만족 시키는 "어떤 객체가 소유하고 있는 무엇"으로 표현하고 코드의 재활용성을 높일 수 있다.

     


    목차

    • 객체 멤버를 가지는 클래스 (Composition, has-a)
    • 학점 관리를 해주는 valarray 클래스 훑어보기

     


     

    객체 멤버를 가지는 클래스 (Composition, has-a 관계)

    포준 템플릿 라이브러리에서 제공하는 C++표준 클래스 템플릿들이 있다. 이런 표준 라이브러리의 클래스들을 가져다 사용하게 되면 간단히 has-a 관계를 구축할 수 있다.

    예를 들어, 학생이란 무엇인가 생각해보자. 학생이란 어디어디에 사는 누구가 될 수도 있고 커피를 좋아하는 학생이 될 수도 있다. 한 사람의 특징을 나타내려면 너무 많은 정의가 필요하지만 그래도 설계를 통해서 학생의 특징은 간단하게 표현해서 구현할 수도 있다.

    만약 학생, 이름, 성적표라는 3개의 클래스가 있다고 가정해보자.
    이름과 성적표의 특징을 가지는 학생이란 클래스를 설계하려면 어떻게 해야할까?

    이름과 성적표의 인터페이스를 학생 클래스에 다중 상속해서 구현할 수도 있겠지만, 그건 이런 상황에서 적절하지 않다. 왜냐하면 has-a 관계가 아니기 때문이다. 학생이 성적표의 일부는 아니니까 말이다. 어쨌든 is-a 관계가 아니기 때문에 이러한 상황에선 상속은 어울리지 않는다. 학생을 나타내주는 이름과 성적표여야 하기때문에 상속보다는 자신의 상태를 나타내주는 멤버로서 표현하는게 적절하다. (Composition)

     

    /////// student.h ///////
    class Student
    {
    private:
        typedef std::valarray<double> ArrayDB;  // 가독성을 위해 typedef로 이름을 바꿔준다.
        std::string m_name;                     // 이름을 표현하는 string 클래스
        ArrayDB scores;                         // 성적표를 관리하는 valarray 클래스
        
        // scores를 출력하기 위한 private 멤버 함수
        std::ostream & scores_out(std::ostream & os) const;
    
    public:
    // 생성자나 기타 기능 생략
        friend std::ostream & operator<<(std::ostream & os, const Student & student);    
    };
    
    ---------------------------------------------------------------------------------
    
    /////// student.cpp ///////
    ostream & Student::scores_out(std::ostream & os) const;
    {
         int i;
         int size = scores.size();
         if (size > 0)  // 배열에 값이 있을 경우
         {
             for(i = 0; i < size; i++)
             {
             os << scores[i] << " ";
             if (i % 5 == 4)           // 5개 마다 개행
                 os << endl;
             } // for문 
              if (i % 5 != 0)
                  os << endl;
          }  // if(size>0)
          else         // 배열에 값이 없을 경우
              os << " 빈 배열 "; 
          return os;
    } // 함수 종료
    
    // private 멤버 함수를 이용해서 입출력 연산자 오버로딩
    ostream & operator<<(ostream & os, const Student & student) // 프렌드 함수
    {
        os << student.name << " 학생의 성적표:\n ";
        student.scores_out(os);   // private 멤버 함수 사용
        return os;
    }

     


     

    학점 관리에 유용한 valarray 클래스 

    valarray 헤더 파일에서 valarray 클래스를 지원하고 있다. 이 클래스는 배열에 들어있는 값들의 합(sum)을 구하거나 최대값(MAX), 최소값(MIN)을 구해주는 기능들이 있다. 이 클래스는 템플릿으로 정의되어 있으며, 더 자세한 내용은 라이브러리 카테고리에서 다룰 예정이다. 우선은 이 포스팅에서는 초기화 방법과 간단한 기능만 파악해본다.

     

    // -- 초기화 방법 --
    
    double arr[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
    
    valarray<double> v1;          // double형 배열을 생성한다. 크기는 0
    valarray<int> v2(8);          // int형 배열을 생성한다. 크기는 8
    valarray<int> v3(10,8);       // int형 배열을 생성하고 크기는 8이며 각 원소는 10으로 초기화
    valaaray<double> v4(arr, 4);  // double형 배열을 생성, 크기는 4, gpa의 값으로 초기화(크기 4까지)
    
    // C++11
    valarray<int> v5 = { 1, 2, 3, 4, 5 };  // C++11 방식의 초기화


    위 코드에서 v4의 초기화 방식은 v4를 arr형 배열의 값들로 초기화하는 과정이다. v4의 크기를 4로 명시해주었기 때문에 arr를 구성하는 5개의 값중에서 4개의 값만 사용해 v4를 초기화하게 된다.

    마지막으로 valarray 클래스의 기능은 다음과 같다.

     

    기능 설명
    operator[]() 각 인덱스에 속한 값에 접근한다. (배열의 기능과 동일)
    size() 원소의 개수를 리턴한다.
    max() 가장 큰 값의 원소를 리턴한다.
    min() 가장 작은 값인 원소를 리턴한다.
    sum() 모든 값의 합을 리턴한다.

     


     

    마무리

    객체 지향에 있어서 is-a 관계와 has-a 관계를 잘 파악하고 그 사람을 나타내주는 특징을 클래스로 표현하고 싶다면 컴포지트 패턴의 레이어링을 사용해서 구현하자.