오늘은 객체지향적 설계에 대해배워보자.
SOLID 원칙을 이해하고 설계할 수 있다.응집도는 클래스 내 모듈들이 얼마나 관련되어 있는지를 나타낸다.
즉, 응집도가 높은게 좋은거다.
클래스 내 관련없는 모듈들이 존재하게 되면, 클래스 내부가 변경되는 경우가 많고, 확장되기도 쉽지 않다.
응집도가 낮은 경우
👉
응집도가 낮은 경우는, 서로 관련 없는 모듈이 class에 있는 경우 이다.
목적이 하나인 팀과 각자 다른 생각을 하고 있는 팀을 생각하면 편하다.
우리의 목적은 피자 배달 입니다. 아래의 경우엔 관련없는 모듈들이 모여있다고 할 수 있다.
즉 응집도가 낮다.
각 기능들이 하나의 목적이 아닌 따로 노는 느낌을 준다.
1️⃣ 피자 배달
2️⃣ 웹사이트 디자인
3️⃣ 회사 마케팅
4️⃣ 창고 관리
응집도가 높은 경우
👉
응집도가 높은 경우는 서로 관련 있는 모듈들만 하나의 class에 있는 경우 이다.
아래의 경우에는 응집도가 높다고 할 수 있습니다. 즉 좋은 설계 이다.
1️⃣ 피자 배달 경로 확인
2️⃣ 주문한 고객 대응
3️⃣ 배달 예상 시간 측정
💡
아래 기능을 제공하는 코드를 구현해보자.
1️⃣ 특정 문자열을 받고 메시지를 출력
2️⃣ 두 수의 합을 반환
3️⃣ 특정 문자열을 받고 역으로 출력
그리고 추가적으로 아래 기능이 추가 되는 상황 이다
1️⃣ 두 수의 곱을 반환하는 기능 추가
2️⃣ 특정 문자열을 받고 메시지를 출력하기 전 대문자로 변환함
응집도가 낮은 코드
💡
Utility 클래스 하나에 모든 걸 구현해보겠다.
👉
대부분 객체지향 프로그래밍을 처음 하시는 분들이 이런 구현을 많이 시도 한다.
class를 사용하긴 하는데…. class하나에 모든 걸 다 집어넣는 구조처럼
이것은 좋은 설계일까? 아래와 같은 이유로 좋은 설계라고 보기 어렵습다.
1️⃣ 목적이 다른 기능이 섞여 있다.
2️⃣ 하나의 class에 모든 기능이 집약되어 있다.

[코드스니펫] 기능을 추가 전 예시코드
#include <iostream>
#include <string>
using namespace std;
class Utility {
public:
void printMessage(const string& message) {
cout << "Message: " << message << endl;
}
void calculateSum(int a, int b) {
cout << "Sum: " << (a + b) << endl;
}
void reverseString(const string& str) {
string reversed = string(str.rbegin(), str.rend());
cout << "Reversed: " << reversed << endl;
}
};
int main() {
Utility util;
util.printMessage("Hello");
util.calculateSum(5, 10);
util.reverseString("world");
return 0;
}
[코드스니펫] 기능을 추가 후 예시코드
#include <iostream>
#include <string>
#include <algorithm> // for transform
using namespace std;
class Utility {
public:
void printMessage(const string& message) {
string upperMessage = message;
transform(upperMessage.begin(), upperMessage.end(), upperMessage.begin(), ::toupper);
cout << "Message: " << upperMessage << endl;
}
void calculateSum(int a, int b) {
cout << "Sum: " << (a + b) << endl;
}
void calculateProduct(int a, int b) {
cout << "Product: " << (a * b) << endl;
}
void reverseString(const string& str) {
string reversed = string(str.rbegin(), str.rend());
cout << "Reversed: " << reversed << endl;
}
};
int main() {
Utility util;
util.printMessage("Hello");
util.calculateSum(5, 10);
util.calculateProduct(5, 10);
util.reverseString("world");
return 0;
}
응집도가 높은 코드
👉
클래스를 나눠서 구현 하겠다.
각 목적에 맞는 클래스를 따로 구현하면 아래와 같은 장점들이 있다.
1️⃣ 기능 변경시 특정 class만 영향을 받는다.
2️⃣ 관련 class끼리 정보를 공유 한다.

