SW/C++

C++11 : 추상화와 캡슐화, 생성자와 소멸자 (개념 및 예제)

얇은생각 2019. 1. 6. 07:30
반응형

C++를 필두로 하는 객체지향 프로그래밍의 중요한 개념인 추상화와 캡슐화에 대해 살펴보겠습니다. 추상화와 캡슐화는C++를 비롯한 객체지향 프로그래밍 언어에 공통으로 나타나는 특징입니다. 따라서 특별한 문법을 암기하는 것이 아니라 개념을 이해하고 이를 코드에 적용하는 훈련이 필요합니다.

 


추상화

추상화란 사물을 사실적으로 표현하는 개념이 아니라 불필요한 부분을 제거하고 공통된 특징만 추출하여 간결하고 이해하기 쉽게 만드는 작업을 의미합니다. 추상화는 객체지향 언어의 주요 특징 중에서도 첫 번째로 꼽습니다. 그 이유는 클래스를 만드는 과정과 밀접한 관계가 있기 때문입니다.

 

즉 표현하려는 실생활에 대한 공통적인 특징을 속성과 기능으로 구분하고 각각 멤버 변수와 멤버 함수로 만들어서 클래스로 표현하는 과정을 추상화라고 합니다.

 

실생활을 클래스로 추상화할 때는 가능한 모든 요구 사항을 고려하여 보편적이고 일반화된 특징들을 추출해야 합니다. 렇게 추상화한 클래스를 통해 객체를 만들고, 이 객체를 통해 다양한 요구 사항을 수용할 수 있어야 합니다.

 

추상화는 내부적으로 숨기는 세부적인 구현과 외부로 공개하는 인터페이스를 잘 구분하여 설계해야 합니다.

 


데이터 은닉

클래스의 멤버 변수를 private 접근 지정자를 사용하여 선언하는 것은 데이터 은닉의 대표적인 사례입니다. 이처럼 객체지향 프로그래밍에서 정보를 숨기는 이유는 해당 정보를 수정하는 권한을 특정해서 보호하려는 의도입니다.

 

객체지향 프로그래밍에서 중심은 바로 객체입니다. 그런데 만약 객체의 속성이 다른 객체나 함수 등에서 쉽게 변경된다면 의도한 내용이 변질될 수 있습니다. 따라서 데이터 은닉을 통해 객체의 속성을 숨기고 공개된 인터페이스를 통해서만 수정할 수 있도록 강제하면, 객체를 중심으로 프로그래밍할 수 있습니다. 이때 공개된 인터페이스란 클래스 내부에public 접근 지정자로 선언한 멤버 함수를 의미합니다.

 


캡슐화

캡슐화는 앞서 설명한 데이터 은닉을 포함하지만 같은 개념은 아닙니다. 캡슐화는 객체의 속성과 기능을 하나로 묶고 세부 구현 내용은 클래스 안으로 숨기는 것을 의미합니다. 즉 외부에서 객체 내부 속성에 직접 접근하거나 조작할 수 없도록 합니다. 외부에서 접근하는 방법은 오로지 공개로 정의한 인터페이스를 통해서만 가능하도록 합니다. 따라서 변경이 발생할 때 다른 곳에 영향을 받지 않도록 하여 오류 발생을 최소화할 수 있습니다.

 

또한 캡슐화한 클래스는 데이터와 함수가 잘 정리되어 있으므로 다른 프로그램에서도 재활용할 수 있습니다. 이때 해당 클래스가 어떻게 구현되었는지 복잡한 내용은 알 필요가 없고 어떻게 접근해서 어떻게 사용할 것인지만 알면 되므로 복잡도가 줄어듭니다.

 

캡슐화를 통한 대표적인 장점은 다음과 같습니다.

외부 사용자가 알 필요 없는 클래스를 캡슐화하여 공개된 인터페이스를 단순화할 수 있습니다.

데이터를 은닉하여 세부 구현 내용을 숨겨서 외부 사용자는 세부 구현에 대해서 알 필요가 없습니다.

세부 구현의 내용이 외부에 노출되지 않으므로, 세부 구현을 변경하더라도 외부 인터페이스를 사용하는 곳의 영향을 최소화할 수 있습니다.

캡슐화가 잘 되어 있는 클래스는 다른 곳에서 재활용할 수 있습니다.

 

 

캡슐화를 적용한 객체지향 프로그래밍

본 예제를 통해서 캡슐화가 된 코드와 캡슐화가 되지 않은 코드를 비교해보겠습니다. 다음 예제는 제대로 캡슐화되지 않은 예제입니다.


#include <iostream>
using namespace std;
 
#define NAME_LEN 20
#define SEX_LEN 10
#define JOB_LEN 20
#define CHARACTER_LEN 20
 
