두번째, 책 고급 프로그래밍 요점 및 정의 정리

클래스(Class)

객체지향 프로그래밍의 핵심 개념인 클래스를 다음의 세가지 관점에서 해석할 수 있다.

객체들의 집합을 의미하는 클래스

클래스는 집합이며, 그 클래스에 속한 멤버는 객체가 된다.
예를들어, ‘도시’가 집합의 의미로 사용된다면, 서울, 부산 등은 그 멤버가 된다.

객체 생성을 위한 템플릿(template) 역할의 클래스

설계 및 구현 단계에서 클래스는 그 클래스에 속한 여러 객체들이 필요로 하는 속성들과 오퍼레이션들을 명세하는 템플릿이다.

Employee Class
- Attributes
  1.이름
  2.부서
  3.직위
  4.급여
  ...

- Operations
  1.입사하다
  2.승진하다
  3.부서이동하다
  4.퇴사하다
  ...

클래스 정의(자바로 작성한 Employee 클래스)

class Employee {
    // 속성 정의
    String name;
    depType dept;
    titleType title;
    int salary;
    ...
    
    // 오퍼레이션 정의
    public void promote(titleType newTitle) { // 승진하다
        ...
    }
    
    public void changeDept(depType newDept) { // 부서이동하다
        ...
    }
    ...
}

사용자 정의 데이터 타입(User-Defined Datatype) 역할의 클래스

클래스는 사용자가 필요로 하는 새로운 데이터 타입을 정의하는 장치의 역할을 한다.

기본 데이터 타입 : int, float, char, boolean
사용자 정의 데이터 타입(클래스) : Customer, Car, Rental, Reservation

인스턴스

  • 클래스가 집합, 그룹의 의미를 가지는 반면, 그 클래스에 속한 각 객체를 인스턴스라고 부른다.
  • 순수 객체지향 언어에서는 모든 객체가 인스턴스이므로, 객체와 인스턴스는 동일하게 사용된다.
  • 인스턴스를 만드는 과정을 객체 생성이라고 한다.
  • 한 클래스에서 생성되는 모든 인스턴스는 같은 종류의 속성 집합을 가지지만, 인스턴스 마다 각 속성의 값은 다를 수 있다. 즉, 인스턴스들의 상태(status)는 다를 수 있다.
  • 한 클래스로부터 생성되는 모든 인스턴스는 동일한 오퍼레이션 집합을 가진다.

인스턴스 생성 방법

// 정적 생성
Employee bob;

// 동적 생성
Employee *anEmployee = new Emplyee(); // 익명 인스턴스

Employee *honorEmployee1, *honorEmployee2;

honorEmployee1 = &bob; // 정적으로 생성된 인스턴스 할당
honorEmployee2 = anEmployee; // 동적으로 생성된 인스턴스 할당

생성자

생성자는 새로운 인스턴스를 생성할때 사용되는 오퍼레이션이다.

  • 생성자의 이름은 클래스의 이름과 동일하고, 반환 타입이 없어야 한다.
  • 생성자는 새로운 인스턴스가 만들어 질 때 자동적으로 한 번만 실행된다.
class Employee {

private :
    string name;
    string dept;
    string title;
    int salary;
    
public :
    Employee() {} // 기본 생성자
    
    // 2개 인자 초기화 생성자
    Employee(string name, int salary) {
        this.name -> name;
        this.salary -> salary;
    }
    
    // 2개 인자 초기화 생성자
    Employee(string name, string dept, string title, int salary) {
        this.name -> name;
        this.dept -> dept;
        this.title -> title;
        this.salary -> salary;
    }
}

한 클래스에 여러개의 생성자가 있는 경우가 있는데, 이를 생성자가 오버로딩되어 정의 되어있다고 표현한다.

소멸자

인스턴스를 주 메모리에서 삭제할 때 사용되는 오퍼레이션이다.

  • 인스턴스가 사용하던 모든 자원들을 반환한다.
  • 메모리에서 인스턴스를 삭제한다.
  • 그 결과 인스턴스에 할당된 메모리가 반환된다.

cpp에서는 프로그래머가 직접 자원을 회수하도록 설계되어 있다.

소멸자 정의

  1. 소멸자의 이름은 ~[클래스명]으로 한다.
  2. 소멸자는 입력 매개변수와 반환 타입을 가지지 않는다.
  3. 소멸자는 오버로딩을 하지 않고, 클래스에 반드시 하나만 존재한다.