[코드스니펫] 기능 추가 전 예시코드
#include <iostream>
#include <string>
#include <algorithm> // for transform
using namespace std;
class MessageHandler {
public:
void printMessage(const string& message) {
cout << "Message: " << message << endl;
}
};
class Calculator {
public:
void calculateSum(int a, int b) {
cout << "Sum: " << (a + b) << endl;
}
};
class StringManipulator {
public:
void reverseString(const string& str) {
string reversed = string(str.rbegin(), str.rend());
cout << "Reversed: " << reversed << endl;
}
};
int main() {
MessageHandler messageHandler;
messageHandler.printMessage("Hello");
Calculator calculator;
calculator.calculateSum(5, 10);
StringManipulator stringManipulator;
stringManipulator.reverseString("world");
return 0;
}
[코드스니펫] 기능 추가 후 예시코드
#include <iostream>
#include <string>
#include <algorithm> // for transform
using namespace std;
class MessageHandler {
public:
void printMessage(const string& message) {
string upperMessage = message;
transform(upperMessage.begin(), upperMessage.end(), upperMessage.begin(), ::toupper);
cout << "Message: " << upperMessage << endl;
}
};
class Calculator {
public:
void calculateSum(int a, int b) {
cout << "Sum: " << (a + b) << endl;
}
void calculateProduct(int a, int b) {
cout << "Product: " << (a * b) << endl;
}
};
class StringManipulator {
public:
void reverseString(const string& str) {
string reversed = string(str.rbegin(), str.rend());
cout << "Reversed: " << reversed << endl;
}
};
int main() {
MessageHandler messageHandler;
messageHandler.printMessage("Hello");
Calculator calculator;
calculator.calculateSum(5, 10);
calculator.calculateProduct(5, 10);
StringManipulator stringManipulator;
stringManipulator.reverseString("world");
return 0;
}
💡 결합도는 각 모듈들의 서로 얼마나 의존하는지 나타낸다
즉 결합도는 낮은게 좋은 코드이다.
결합도가 높게되면 각 모듈들은 서로 의존적이게 되므로, 하나의 모듈이 변경될 때 다른 모듈까지 영향을 미치게 된다.
결합도는 자동차의 내부 부품관련 시스템을 구현해 보면서 자연스럽게 익혀보자.

💡
자동차와 엔진의 관계를 코드로 구현해 봅시다.
1️⃣ 자동차는 초기에 Engine이 장착되어 있습니다.
2️⃣ 자동차 시동이 걸리면 엔진이 동작한다고 출력 합니다.
하지만 이제 엔진의 종류가 늘어났습니다.
기존 자동차에는 여러가지 엔진이 모두 장착 가능해야 합니다.
결합도가 높은 코드
👉
자동차 클래스가 디젤 엔진 클래스를 직접 포함 합니다.
이 구조는 아래와 같은 문제점이 있습니다.
1️⃣ 새로운 전기엔진을 추가하려고 하면 자동차 코드도 수정해야 합니다.
2️⃣ 변경이 잦은 경우 수정 범위가 커집니다.

