클래스와 객체지향
객체지향 프로그램을 실행하려면 클래스를 선언하는 과정이 필요하다.
클래스 선언 = 객체 생성의 청사진
클래스에 객체에서 처리할 행동을 결정한다.
(행동 : 객체에서 동작 수행해야 하는 처리 로직)
객체지향 (OOP : Object-Oriented Programming)
- 70년대부터 사용한 개발 방법론으로 C++, Java에서 오래 전부터 도입해서 사용했다.
- 최근 대규모 SW 설계에 유용한 OOP 개발 방식이 인기를 끌었다.
- 서비스에는 다양한 사용자 경험이 요구되는 등 각종 사항들이 증가한다.
- 프로그램의 기능이 복잡해지고 규모도 점점 커진다.
- 코드에서 처리하는 일들이 다양해진다.
- ⇒ 절차지향적 개발의 한계
⇒ 이런 이유로 객체지향 개발과 디자인 패턴이 인기를 얻었다.
- 현대 개발에서 OOP를 이해하지 않고는 고급 프로그래밍을 다루는 것이 어렵다.
- 최근 인기 있는 프레임워크들을 사용하려면 객체지향 개발 방법을 숙지해야 한다.
- 객체지향 방식을 사용해서 코딩하지 않으면 협업이 어려울 정도로 개발 환경에서 OOP가 보편화되었다.
클래스 Class
객체지향 개발의 시작 : 클래스 선언
- OOP를 지원하는 언어에선 class 키워드로 클래스를 선언한다.
- 클래스 파일 생성 시, 각 언어의 스타일 규격에 맞게 작성하는 것을 권장한다.
객체 Object
객체지향 코드의 실행 : 클래스를 통한 객체 생성
- 객체 생성을 위한 키워드 new
- new : 선언된 클래스로 객체를 생성하고 반환한다.
- 컴파일 / 인터프리터 과정에서 선언된 클래스에 따라 객체를 생성하고
이를 메모리에 할당한다.
- 언어에서 객체를 생성하는 과정 = 인스턴스화 (Instantiate)
- 인스턴스화로 생성된 객체 = 인스턴스 (Instance)
객체 사용
- 클래스 → 데이터(property)와 함수(method)를 하나의 그룹으로 묶는다. = 캡슐화
- 캡슐화된 객체의 함수를 사용하기 위해선 행위(method)를 호출해야 한다.
의존성
객체지향
- 하나의 객체로 처리할 수 있는 일은 매우 제한적이다.
- OOP = 수많은 객체의 집합을 설계하는 것
- 생성된 다수의 객체는 상호 작업을 위한 관계를 설정해주어야 한다.
- 관계 (Relation)
- 객체 간 동작을 위해 접근하는 것.
- 개별 객체는 요구되는 역할을 수행하기 위해 책임이 부여된다.
- 객체지향은 부여된 책임 간의 관계를 설정한다. → 상호 동작을 수행하여 로직을 처리한다.
- 객체의 관계 설정→ 객체 간에 상호작용이 발생한다.
- 문제를 해결하기 위해 책임 있는 객체는 서브 책임을 가진 다른 객체에 소속된 문제 해결을 위임한다.
- ⇒ 객체들은 이 과정에서 정보를 주고 받는다. = 대화 (Message)
- 객체는 서로 메시지를 전달하며 고유의 기능을 실행한다.
의존
- 객체지향에서 객체의 관계를 설정하는 것 = 의존
- 객체지향에선 절차지향과 달리 해결해야 하는 기능과 관련된 책임을 캡슐화로 분배한다.
- 객체는 각자 고유한 역할을 분담한다. → 하나의 객체만으로 동작하는 것은 의미가 없다.
- 객체의 관계 = 객체 간 결합 = 의존성
- ⇒ 캡슐화된 객체는 서로 관계를 맺고 대화를 통해 동작을 수행한다.
- 의존성의 유형
- 객체 내부에 다른 객체를 생성하는 것 ⇒ 두 객체 간 의존성 발생
- 외부에 의해 결합 관계가 발생하는 것 ⇒ 의존성 주입
객체 선택
- 객체 내부에서 다른 객체를 생성하여 의존성이 발생한 경우
- 만약 생성해야 할 클래스가 조건이나 상황에 따라 다르다면,
⇒ 조건에 맞게 구체적으로 직접 지정한 후 객체를 생성해야 한다.
- 조건에 따라 생성되는 객체가 다르다면 객체를 유연하게 생성할 수 없다.
- 만일 새로운 조건이 추가된다면 조건에 맞게 객체 생성 로직을 수정해야 한다.
- 이처럼 객체를 어떻게 생성 처리할 지에 따라 의존성과 유연성이 결정된다.
강력한 결합
- 객체의 메서드 안에 다른 객체를 생성하는 경우
→ 하나의 객체가 또 다른 객체를 생성 ⇒ 두 객체 간 결합 관계가 발생한다.
- 메서드 코드 내에서 클래스 이름을 직접 지정해 객체를 생성하는 경우
= 강력한 결합 관계 발생
- 강력한 결합 관계 발생 시,
클래스 이름 등이 변경될 때 이름을 통해 생성된 코드를 모두 직접 찾아 수정해야 한다.
- 강력한 결합 코드의 영향
- 향후 유연한 코드 확장을 방해한다.
- 변경과 수정을 어렵게 만드는 원인이 된다.
클래스 변경
- 객체의 의존성과 결합 관계의 문제점을 개선한 것 = 디자인 패턴
- 팩토리 패턴 : 객체 생성 시 확장과 수정을 쉽게 하기 위한 설계 방법이다.
- 객체지향에서 객체 간 의존성은 객체를 생성할 때마다 발생한다.
- 코드에서 직접 생성한 객체 → 의존성이 강력하다 → 유지 보수 및 수정이 어렵다.
- 의존 관계가 강력한 결합 관계는 느슨한 결합 관계로 변경해서 문제를 해결할 수 있다.
- 팩토리 패턴은 클래스 구조를 변경하여 객체 구조를 느슨한 관계로 변경한다.
의존성 주입
객체의 의존성
- 내부적 발생
- 외부적 발생 → 외부적 요인으로 결합 = 의존성 주입
- 의존성 주입 발생 ⇒ 객체는 복합 객체의 형태
복합 객체
- 하나의 객체가 다른 객체의 정보를 가진 구조
- 객체 정보는 클래스의 프로퍼티 값을 통해 다른 외부 객체를 가리킨다.
- 복합 객체 → 종속적, 연관 관계를 갖는다.
- ex) 객체 생성 시, 생성자에 매개변수를 통해 외부 객체를 전달 및 프로퍼티에 저장하는 경우
생성과 주입
- 의존성 주입 : 객체에 또 다른 객체의 정보를 전달하는 것
- 외부에서 전달 받은 객체(정보)를 내부 프로퍼티에 저장한다. ⇒ 복합 구조 형성
의존 관계의 문제점
팩토리 패턴의 목적 : 개체 생성 시 발생하는 강력한 의존 관계를 느슨하게 만드는 것
new 키워드
- : 선언된 클래스를 기반으로 객체를 생성하는 인스턴스화 작업을 수행한다.
- 객체지향에서 객체 생성은 피할 수 없는 과정이다.
인스턴스화를 통해 객체가 생성되면 의존성도 발생한다.
⇒ new 키워드로 직접 객체를 생성 = 강력한 결합 관계의 코드
- 하지만, new 키워드를 사용하지 않고 객체를 생성할 순 없다.
- new 키워드는 객체를 생성하는 유일한 방법이다.
변화 대응
- 객체 생성 과정에서 강력하지 않은 연관 관계를 만들 방법
= new 키워드를 사용하지 않고 객체를 만들 방법
= 생성 패턴
- 생성 패턴 중에서도 팩토리 패턴은 객체의 생성을 별개의 클래스로 구축하여 위임 처리한다.
생성 위임
- 생성 패턴
- 객체 생성을 위임하여 별개의 클래스로 분리
- 객체 생성 과정을 담당할 별도의 클래스를 선언
- ⇒ 프로그램 내, 필요한 객체를 생성하고 관리하는 캡슐화된 클래스를 선언한다.
- 프로그램 내, 객체 생성이 필요한 경우, 분리 설계된 팩토리 객체에 생성을 위임한다.
- 실제 코드가 new 키워드를 직접 사용하지 않고 팩토리 객체를 호출하는 것으로 대체한다.
- 디자인 패턴에서 객체를 생성하고 캡슐화하여 위임하는 것 = 공장 (Factory)
객체 공장
- (개발자마다 객체 생성 방법이 다양하지만) 팩토리 패턴은
- new 키워드를 코드에 직접 작성하지 않는다.
- 일정한 규격에 맞춘 메서드나 함수를 이용한다.
- ⇒ 공장에서 물건 찍어 내 듯 객체를 생성한다.
= 객체 생성 처리를 규격화하여 관리한다.
- 디자인 패턴은 강한 연관 관계를 느슨한 관계로 변환한다.
느슨한 관계는 보다 다양한 코드 변화를 처리할 수 있다.
- 팩토리는 객체의 생성 작업을 분리한다.
- 분리된 객체의 생성 처리는 크게 2가지로 나뉜다.
- 클래스로 분리 : 일반적인 팩토리.
개별 클래스로 분리해 위임을 처리하는 방법이다.
- 메서드로 분리 : 단순 팩토리 (Simple Factory)라고 한다.
기존 클래스에 생성 메서드만 추가해 객체를 생성, 처리한다.
팩토리 패턴
객체 생성을 위임할 수 있는 클래스를 정의한다.
- 팩토리 패턴의 클래스 :
- 객체 생성을 담당한다.
- 객체의 생성을 느슨한 관계가 되도록 처리한다.
느슨한 결합
- 코드에서 직접 클래스의 이름을 지정해 객체를 생성하지 않는다.
별도의 객체에 필요한 객체를 생성하도록 책임을 위임한다.
#include "../objs/Object.h"
#ifndef FACTORY_H
#define FACTORY_H
class Factory
{
public:
Factory(){};
~Factory(){};
static Object get_instance() { return Object(); };
};
#endif
#include "factory/Factory.h"
int main() {
Object obj = Factory::get_instance();
obj.set_name("OBJ_1");
cout << obj.get_name() << endl;
return 0;
}
동적 팩토리
- 팩토리 패턴은 분리된 factory 객체를 통해 필요로 하는 모든 객체의 생성을 위임한다.
- 코드에서 직접 new 키워드를 사용하거나 직접 필요한 객체를 사용하지 않는다.
⇒ 강력한 결합 관계 발생을 줄인다.
- 기존의 클래스를 수정하여,
Factory 클래스의 get_instance() 메서드를 정적(static) 타입으로 호출한다.
- 팩토리 패턴으로 강력한 의존 관계 문제가 해결되었다.
장단점
- 단점 : 팩토리 패턴 사용으로 객체를 직접 생성하는 것과 달리
Factory 객체를 통해 호출(call)하는 처리 과정이 한 단계 더 필요해짐.
⇒ 불필요한 호출 증가로 프로그램 성능 저하를 초래한다.
- 장점 : 객체 생성을 다른 객체에 위임함으로써
- 내부적인 결합을 제거했다. = 강력한 의존 관계 문제가 해결
- 동적으로 객체를 관리할 수 있다.
클래스의 선택
- Factory 객체에서 Object 객체 외에도 비슷한 성격의 다른 클래스 ObjectOther객체를 생성해야 하는 경우
(= 새로운 객체가 추가된 경우)
- get_instance() 메서드에 구분을 위한 매개변수와 매개변수에 따라 다른 객체를 생성하도록 조건을 추가한다.
#ifndef FACTORY_OTHER_H
#define FACTORY_OTHER_H
#include <iostream>
using namespace std;
class Object;
class FactoryOther
{
public:
static const string TYPE_A;
static const string TYPE_B;
FactoryOther();
~FactoryOther();
static Object get_instance(string);
};
#endif
~
Object FactoryOther::get_instance(string type){
if(type == FactoryOther::TYPE_A){
cout << "instantiate Type A" << endl;
return Object();
}
else if (type == FactoryOther::TYPE_B){
cout << "instantiate Type B" << endl;
return ObjectOther();
}
}
~
#include "objs/Object.h"
#include "factory/FactoryOther.h"
#include "factory/FactoryOther.cpp"
int main() {
Object obj2 = FactoryOther::get_instance(FactoryOther::TYPE_A);
return 0;
}
- 생성할 객체를 선택할 수 있는 조건 로직을 추가한다.
- 외부에서 매개변수 값을 받아서 처리
⇒ 조건에 따라 객체를 선택적으로 생성하고 반환할 수 있다.
- 조건이 추가되고 변경될 때마다 다른 객체를 생성하고 반환할 수 있다.
형식 안정성
- 팩토리 클래스는 프로그램 내부에서 필요한 객체를 생성하는 역할을 수행한다.
다수의 객체를 생성할 때는 이를 선택하기 위해 매개변수를 전달한다.
- 매개변수의 타입은 대부분 문자열을 사용한다.
- 전달받은 매개변수를 판별하지 못해 정확히 생성해야 할 객체를 결정하지 못하는 경우
(조건 불일치)
- ⇒ 프로그램이 안전하게 동작하도록 하기 위해 상수(const)를 사용하기도 한다.
const string FactoryOther::TYPE_A = "TYPE_A";
const string FactoryOther::TYPE_B = "TYPE_B";
단순 팩토리 Simple Factory
팩토리 패턴의 특징과 처리 로직을 간략하게 작성한 것
기존의 단점이었던 별개의 Factory 객체를 생성하지 않는다.
대신, 자신의 객체에 필요한 객체를 생성하는 전용 메서드를 추가한다.
메서드
- 전형적인 팩토리 패턴은 생성, 관리하는 객체를 별도의 클래스로 분리하여 위임한다.
- → 새로운 클래스가 늘어날수록 추적 관리할 코드가 많아진다.
- 단순 팩토리는 이런 관점에서 등장해서 별도의 팩토리 클래스를 생성하지 않고,
자기 자신의 클래스에 객체 생성을 처리할 수 있는 전용 메서드를 추가한다.
단순 팩토리
- 객체 생성 과정이 복잡하지 않을 경우,
추가 클래스 파일을 생성하지 않고도 팩토리 패턴을 적용할 수 있다.
- 단순 팩토리 = 정적(static) 팩토리 패턴
장점과 단점
장점
- 생성과 관련된 모든 처리를 별도의 클래스 객체로 위임할 수 있다.
- 사용과 생성을 분리하는 과정에서 중복된 코드를 정리하는 효과가 있다
- 유연성과 확장성이 개선된다.
- 개발 과정에서 클래스 이름 변경 시, 일일이 수정하지 않아도 된다.
팩토리 객체를 통해서 손쉽게 변경할 수 있다.
- 어떤 객체를 생성하지 모르는 초기 단계 코드에 매우 유용하다.
단점
- 객체 생성을 위임하는데 별도의 새로운 클래스가 필요하다
- 관리할 클래스 파일이 늘어난다.
UML
Factory Pattern Class Diagram