개발/개발 공통

[객체지향 SOLID 원칙] 개방 폐쇄 원칙(OCP)의 이해와 예제

growing-dev 2023. 5. 26. 13:47
반응형

SRP 원칙에 이어서 개방 폐쇄 원칙인 OCP(Open Closed Principle)에 대해서 알아보도록 하겠습니다.

 

 

 개방 폐쇄 원칙(OCP)의 이해와 예제 

 

 

 

 

 개방 폐쇄 원칙이란

 

소프트웨어 개발 작업에 이용된 많은 모듈 중에 하나에 수정을 가할 때 그 모듈을 이용하는 다른 모듈을 줄줄이 고쳐야 한다면, 이와 같은 프로그램은 수정하기가 어렵습니다. 개방-폐쇄 원칙은 시스템의 구조를 올바르게 재조직(리팩토링)하여 나중에 이와 같은 유형의 변경이 더 이상의 수정을 유발하지 않도록 하는 것입니다. 개방-폐쇄 원칙이 잘 적용되면, 기능을 추가하거나 변경해야 할 때 이미 제대로 동작하고 있던 원래 코드를 변경하지 않아도, 기존의 코드에 새로운 코드를 추가함으로써 기능의 추가나 변경이 가능합니다.

2023.05.26 - [개발/개발 공통] - [객체지향 SOLID 원칙] 단일 책임 원칙(SRP)의 이해와 예제

 

인터페이스를 추가하여 다형성을 활용하는 것이 가장 대표적인 예입니다.

 

 

 예제

 

아래와 같이 greet 이라는 인사와 관련된 클래스의 예제를   봅니다.

hi라는 내부 string에 따라 상황에 맞는 인사를 return 하도록 한 예제입니다.

만약 지속적으로 인사 예제가 추가되는 경우 Greet이라는 클래스가 계속 수정되고 테스트되어야 합니다. 

지금 예제는 다소 단순하고 가독성이 나쁘지 않긴 하지만 조금 크거나 내가 잘 모르는 클래스에 기능을 추가해야 한다고 생각하면 조금 불편해 보입니다.

이런 경우 OCP 의 원칙을 위반한다고 볼 수 있고 인터페이스를 통해 개선해 볼 수 있습니다.

 

#include <iostream>
using namespace std;

class Greet {
public:
	string greet() {
		if (hi == "morning") {
			return "Good morning";
		}
		else if (hi == "casual") {
			return "What up";
		}
		else {
			return "Hi";
		}
	}

	void setHi(string _hi) {
		hi = _hi;
	}

private:
	string hi;
};

 

sayHi 하는 인터페이스 클래스를 추가하였습니다.

Greet 클래스는 sayHi 인터페이스에만 의존하여 say 만 호출합니다. (의존성 역전 원칙과도 연관됩니다.)

실제 각 상황별 say는 각 클래스 객체는 sayHi 인터페이스를 구현하고 say를 수행합니다. 

 

#include <string>
#include <iostream>

using namespace std;

class sayHi {
public:
	virtual string say() = 0;
};

class Morning : public sayHi {
public:
	string say() override {
		return "Good morning";
	}
};

class Casual : public sayHi {
public:
	string say() override {
		return "What up?";
	}
};

class Default : public sayHi {
public:
	string say() override {
		return "Hi";
	}
};

class Greet {
public:
	string greet() {
		return hi->say();
	}

	void setHi(sayHi* _hi) {
		this->hi = _hi;
	}

private:
	sayHi* hi;
};

 

 

테스트는 아래와 같습니다.

Greet을 하나 생성하고 setHi를 통해 원하는 종류의 인사를 주입시켜 줍니다.

이후 greet() 을 호출해주기만 하면 각 상황에 맞는 인사가 return 됩니다. 지금의 경우 Morning을 할당했으므로 Good morning이 반환되어 테스트가 성공함을 알 수 있습니다.

 

TEST(Hi, HiTest) {
	Greet* g = new Greet();
	g->setHi(new Morning());
	EXPECT_EQ(g->greet(), "Good morning");
}

 

테스트 결과

 

 

 결론

 

객체지향에서 기존 절차지향과 가장 다른 것이 이 다형성과 관련된 것이라고 생각합니다. 인터페이스, Override를 활용하여 기능 추가를 쉽게 만들고 기존 코드에 영향을 최소화하는 방식은 규모가 커지거나 의존성이 커지는 프로젝트에 매우 필수적인 요소라고 할 수 있을 것입니다.

 

반응형