개발/개발 공통

[객체지향 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를 활용하여 기능 추가를 쉽게 만들고 기존 코드에 영향을 최소화하는 방식은 규모가 커지거나 의존성이 커지는 프로젝트에 매우 필수적인 요소라고 할 수 있을 것입니다.

 

반응형