[코드스니펫] 기능 추가 전 예시코드
#include <iostream>
#include <string>
using namespace std;
// 기존 Engine 클래스
class Engine {
public:
string state;
Engine() : state("off") {}
void start() {
state = "on";
cout << "Engine started" << endl;
}
};
class Car {
public:
Engine engine;
void startCar() {
if (engine.state == "off") {
engine.start();
cout << "Car started" << endl;
}
}
};
[코드스니펫] 기능 추가 후 예시코드
#include <iostream>
#include <string>
using namespace std;
// 기존 Engine 클래스
class Engine {
public:
string state;
Engine() : state("off") {}
void start() {
state = "on";
cout << "Engine started" << endl;
}
};
// 새로운 ElectricEngine 클래스 (기존 Engine과는 별도)
class ElectricEngine {
public:
string state;
ElectricEngine() : state("off") {}
void start() {
state = "on";
cout << "Electric Engine running silently" << endl;
}
};
// 기존 Car 클래스 수정
class Car {
public:
Engine engine; // Car 클래스는 여전히 Engine 클래스에 강하게 의존
void startCar() {
if (engine.state == "off") {
engine.start();
cout << "Car started" << endl;
}
}
};
결합도가 낮은 코드
👉
자동차 클래스가 디젤 엔진 클래스를 직접 포함 하지 않고 인터페이스를 포함합니다.
이 구조는 아래와 같은 문제점이 있습니다.
1️⃣ 인터페이스에만 의존하므로 새로운 엔진을 추가해도 Car는 변경이 필요 없습니다.
2️⃣ 확장성이 높아집니다.

[코드스니펫] 기능 추가 전 예시코드
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Engine {
public:
virtual void start() = 0;
virtual ~Engine() = default;
};
class DieselEngine : public Engine {
public:
void start() {
cout << "Diesel Engine started" << endl;
}
};
class Car {
private:
unique_ptr<Engine> engine; // 인터페이스에 의존하여 결합도 감소
public:
Car(unique_ptr<Engine> eng) : engine(move(eng)) {}
void startCar() {
engine->start();
cout << "Car started" << endl;
}
};
int main() {
auto engine = make_unique<DieselEngine>();
Car myCar(move(engine));
myCar.startCar();
return 0;
}
[코드스니펫] 기능 추가 후 예시코드
#include <iostream>
#include <memory>
using namespace std;
// 공통 인터페이스 정의
class Engine {
public:
virtual void start() = 0;
virtual ~Engine() = default;
};
// DieselEngine 구현
class DieselEngine : public Engine {
public:
void start() {
cout << "Diesel Engine started" << endl;
}
};
// 새로운 ElectricEngine 구현
class ElectricEngine : public Engine {
public:
void start() {
cout << "Electric Engine started silently" << endl;
}
};
// Car 클래스는 Engine 인터페이스에만 의존
class Car {
private:
unique_ptr<Engine> engine;
public:
Car(unique_ptr<Engine> eng) : engine(move(eng)) {}
void startCar() {
engine->start();
cout << "Car started" << endl;
}
};
int main() {
// DieselEngine을 사용하는 경우
auto dieselEngine = make_unique<DieselEngine>();
Car dieselCar(move(dieselEngine));
dieselCar.startCar();
// ElectricEngine을 사용하는 경우
auto electricEngine = make_unique<ElectricEngine>();
Car electricCar(move(electricEngine));
electricCar.startCar();
return 0;
}
어떻게 해야 진정한 객체지향 프로그래밍을 할 수 있을까?
훌륭한 개발자들이 만든 설계 원칙이 있다. 바로 SOLID원칙 이다.
SOLID 원칙이란 객체지향을 설계할때 주요한 5가지 원칙을 말한다.
SOLID 원칙의 목적은 크게 2가지 이다.
1️⃣ 유지보수성 및 확장성 향상
2️⃣ 변경에 유연하게 함
각 클래스는 하나의 책임을 가져야 한다는 원칙이다.
클래스의 역할과 책임을 명확히 분리해서 변경이 꼭 필요한 경우에만 필요한 클래스에 수정되도록 한다.
아래 상황을 구현해 보자.
1️⃣ 학생의 이름을 받을수 있어야 함
2️⃣ 학생의 이름을 출력할 수 있어야 함
3️⃣ 학생의 점수를 받고 성적을 계산할 수 있어야 함
잘못 적용된 사례
👉
아래와 같이 Student 클래스에 모든 메서드가 구현된 경우 이다.
Student 클래스는 Student 정보만 있는게 최선 이다.