~Employee() {
    cout << "Object(" << name << ") is being delete" << endl;
    name.clear();
}

cpp에서의 생성자 및 소멸자 호출 시점

  정적 인스턴스 생성 동적 인스턴스 생성
생성자 호출시점 객체 정의시 new 연산자로 객체 생성 시
소멸자 호출시점 인스턴스를 생성한 메소드 실행이 완료될 때 delete 연산자로 해당 인스턴스를 삭제할 때

메세지(Message)

대부분의 객체는 혼자서 모든 기능을 완벽하게 수행하는 것이 힘들다.
즉, 객체가 그 기능을 성공적으로 수행하기 위해 다른 객체의 기능을 호출하는 것이 필요하며, 객체지향에서는 메세지 전달을 통해서만 다른 객체의 기능을 호출할 수 있다.

복합 객체(Composite Object)

  1. 강한 포함관계와 약한 포함 관계 한 객체가 다른 여러개의 객체들을 속성을 가지고 있으면 이를 복합 객체 또는 전체 객체라고 하며, 포함되는 개체를 부품 객체 또는 부속 객체라고 한다.
    그리고 이 두 객체들간에는 포함관계(Aggregation Relationship)가 있다고 한다.
    • 강한 포함(Strong Aggregation) 관계
      복합 객체가 소멸 될때에 부속 객체가 같이 소멸하는 경우(생명주기 동일)
      ex) 자동차 - 엔진

    • 약한 포함(Weak Aggregation) 관계
      복합 객체가 소멸 되도, 부속 객체가 소멸되지 않을 때(생명주기 독립)
      ex) 자동차 - 타이어

  2. 부속 객체의 정보 은닉 부속 객체가 제공하는 외부 인터페이스의 메소드를 통해서만 부분 객체의 내부 정보를 읽거나 바꿀 수 있다. 이는 부속 객체의 정보 은닉을 지켜주어 부속 객체의 무결성, 유지보수성을 보장하기 위함이다.

static

한 클래스에서 Static으로 정의된 속성들은 각 객체별로 메모리가 할당되지 않으며, 그 클래스 자체에 Static 속성들의 메모리가 할당된다.
각 객체들마다 생성되는 일반적인 속성을 인스턴스 속성이라고 부르며, 클래스 자체에 생성되는 속성을 Static 속성이라고 부른다.

static [데이터 타입][속성 이름];

static 오퍼레이션

클래스 안에 정의된 일반적인 오퍼레이션들은 각 객체들의 상태 값을 처리하기 위한 목적인 반면에, Static으로 선언된 오퍼레이션은 Static 속성 값을 처리하기 위한 목적으로 정의된다.

static [반환 타입][메소드명]([입력 매개변수]);

static의 활용

- 클래스로부터 생성된 모든 인스턴스의 개수 정보를 관리하기 위해
- 한 클래스로부터 나온 모든 인스턴스들을 대상으로 검색하기 위해
- 모든 인스턴스들이 공유해서 사용할 수 있는 데이터베이스로의 핸들을 관리하기 위해
- 새로운 객체 생성 시 모든 객체에게 동일하게 적용되는 기본 정보 저장을 위해
- 새로운 객체 생성 시 호출되는 생성자 정의를 위해(생성자도 Static 메소드)

상속(Inheritance)

일반화(Generalization)와 세분화(Specialization)

- 일반화는 구체적인 여러 개체들의 공통적인 요소들을 식별하여 하나의 범용적인 개체를 정의하는 것이다.
- 세분화는 하나의 범용적인 개체에 여러 개의 구체적인 개체들로 세분화하는 개념적인 장치이다.

상속 장치(Inheritance Device)

일반화 과정에서 도출된 범용적이며 공통적인 속성들과 메소드들을 가지는 클래스를 슈퍼클래스라고 한다.
슈퍼클래스는 부모 클래스(Parent Class) 또는 베이스 클래스(Base Class)라고도 불린다.

세분화 과정에서 도출된 슈퍼클래스에는 포함되어 있지 않는 추가적인 속성들과 메소드들을 가진 클래스를 서브클래스(Sub Class)라고 한다.
서브클래스는 자식 클래스(Child Class) 또는 확장 클래스(Expended Class)라고도 불린다.
서브클래스는 항상 어떤 슈퍼클래스의 하위에 위치한다.

  • 슈퍼 클래스들은 보다 범용적인 정보를 포함하며, 서브클래스는 보다 구체적이며 세부적인 정보를 포함한다.