class Chulsoo
{
private:
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
public:
    Chulsoo(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        cout << "Chulsoo::Chulsoo(name,sex,job,character,age,marriageStatus) 생성자 시작" << endl;
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
        cout << "Chulsoo::Chulsoo(name,sex,job,character,age,marriageStatus) 생성자 완료" << endl;
    }
    Chulsoo() 
    {
        cout << "Chulsoo::Chulsoo() 생성자 완료" << endl;
    }
    void introduce();
    void eat(char * food);
    void sleep();
    void drive(char * destination);
    void write();
    void read();
};
 
class Younghee
{
private:
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
public:
    Younghee(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        cout << "Younghee::Younghee(name,sex,job,character,age,marriageStatus) 생성자 시작" << endl;
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
        cout << "Younghee::Younghee(name,sex,job,character,age,marriageStatus) 생성자 완료" << endl;
    }
 
    Younghee() 
    {
        cout << "Younghee::Younghee() 생성자 완료" << endl;
    }
    void introduce();
    void eat(char * food);
    void sleep();
    void shopping();
};
 
class DailyLife
{
private:
    Chulsoo chulsoo; // 객체를 private 멤버로 가진다.
    Younghee younghee; // 객체를 private 멤버로 가진다.
public:
    DailyLife(Chulsoo chulsooObject, Younghee youngheeObject)
    {
        cout << "DailyLife::DailyLife(chulsooObject, youngheeObject) 생성자 시작" << endl;
        chulsoo = chulsooObject;
        younghee = youngheeObject;
        cout << "DailyLife::DailyLife(chulsooObject, youngheeObject) 생성자 완료" << endl;
    }
    void run()
    {
        chulsoo.drive("레스토랑");
        chulsoo.eat("스테이크");
        younghee.eat("스테이크");
        chulsoo.drive("집");
        younghee.sleep();
        chulsoo.write();
        chulsoo.read();
        chulsoo.sleep();
        cout << endl;
        chulsoo.introduce();
        cout << endl;
        younghee.introduce();
    }
};
 
int main(void)
{
    Chulsoo chulsoo("철수""남성""작가""diligent"32true);
    Younghee younghee("영희""여성""주부""impatient"32true);
    DailyLife dailylife(chulsoo, younghee);
    dailylife.run();
    return 0;
}
 
void Chulsoo::introduce()
{
    cout << "이름: " << name << endl;
    cout << "성별: " << sex << endl;
    cout << "직업: " << job << endl;
    cout << "성격: " << character << endl;
    cout << "나이: " << age << endl;
    cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
}
 
void Chulsoo::eat(char * food)
{
    cout << "철수는 " << food << "를 먹는다" << endl;
}
 
void Chulsoo::sleep()
{
    cout << "철수는 잔다" << endl;
}
 
void Chulsoo::drive(char * destination)
{
    cout << "철수는 " << destination << "으로 운전한다" << endl;
}
 
void Chulsoo::write()
{
    cout << "철수는 책을 쓴다" << endl;
}
 
void Chulsoo::read()
{
    cout << "철수는 책을 읽는다" << endl;
}
 
void Younghee::introduce()
{
    cout << "이름: " << name << endl;
    cout << "성별: " << sex << endl;
    cout << "직업: " << job << endl;
    cout << "성격: " << character << endl;
    cout << "나이: " << age << endl;
    cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
}
 
void Younghee::eat(char * food)
{
    cout << "영희는 " << food << "를 먹는다" << endl;
}
 
void Younghee::sleep()
{
    cout << "영희는 잔다" << endl;
}
 
void Younghee::shopping()
{
    cout << "영희는 쇼핑을 한다" << endl;
}
cs

 

캡슐화란 객체의 속성과 기능을 하나로 묶고, 세부 구현 내용은 클래스 안으로 숨기고, 외부 인터페이스를 통해서 객체에 접근할 수 있습니다. 위 예제는 캡슐화의 장점 중 하나인 데이터 은닉을 사용하여 세부 구현을 숨기므로, 클래스를 사용하는 사용자가 세부 구현을 알 필요가 없다는 사항을 어깁니다. 어떻게 하면 캡슐화를 잘 할 수 있을까요?


#include <iostream>
using namespace std;
 
class File
{
public:
    void load()
    {
        cout << "mp3 파일을 메모리에 올립니다." << endl;
    }
};
 
class PowerDevice
{
public:
    void powerUp()
    {
        cout << "mp3 play를 위한 파워를 올립니다." << endl;
    }
};
 
class Memory
{
public:
    void expand()
    {
        cout << "mp3 play를 위한 메모리를 늘립니다." << endl;
    }
};
 
class Mp3
{
private:
    File file;
    PowerDevice powerdevice;
    Memory memory;
public:
    void play()
    {
        powerdevice.powerUp();
        memory.expand();
        file.load();
        cout << "mp3를 play 합니다." << endl;
    }
};
 
int main()
{
    Mp3 mp3;
    mp3.play();
}
cs


위의 예제를 통해 세부 구현이 바뀌어도 세부 구현이 외부에 노출되지 않으므로 외부 인터페이스를 사용하는 곳의 영향을 최소화할 수 있다.

 

세부 구현은 숨겨지고 공통 인터페이스만 노출되므로 쉽게 재사용할 수 있습니다.

반응형