단일 책임 원칙이 제대로 적용되지 않은 코드
#include <iostream>
#include <string>
class Student {
public:
void setName(const std::string& name) {
this->name = name;
}
void displayDetails() {
std::cout << "Student Name: " << name << std::endl;
}
void calculateGrade(int score) {
if (score >= 90) {
std::cout << "Grade: A" << std::endl;
} else if (score >= 80) {
std::cout << "Grade: B" << std::endl;
} else {
std::cout << "Grade: C" << std::endl;
}
}
private:
std::string name;
};
제대로 적용된 사례
👉
각 기능을 나눠서 클래스를 구현했다.
Student는 학생정보만 담고 있고 성적계산은 GradeCalculator 에서 하고 있다.
그리고 출력은 StudenetPrinter에서 맡고 있다.

단일 책임 원칙이 제대로 적용된 코드
#include <iostream>
#include <string>
// 학생 정보 관리 클래스
class Student {
public:
void setName(const std::string& name) {
this->name = name;
}
std::string getName() const {
return name;
}
private:
std::string name;
};
// 성적 계산 클래스
class GradeCalculator {
public:
void calculateGrade(int score) {
if (score >= 90) {
std::cout << "Grade: A" << std::endl;
} else if (score >= 80) {
std::cout << "Grade: B" << std::endl;
} else {
std::cout << "Grade: C" << std::endl;
}
}
};
// 출력 클래스
class StudentPrinter {
public:
void displayDetails(const Student& student) {
std::cout << "Student Name: " << student.getName() << std::endl;
}
};
확장에는 열려 있어야 하고, 수정에는 닫혀있어야 한다는 개념이다.
기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있도록 하는게 목적이다.
아래 상황을 구현해보자.
1️⃣ 도형에 해당되는 번호를 받고, 해당 도형을 그려주는 클래스 제작
잘못 적용된 사례
👉
아래와 같이 ShapeManager 클래스 하나가 모든 도형을 다 관리하고 있는 경우는 잘못된 거다.
어떤 도형이 추가된다면 drawShape의 코드가 수정된다. 즉 ShapeManager는 계속해서 영향을 받는다.

개방 폐쇄이 제대로 적용되지 않은 코드
class ShapeManager {
public:
void drawShape(int shapeType) {
if (shapeType == 1) {
// 원 그리기
} else if (shapeType == 2) {
// 사각형 그리기
}
}
};
제대로 적용된 사례
👉
ShapeManager는 Shape의 인터페이스를 인자로 받는다.
도형이 추가된다고 해도 ShapeManager는 전혀 영향을 받지 않는다.
해당 도형 관련 클래스가 수정 되고 Shape 인터페이스만 구현해주면 된다.

class Shape {
public:
virtual void draw() = 0; // 순수 가상 함수
};
class Circle : public Shape {
public:
void draw() {
// 원 그리기
}
};
class Square : public Shape {
public:
void draw() {
// 사각형 그리기
}
};
class ShapeManager {
public:
void drawShape(Shape& shape) {
shape.draw(); // 다형성 활용
}
};
자식 클래스는 부모 클래스에서 기대되는 행동을 보장해야 한다.
우리는 이전에 다형성이라는 것을 배웠다.
인터페이스를 하나 만들고 자식 클래스에서 이를 구현해서 사용했다
그런데 여기서 중요한건 이 당형성이 제대로 동작하려면 자식클래스는 부모클래스 인터페이스와 잘 맞도록 구현되어있어야 한다.
아래 상황을 구현해봅시다.
1️⃣ 모든 도형은 넓이를 계산할 수 있어야 합니다.
2️⃣ Rectangle클래스와 이를 상속받는 Square (정사각형)클래스를 설계해 봅시다.
잘못 적용된 사례
👉
아래와 같이 Rectangle 인터페이스가 존재하고 Square는 이를 상속 받는다. 하지만! Square는 정사각형 이다.
너비와 높이를 따로 설정할 필요가 없다. 따라서 Rectangle에서 기대하는 행동을 보장하지 못한다.