Protected 속성 및 메소드

상속 관계에 있는 클래스들 끼리 접근 제한을 지정하고자 할 때에 사용된다.

class A {
    private int i;
    protected int j;
    public int k;
}

class B extends A {
    void f() {
        i++; // error!
        j++; // okay!
        k++; // okay!
    }
}

class C {
    void f() {
        i++; // error!
        j++; // error!
        k++; // okay!
    }
}

Protected 가시성의 단점

- 서브클래스 내에서는 public 속성과 동일한 효과로 인해 서브클래스에서 부모클래스의 속성에 잘못된 값을 입력해도 이를 통제할 수 없다.
- 서브클래스에서 부모 클래스의 속성에 직접 접근이 가능하므로 부모 클래스의 속성의 이름이나 데이터 타입이 변경되면 그 값을 참조하는 서브클래스들도 모두 변경되어야 한다.

Protected 가시성의 단점 해결방법

- 이러한 문제들을 해결하기 위해 부모클래스의 속성들을 private로 선언하고, getter와 setter 오퍼레이션을 통해 접근을 허용한다.

상속의 효과

프로그램 구조에 대한 이해도(Understandability) 향상

상속 관계만을 봐도 전체 시스템의 구성을 예상할 수 있다.
예를 들어, 루트 클래스인 Person을 보고 그 하위에 서브클래스들이 사람과 관련되었다라는 것을 알수 있다.
물론, 세부적인 부분들은 직접 봐야된다.

재사용성(Reusability) 향상

슈퍼클래스의 속성 및 오퍼레이션을 재사용할 수 있다.

필자도 재사용성 하나만으로도 상속 구조 적극적으로 활용해야 된다는 입장을 갖고 있습니다.

확장성(Extensibility) 향상

Subclassing 기법을 활용함으로써 필요로 하는 속성 및 메소드를 최대한 많이 제공하는 클래스로 부터 새로운 클래스를 정의하여 프로그램을 쉽게 확장 할 수 있다.

유지보수성(Maintainability) 향상

캡슐화 원칙을 준수하여 클래스 상속 구조가 논리적으로 정의 되어 있으면, 수정해야 되는 클래스가 줄고, Change Propagation(수정 전파 현상)의 문제가 없어진다.

상속 장치의 활용 방법

공통성 식별을 통한 슈퍼클래서 정의

여러 클래스에 공통으로 사용되는 속성 및 메소드는 상위 클래스에 위치 시키고, 특정 클래스에만 필요한 속성 및 메소드를 하위 클래스에 위치시켜 클래스 간의 상속을 구현한다.

Subclassing을 통한 새로운 클래스 정의

기존에 정의한 클래스를 상속받아 추가적으로 필요한 속성과 메소드를 구현하여 클래스를 구현한다.
슈퍼 클래스의 메소드에 기능을 추가하거나 변경하고 싶을 때에는 오버라이딩하여 구현한다.

final로 정의된 클래스로부터는 subclassing이 불가능하다.

캡슐화의 원칙을 적용하여 유지보수성 향상

CPP에서의 서브클래스 정의 방법

class Member: public Person {
    private:
        string grade;
        int deposit;
    ...
}

다중 상속

하나의 클래스가 두 개 이상의 클래스로부터 동시에 상속받는 것을 의미한다.
다중 상속을 통해 재사용성을 극대화 시킬 수 있다.

다중상속의 애매모호함

A 클래스와 B 클래스 내에 print 매소드가 존재하고 이 둘을 상속받는 C 클래스에서 print 매소드를 호출하면 애매모호해 진다.(누구꺼지?)

java에서의 다중 상속

java에서는 다중상속을 지원하지 않음으로 우회하는 방법을 사용해야 한다.
우회하는 방법으로는 1)다중상속 직렬화와 2)인터페이스를 이용한 다중 상속이 있다.

1) 직렬화 A->B->C => 상위 클래스들 정의할 때에 직렬화를 전제하고 정의.
2) 인터페이스를 구현하는 implements 키워드를 통해 여러 개의 인터페이스를 활용하여 정의.