프로그래밍/C++

객체지향 프로그래밍

Rozentea 2024. 1. 24. 18:02
해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~

 

1. 객체지향 프로그래밍 (Object - Oriented Programming) 개요


객체지향 프로그래밍(Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나이다.

 

오늘날 많은 유명한 프로그래밍 언어(Java, C++, Python, PHP, Ruby, Object-C)는 객체지향 프로그래밍을 지원한다.

"객체지향"이라는 개념은 불행히도 명확한 정의가 없는 것이 특징이다. 우리가 어떠한 개념을 이해하려할 때, 그 개념의 특성(attribute, property)을 통해 이해하는 것처럼 객체지향도 객체지향의 특성을 통해 이해할 수밖에 없다. 

객체지향 프로그래밍은 실세계에 존재하고 인지하고 있는 객체(Object)를 소프트웨어의 세계에서 표현하기 위해 객체의 핵심적인 개념 또는 기능만을 추출하는 추상화(abstraction)를 통해 모델링하려는 프로그래밍 패러다임을 말한다.

 

즉, 객체지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다.

보다 쉽게 설명하자면 우리가 주변의 실세계에서 사물을 인지하는 방식을 프로그래밍에 접목하려는 사상을 의미한다.

 

객체지향 프로그래밍은 함수들의 집합 혹은 단순한 컴퓨터의 명령어들의 목록이라는 전통적인 절차지향 프로그래밍과는 다른 관계성있는 객체들의 집합이라는 관점으로 접근하는 소프트웨어 디자인으로 볼 수 있다.

 

각 객체는 메시지를 받을 수도 있고, 데이터를 처리할 수도 있으며, 또다른 객체에게 메시지를 전달할 수도 있다.

각 객체는 별도의 역할이나 책임을 갖는 작은 독립적인 기계 또는 부품으로 볼 수 있다.

 

객체지향 프로그래밍은 보다 유연하고 변경이 쉽게 만들고 개발과 유지보수를 간편하게 해 확장성 측면에서도 유리한 프로그래밍을 하도록 의도되었고, 대규모 소프트웨어 개발에 널리 사용되고 있다.

 

그러나 지나친 프로그램의 객체화 경향은 실제 세계의 모습을 그대로 반영하지 못한다는 비판을 받기도 한다.

 

객체지향 프로그래밍의 간단한 역사

이러한 객체지향 언어의 시초는 1960년 노위지안 컴퓨팅 센터의 조한 달과 크리스틴이 발표한 시뮬라67이다.

시뮬라67이 채택하고 있는 가장 중요한 개념은 클래스의 도입으로서 이 아이디어는 스몰토크, C++등 에도 사용되었다.

하지만 시뮬라67 발표 후 10여년 간 객체 지향 언어는 전혀 주목 받지 못하였다.

 

객체지향 언어는 1990년대 초반에 많은 발전이 있었다.

스몰토크와 에이다(스몰토크 이후 1980년대 초반에 개발된 객체 지향 프로그래밍 언어)가 가지고 있던 문제들을 해결해 나가는 과정으로 발전했다. C++은 C를 기반으로 하고 있어 프로그래머들의 인기를 받고 있지만 그로 인해 객체 지향성을 제대로 반영하지 못하고 있다는 비난을 받기도 했다.

 

2. 객체지향 프로그래밍의 장점


개요에서 간단히 설명했듯이 객체지향적으로 프로그램을 설계하면 여러 이점들이 있다.

가장 큰 이점 중 하나는 객체 지향적 설계를 통해서 프로그램을 보다 유연하고 변경이 용이하도록 만들 수 있다는 점이다.

마치 컴퓨터 부품을 갈아 끼울 때, 해당 부품만 쉽게 교체하고 나머지 부품들을 건드리지 않아도 된다는 것처럼 소프트웨어를 설계할 때 객체 지향적 원리를 잘 적용해 둔 프로그램은 각각의 부품들이 각자의 독립적인 역할을 가지기 때문에 코드의 변경을 최소화하고 유지보수를 하는 데 유리하다.

 

더 나아가, 코드의 재사용을 통해 반복적인 코드를 최소화하고, 코드를 최대한 간결하게 표현할 수 있다.

또한 객체지향 프로그래밍은 실제 우리가 보고 경험하는 세계를 최대한 프로그램 설계에 반영하기 위해 지속적인 노력을 통해 발전해왔기 때문에, 보다 인간 친화적이고 직관적인 코드를 작성하기에 용이하다.

 

객체 지향적 설계의 이점들을 가장 잘 살릴 수 있는 방향으로 5가지의 원칙을 지키며, 4가지의 특징들을 발전해 왔다고 할 수 있습니다.

 

3. 객체지향 프로그래밍의 특징


우선 각 특징을 살펴보기 이전 클래스와 객체에 대해 살펴보자

 

00. 클래스 (Class)

클래스는 추상 자료형을 표현하기 위해 사용되는 표현 방법이다.

클래스는 사용자가 정의한 사용자 정의형 자료형이고 기존 자료형과 마찬가지로 자료형일 뿐 메모리에 저장된 변수가 아니다.

class CPet
{
private:
    string str_name;
    ePET_TYPE ePet_Type;

public:
    void eat();
    void sleep();

public:
    CPet();
    ~CPet();
};

위 코드 처럼 Pet이라는 것을 표현하기 위해 Pet이 지닌 속성과 기능을 멤버 변수와 멤버 함수로 묶어 정의한 것을 의미한다.

 

00. 객체 (Object)

객체는 객체지향 프로그래밍의 가장 기본적인 단위이자 시작점이라 할 수 있다.

객체는 클래스에서 정의한 것을 토대로 실제 메모리에 할당된 것으로 실제 프로그램에서 사용되는 데이터이다.

 

CPet* Object = new CPet();

위와 같이 클래스를 지정해 뒀다면, 해당 클래스를 동적할당해 실제 메모리에 할당하는 것을 객체라고 한다.

 

객체지형 프로그래밍의 특징으로는 추상화, 상속, 다형성, 캡슐화가 있다.

 

1. 추상화 (Abstration)

추상이라는 용어의 사전적 의미를 보면 "사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 표현하여 파악하는 것"이라고 정의되어 있습니다. 여기서 핵심이 되는 개념은 "공통성과 본질을 모아 추출"한다는 것입니다.

 

객체지향 프로그래밍에서 의미하는 추상화는 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 의미합니다.

 

예를 들어 고양이와 강아지는 모두 애완동물이라는 상위 개념이 존재하고, 이름과 종류를 가지고 있고, 먹고 잔다는 공통점을 가집니다.

이것을 C++를 사용해 표현하면 다음과 같습니다.

class CPet
{
private:
    string str_name;
    ePET_TYPE ePet_Type;

public:
    void eat();
    void sleep();

public:
    CPet();
    ~CPet();
};

이름과 종류가 있다는 것을 클래스의 멤버 변수로 str_name과 ePet_Type으로 선언하고

먹고 잔다는 기능적 공톰점을 각각 eat(), sleep()으로 구현해 클래스의 멤버 함수로 만들어 주었습니다.

 

이처럼 객체의 공통적인 속성과 기능을 추출해 정의하는 것을 추상화라고 합니다.

 

2. 상속 (Inheritance)

상속이란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 것을 의미한다.

 

추상화 예시의 연장선으로 고양이와 강아지의 공통적인 속성과 기능들이 있는 CPet을 상속 받아 하위 클래스로 만들 수 있습니다.

class CCat : 
    public CPet
{
private:
	eCAT_TYPE eCatType;

public:
    CCat();
    ~CCat();
};



class CDog :
    public CPet
{
private:
	eDOG_TYPE eDogType;

public:
    CDog();
    ~CDog();
};

 

상속을 받으면 공통적인 속성과 기능은 상위 클래스에서만 작성하고, 하위 클래스에서는 각각 차별적인 고양이의 종류(껌냥이, 황냥이)와 강아지의 종류(진돗개, 말티즈)등의 속성만을 작성할 수 있게됩니다.

 

이처럼 클래스간 공유되는 속성과 기능을 상위 클래스로 만들고, 공통되지 않는 속성과 기능을 하위클래스로 만들어 기존 클래스를 재활용해 새로운 클래스를 만드는 것을 의미합니다.

 

이를 통해 공통된 속성과 기능을 한 번만 작성해 재활용할 수 있다는 장점이 있습니다.

 

뿐만아니라 공통된 기능이더라도 함수 오버라이딩을 이용해 각 하위 클래스 별로 기능을 재정의 할 수도 있습니다.

 

3. 다형성

객체 지향 프로그래밍에서의 다형성이란 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미합니다. 좀 더 구체적으로 상위 클래스 타입의 참조변수로 하위 클래스 객체를 참조할 수 있도록 하는 것 입니다.

void main()
{
    CPet* Object = new CPet();

    CCat* CatObject = new CCat();
    CDog* DogObject = new CDog();

    CPet* Cat = dynamic_cast<CPet*>(CatObject);
    CPet* Dog = dynamic_cast<CPet*>(DogObject);
    Cat->eat();
    Dog->eat();
}

이 처럼 하위 객체인 CCat과 CDog는 CPet*로 참조할 수 있습니다.

즉, 고양이와 강아지를 애완동물로 지칭할 수 있는 것과 유사합니다.

 

본래 다형성이란 "어떤 객체의 속성이나 기능이 상황에 따라 다른 역활을 수행할 수 있는 성질"을 의미합니다.

비유적으로 표현하자면, 어떤 중년의 남성이 있다고 했을 때 그 남자의 역할이 아내에게는 [남편], 자식에게는 [아버지], 부모님에게는 [자식] 등 상황과 환경에 따라서 달라지는 것과 비슷하다고 할 수 있습니다.

 

대표적으로 위에서 말한 함수 오버라이딩과 함수 오버로딩이 있습니다.

class CPet
{
private:
    string str_name;
    ePET_TYPE ePet_Type;

public:
    void eat() { cout << "밥을 먹는다."; }
    void sleep();

public:
    CPet();
    ~CPet();
};

class CCat : 
    public CPet
{
public:
    void eat() { cout << "고양이가 밥을 먹는다."; }
    void sleep();

public:
    CCat();
    ~CCat();
};

class CDog :
    public CPet
{
public:
    void eat() { cout << "강아지가 밥을 먹는다." }
    void sleep();

public:
    CDog();
    ~CDog();
};

이 처럼 eat()을 오버라이딩하면 객체의 클래스가 무엇이냐에 따라 eat()이라는 함수는 각각 다른 메세지를 출력해주게 된다.

 

즉, 같은 이름의 함수가 상황에 따라 다른 기능을 수행하는 것이 다형성입니다.

 

4. 캡슐화 (Encapsulation)

캡슐화란 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말합니다.

객체 지향 프로그래밍에서 캡슐화를 하는 이유로 크게 2가지가 있습니다.

1. 데이터 보호 (data protection) - 외부로부터 클래스에 정의된 속성와 기능들을 보호한다.

2. 데이터 은닉(data hiding) - 내부의 동작을 감추고 외부에는 필요한 부분만 노출한다.

 

4. 객체지향 프로그래밍의 5가지 원칙 (S.O.L.I.D)


SOLID 원칙이란 객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙 (SRP, OCP, LSP, ISP, DIP)를 말한다.

이러한 원칙은 적용하는 순서는 없고 SOLID라는 단어는 철자법과 눈에 띄게 만들기 위해 배열했을 뿐이다.

프로젝트에 적용할 원칙의 수는 코드의 구성에 따라 다른데, 각 원칙은 특정 문제를 해결하기 위한 지침일 뿐이며, 코드에 해당 문제가 없으면 원칙을 적용할 이유가 없다.

 

SOLID의 5가지 원칙들은 서로 독립된 개별적인 개념이 아니라 서로 개념적으로 연관되어 있다.

원칙끼리 서로가 서로를 이용하기도 하고 포함하기도 한다.

 

1. 단일 책임 원칙 (Single Reponsibility Principle, SRP)

단일 책임 원칙은 "클래스(객체)는 단 하나의 책임만 가져야 한다"는 원칙이다.

여기서 '책임'이라는 의미는 하나의 '기능 담당'으로 보면 된다.

즉, 하나의 클래스는 하나의 기능 담당하여 하나의 책임을 수행하는데 집중되도록 클래스를 따로따로 여러개 설계하라는 원칙이다.

 

만일 하나의 클래스에 기능이 여러개 있다면 기능 변경(수정)이 일어났을 때 수정해야할 코드가 많아진다.

예를 들어 A를 고쳤더니 B를 수정해야하고 또 C를 수정해야하고, C를 수정했더니 다시 A를 수정해야 하는 마치 책임이 순환되는 형태가 되어버린다.

따라서 SRP 원칙을 따름으로써 한 책임의 변경으로 부터 다른 책임의 변경으로의 연쇄작용을 극복할 수 있게 된다.

 

최종적으로 단일 책임 원칙의 목적은 프로그램의 유지보수성을 높이기 위한 설계 방법이다.

 

이때 책임의 범위는 딱 정해져있는 것이 아니고, 어떤 프로그램을 개발하느냐에 따라 개발자마다 생각 기준이 달라질 수 있다. 따라서 단일 책임 원칙에 100% 완벽한 해답은 없다.

 

2. 개방 폐쇄 원칙 (Open Closed Principle, OCP)

개방 폐쇄 원칙은 "클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야 한다"는 원칙이다.

기능 추가 요청이 오면 클래스 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화하도록 프로그램을 작성해야 하는 설계 기법이다.

[확장에 열려있다] - 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 큰 힘을 들이지 않고 애플리케이션의 기능을 확장할 수 있음.

[변경에 닫혀있다] - 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정을 제한함.

더보기

확장이 열리고 변경이 닫혀있는 예는 상속구조를 생각하면 편할 것 같다.

Pet이라는 클래스가 이름, 타입을 멤버 변수로 지니고, 멤버 함수로 eat(), sleep()을 지니고 있을 때

Cat이라는 클래스는 Pet 클래스를 상속받아 만들어 생성자에서 이름과 타입을 지정하고, eat()과 sleep()을 만들어 Cat만의 eat(), sleep() 기능을 구현하는 것을 말하는 듯하다.

이떄, eat(), sleep() 함수를 구현할 때, Cat쪽에서 Pet의 함수 수정이 제한되기 때문에 이러한 점을 말하는 것 같다.

 

어렵게 생각할 필요없이, OCP 원칙은 추상화 사용을 통한 관계 구축을 권장한다는 의미이다.

즉, 다형성과 확장을 가능케 하는 객체지향의 장점을 극대화하는 기본적인 설계원칙이다.

 

3. 리스코프 치환 원칙 (Listov Substitution Principle, LSP)

리스코프 치환 원칙은 "서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다."는 원칙이다.

쉽게 말하면 LSP는 다형성 원리를 이용하기 위한 원칙 개념으로 보면 된다.

 

간단히 말하면 리스코프 치환 원칙이란, 다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 매서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미한다.

 

따라서 기본적으로 LSP 원칙은 부모 메서드의 오버라이딩을 조심스럽게 따져가며 해야한다.

왜냐하면 부모 클래스와 동일한 수준의 선행 조건을 기대하고 사용하는 프로그램 코드에서 예상치 못한 문제를 일으킬 수 있기 때문이다.

 

4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

인터페이스 분리 원칙은 "인터페이스를 각각 사용에 맞게 끔 잘게 분리해야 한다"는 설계 원칙이다.

SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조하는 것으로 보면 된다.

 

즉, SRP 원칙의 목표는 클래스 분리를 통하여 이루어진다면, ISP 원칙은 인터페이스 분리를 통해 설계하는 원칙이다.

 

ISP 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공하는 것이 목표이다.

 

다만 ISP 원칙의 주의해야할 점은 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스들을 분리하는 행위를 가하지 말하야 한다.

(인터페이스는 한번 구성하였으면 왠만해서는 변하면 안되는 정책 개념)

 

5. 의존 역전 원칙 (Dependency Inversion Principle, DIP)

의존 역전 원칙은 "어떤 클래스를 참조해서 사용해야하는 상황이 생긴다면, 그 클래스를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조해야 한다"는 원칙이다.

 

쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다.

의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존하라는 것이다.

5. 최종 정리


객체지향 프로그래밍은 Object - Oriented Programming으로 OOP라고도 줄여서 말합니다.

객체지향 프로그래밍은 컴퓨터 프로그래밍 패러다임 중 하나로, 실세계에 존재하는 객체를 소프트웨어 세계에서 표현하기 위해 객체의 핵심적인 개념 또는 기능만을 추출하여 추상화(abstraction)를 통해 모델링하려는 프로그래밍 패러다임입니다.

이러한 객체지향 프로그래밍은 크게 2가지 장점이 있습니다.

객체의 공통된 부분을 묶어 클래스를 만들고 차이점들은 해당 클래스를 상속받아 확장해 만들기 때문에 코드 재사용이 용이하고,

수정할 부분을 일일이 찾아 수정해야하는 절차 지향 프로그래밍과 달리 클래스 내부에 있는 멤버 함수, 멤버 변수들을 쉽게 찾아 수정할 수 있기 때문에 유지보수가 쉽다는 장점이 있습니다.

이러한 장점으로 대형 프로젝트에서 사용되기에 적합합니다.

반면 단점으로는 처리 속도가 상대적으로 느리고, 객체가 많아지면 용량이 커질 수 있다는 점입니다.

 

객체지향 프로그래밍의 특징으로는 추상화, 상속, 다형성, 캡슐화가 있습니다.

 

객체 지향 프로그래밍의 원칙로는 5가지를 따르는데, 각 특성의 앞글자를 따 SOLID라고도 부릅니다.

5가지 원칙으로는 각각 단일 책임 원칙, 개방 폐쇠 원칙, 리스코프 치환 원칙, 인터페이스 분리 원칙, 의존 역전 원칙이 있습니다.

 

 

6. 참고 문헌


 

객체 지향 프로그래밍 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 객체 지향 프로그래밍(영어: Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나이다. 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목

ko.wikipedia.org

 

 

Object-Oriented Programming | PoiemaWeb

오늘날 많은 유명한 프로그래밍 언어(Java, C++, C#, Python, PHP, Ruby, Object-C)는 객체지향 프로그래밍을 지원한다.

poiemaweb.com

 

 

객체 지향 프로그래밍의 4가지 특징ㅣ추상화, 상속, 다형성, 캡슐화 -

객체 지향 프로그래밍은 객체의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임을 의미합니다. 객체 지향 프로그래밍의 기본적인 개념과 그 설계를 바르게 하기 위

www.codestates.com

 

 

💠 객체 지향 설계의 5가지 원칙 - S.O.L.I.D

객체 지향 설계의 5원칙 S.O.L.I.D 모든 코드에서 LSP를 지키기에는 어려움. 리스코프 치환 원칙에 따르면 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대신하더라도 의도에 맞게 작동되어

inpa.tistory.com