#include <iostream>
class Rectangle {
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getWidth() const { return width; }
int getHeight() const { return height; }
int getArea() const { return width * height; }
private:
int width = 0;
int height = 0;
};
class Square : public Rectangle {
public:
void setWidth(int w) override {
Rectangle::setWidth(w);
Rectangle::setHeight(w); // 정사각형은 너비와 높이가 같아야 함
}
void setHeight(int h) override {
Rectangle::setHeight(h);
Rectangle::setWidth(h); // 정사각형은 너비와 높이가 같아야 함
}
};
void testRectangle(Rectangle& rect) {
rect.setWidth(5);
rect.setHeight(10);
std::cout << "Expected area: 50, Actual area: " << rect.getArea() << std::endl;
}
int main() {
Rectangle rect;
testRectangle(rect); // Expected area: 50
Square square;
testRectangle(square); // Expected area: 50, Actual area: 100 (문제 발생)
return 0;
}
제대로 적용된 사례
👉
ShapeManager는 Shape의 인터페이스를 인자로 받는다.
도형이 추가된다고 해도 ShapeManager는 전혀 영향을 받지 않는다.
해당 도형 관련 클래스가 수정 되고 Shape 인터페이스만 구현해주면 된다.

#include <iostream>
class Shape {
public:
virtual int getArea() const = 0; // 넓이를 계산하는 순수 가상 함수
};
class Rectangle : public Shape {
public:
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int getWidth() const { return width; }
int getHeight() const { return height; }
int getArea() const override { return width * height; }
private:
int width = 0;
int height = 0;
};
class Square : public Shape {
public:
void setSide(int s) { side = s; }
int getSide() const { return side; }
int getArea() const override { return side * side; }
private:
int side = 0;
};
void testShape(Shape& shape) {
std::cout << "Area: " << shape.getArea() << std::endl;
}
int main() {
Rectangle rect;
rect.setWidth(5);
rect.setHeight(10);
testShape(rect); // Area: 50
Square square;
square.setSide(7);
testShape(square); // Area: 49
return 0;
}
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.
즉, 각 클래스에는 불필요한 메서드를 구현하지 말자라는 의미이다.
아래 요구사항에 맞는 설계를 해보자
1️⃣ 프린트,스캔이 가능 해야 한다.
잘못 적용된 사례
👉
프린터, 스캔을 하나의 클래스에서 모두 구현하게 되면, 이를 상속받는 클래스에서는 필요가 없는 경우에도 이를 모두 구현해야 한다.

인터페이스 분리 원칙이 제대로 적용되지 않은 코드
class Machnine {
private:
public:
Machnine() {}
void print() {
//세부 기능 구현
}
void scan() {
//세부 기능 구현
}
};
제대로 적용된 사례
👉
프린터,스캔, 팩스 클래스를 따로 구현하게 되면 각각 필요한 클래스만 가져다가 사용하면 된다.

class Printer {
public:
virtual void print() = 0;
};
class Scanner {
public:
virtual void scan() = 0;
};
class BasicPrinter : public Printer {
public:
void print() override {
// 문서 출력
}
};
class MultiFunctionDevice {//
private:
Printer* printer;
Scanner* scanner;
public:
MultiFunctionDevice(Printer* p, Scanner* s) : printer(p), scanner(s) {}
void print() {
if (printer) printer->print();
}
void scan() {
if (scanner) scanner->scan();
}
};
고수준 모듈은 저수준 모듈에 의존하지 않고 둘다 추상화에 의존해야 한다.
쉽게 말하면 고수준 모듈(다른 클래스나 모듈을 사용하는 사용자 역할을 함)은 저수준 모듈(구체적인 작업을 처리하는 세부사항을 담은 클래스)에 의존하면 좋은 설계가 아니다.
아래 요구사항에 맞는 설계를 해보자
1️⃣ 컴퓨터는 키보드와 모니터가 있다.
2️⃣ 키보드는 입력을 받을 수 있고 모니터는 출력 할 수 있다.
잘못 적용된 사례
👉
현재 Computer클래스는 Keyboard 클래스와 Monitor클래스에 강하게 결합되어 있다.
즉Keyboard 종류가 여러개가 되거나 Monitor종류가 여러개가 되면 변경량이 많아 진다.

의존 역전 원칙이 제대로 적용되지 않은 코드
#include<string>
class Keyboard {
public:
std::string getInput() {
return "입력 데이터";
}
};
class Monitor {
public:
void display(const std::string& data) {
// 출력
}
};
class Computer {
Keyboard keyboard;
Monitor monitor;
public:
void operate() {
std::string input = keyboard.getInput();
monitor.display(input);
}
};
제대로 적용된 사례
👉
키보드와 모니터를 인터페이스화 해서 강한결합이 아닌 약한 결합으로 했다.
이런 설계는 다른 입/출력장치로 쉽게 교체 가능하다.

인터페이스 분리 원칙이 제대로 적용된 코드
#include<string>
class InputDevice {
public:
virtual std::string getInput() = 0;
};
class OutputDevice {
public:
virtual void display(const std::string& data) = 0;
};
class Keyboard : public InputDevice {
public:
std::string getInput() override {
return "키보드 입력 데이터";
}
};
class Monitor : public OutputDevice {
public:
void display(const std::string& data) override {
// 화면에 출력
}
};
class Computer {
private:
InputDevice* inputDevice;
OutputDevice* outputDevice;
public:
Computer(InputDevice* input, OutputDevice* output)
: inputDevice(input), outputDevice(output) {}
void operate() {
std::string data = inputDevice->getInput();
outputDevice->display(data);
}
};
지금 까지 배운내용으로 문제를 풀어보자
숙제 1설명
📖SOLID원칙을 적용한 Student 클래스 구현
단일책임원칙은 클래스가 단 하나의 책임만, 가지며 하나의 역할만 수행해야 한다는 원칙이다. 이 원칙이 잘 적용된 간단한 “학생 정보를 관리하는 프로그램”을 구현해보자.
구현 요구사항은 아래와 같습니다.
완성된 모습

[출력 예시]
학생 이름: John Doe
학생 나이: 20
정답 코드
#include <iostream>
#include <vector>
#include <string>
using namespace std; // namespace std 사용
class Student {
private:
string name;
int age;
public:
Student(const string& studentName,const int& studentAge) : name(studentName), age(studentAge){}
string getName() {
return name;
}
int getAge() {
return age;
}
string getinfo() {
return "학생 이름: " + name + "\n학생 나이: " + to_string(age);
}
};
class StudentPrinter {
public:
void print(Student& student) {
cout << student.getinfo() << endl;
}
};
int main() {
// Student 객체 생성
Student student("John Doe", 20);
// StudentPrinter 객체 생성
StudentPrinter printer;
// 학생 정보 출력
printer.print(student);
return 0;
}
실행 결과

숙제2 설명
📖 TO-DO LIST 구현하기

[실제 제공된 메인함수의 출력 예시 입니다]
=== MemoryStorage로 작업 ===
현재 할 일 목록:
저장 방식: 메모리 저장소
1. C++ 과제 작성하기
2. SOLID 원칙 공부하기
업데이트된 할 일 목록:
저장 방식: 메모리 저장소
1. C++ 과제 작성하기
2. SOLID 원칙 공부하기
3. C++ 과제 작성하기 [완료]
=== DBStorage로 작업 ===
DB에 할 일 추가: DB 작업 테스트
DB에 할 일 추가: To-Do 목록 추가
현재 할 일 목록:
저장 방식: DB 저장소 (시뮬레이션)
DB에서 할 일 가져오기
1. DB 작업 테스트
2. To-Do 목록 추가
DB에서 할 일 가져오기
DB에 할 일 추가: To-Do 목록 추가
업데이트된 할 일 목록:
저장 방식: DB 저장소 (시뮬레이션)
DB에서 할 일 가져오기
1. DB 작업 테스트
2. To-Do 목록 추가
3. To-Do 목록 추가 [완료]
정답 코드
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 단일 책임 원칙: Task 클래스는 할 일 데이터를 관리
class Task {
string description;
bool completed;
public:
Task(const string& desc) : description(desc), completed(false) {}
void complete() { completed = true; }
string getDescription() const { return description; }
bool isCompleted() const { return completed; }
};
// 의존 역전 원칙: 할 일 저장 방식을 추상화하는 인터페이스
class IStorage {
public:
virtual void addTask(Task task) = 0; // 할 일 추가
virtual vector<Task> getTasks() = 0; // 할 일 목록 반환
virtual string getStorageType() = 0; // 저장소 타입 반환
virtual ~IStorage() {} // 소멸자
};
// 단일 책임 원칙: TaskManager는 할 일 관리 로직을 처리
class TaskManager {
IStorage* storage;
public:
TaskManager(IStorage* s) : storage(s) {}
// 새로운 할 일 추가
void addTask(const string& description) {
storage->addTask(Task(description));
}
// 현재 할 일 목록 출력
void showTasks() {
cout << "저장 방식: " << storage->getStorageType() << "\n";
vector<Task> tasks = storage->getTasks();
for (size_t i = 0; i < tasks.size(); ++i) {
cout << i + 1 << ". " << tasks[i].getDescription();
if (tasks[i].isCompleted()) cout << " [완료]";
cout << "\n";
}
}
// 특정 할 일을 완료 처리
void completeTask(size_t index) {
vector<Task> tasks = storage->getTasks();
if (index > 0 && index <= tasks.size()) {
tasks[index - 1].complete();
storage->addTask(tasks[index - 1]); // 변경된 할 일 저장
}
else {
cout << "잘못된 번호입니다.\n";
}
}
};
// 메모리에 할 일을 저장하는 구현 클래스
class MemoryStorage : public IStorage {
vector<Task> tasks;
public:
void addTask(Task task) {
tasks.push_back(task);
}
vector<Task> getTasks() {
return tasks;
}
string getStorageType() {
return "메모리 저장소";
}
};
// DB 방식으로 할 일을 저장하는 구현 클래스 (시뮬레이션)
class DBStorage : public IStorage {
vector<Task> tasks;
public:
void addTask(Task task) {
cout << "DB에 할 일 추가: " << task.getDescription() << "\n";
tasks.push_back(task);
}
vector<Task> getTasks() {
cout << "DB에서 할 일 가져오기\n";
return tasks;
}
string getStorageType() {
return "DB 저장소 (시뮬레이션)";
}
};
// 메인 함수
int main() {
cout << "=== MemoryStorage로 작업 ===\n";
MemoryStorage memoryStorage;
TaskManager manager1(&memoryStorage);
manager1.addTask("C++ 과제 작성하기");
manager1.addTask("SOLID 원칙 공부하기");
cout << "\n현재 할 일 목록:\n";
manager1.showTasks();
manager1.completeTask(1);
cout << "\n업데이트된 할 일 목록:\n";
manager1.showTasks();
cout << "\n=== DBStorage로 작업 ===\n";
DBStorage dbStorage;
TaskManager manager2(&dbStorage);
manager2.addTask("DB 작업 테스트");
manager2.addTask("To-Do 목록 추가");
cout << "\n현재 할 일 목록:\n";
manager2.showTasks();
manager2.completeTask(2);
cout << "\n업데이트된 할 일 목록:\n";
manager2.showTasks();
return 0;
}