프로그래밍 공부
작성일
2024. 1. 11. 09:31
작성자
WDmil
728x90

프로그래밍의 코드작성 기초에 대해 다루기 때문에, 한번에 축약하여 작성한다.

https://github.com/ChoinCola/C-STLSubProject1

 

GitHub - ChoinCola/C-STLSubProject1

Contribute to ChoinCola/C-STLSubProject1 development by creating an account on GitHub.

github.com

C++프로그램 개발 프로젝트로, 직원 데이터 베이스 프로그램을 개발해볼 것 이다.

 

이 프로그램은 회사에 속한 종업원들의 정보를 관리한다.

 

유연해야하며 유용한 기능을 담고있어야 한다.

 

가져야하는 기능은 다음과 같다.

  • 종업원 추가
  • 종업원 삭제
  • 종업원의 승진
  • 과거부터 현재까지 전체 종업원 목록 열람
  • 현재 재직 중인 종업원 목록 열람
  • 퇴직한 종업원 목록 열람

코드 분리

 

이 프로그램은 세 부분으로 나누어 개발한다.

Employee 클래스

Database 클래스

UserInterface 클래스로,

 

Employee클래스는 종업원 한명의 데이터는 추상화 한다.

Database 클래스는 회사의 전체 종업원 데이터를 관리한다.

UserInterface 소스파일은 사용자가 프로그램을 이용할 수 있도록 명령어 인터페이스를 제공한다.


Employee

 

특정 종업원 한명의 정보를 유지보수한다.

 

이 클래스의 메서드들은 종업원 정보를 조회하고 변경할 수 있게 한다.

 

Employee 클래스는 자신의 정보를 출력하는 메서드, 급여와 직급을 조정하는 메서드를 제공한다.

 

Employee.h

/*
	Employee 는, 특정 종업원 한명의 정보를 유지보수한다.

	이 클래스의 메서드 들은 종업원 정보를 조회하고 변경할 수 있게 한다.

	Employee 클래스는 자신의 정보를 출력하는 메서드도 내장한다.

	그리고 급여와 직급을 조정하는 메서드도 제공한다.
*/

#pragma once
#include <string>

// 네임스페이스로 생성된 모든 항목을 모든 코드에 적용하여, 심벌이름에 대한 매핑 단위를 이룬다.
namespace Records {
	// 신입사원의 첫 급여 액수.
	const int kDefaultStartingSalary = 30000;

	/*
		Records 네임스페이스 안에 있기 때문에, 안에서는 선언으로 접근할 수 있지만,
		외부에서는 Records::kdefaultStartingSalary 로 접근해야한다.
	*/

	class Employee
	{
	public:
		Employee();
		~Employee();
		void promote(int raiseAmount = 1000);
		void demote(int demeritAmount = 1000);
		void hire(); // 종업원 채옹 또는 재채용
		void fire(); // 종업원 해고
		void display() const; // 콘솔에 종업원 정보 출력

		// 게터와 세터
		void setFirstName(const std::string& firstName);
		const std::string& getFirstName() const;

		void setLastName(const std::string& lastName);
		const std::string& getLastName() const;

		void setEmployeeNumber(int employeeNumber);
		int getEmployeeNumber() const;

		void setSalary(int newSalary);
		int getSalary() const;

		bool getIsHired() const;
		// 아래부분은 private 데이터 부분으로, 클래스 밖에서는 볼수도, 수정할 수도 없다.
		// get과 set으로만 접근 가능하다.

	private:
		std::string mFirstName;
		std::string mLastName;
		int mEmployeeNumber;
		int mSalary;
		bool mHired;
	};
}

 

Employee.cpp

#include "Employee.h"
#include <iostream>

using namespace std;
/*
    Employee.cpp 는 클래스의 메서드들에 대한 구현부를 담고있다.
    생성자는 데이터 멤버의 초깃값을 설정한다.
    종업원 이름은 공백문자,
    번호는 -1
    급여는 신입급여,
    교용상태는 미고용으로 초기화하여 시작한다.
*/
Records::Employee::Employee()
    : mFirstName("")
    , mLastName("")
    , mEmployeeNumber(-1)
    , mSalary(kDefaultStartingSalary)
    , mHired(false)
{
}

Records::Employee::~Employee()
{
    // 동적생성데이터가 존재하지 않음으로, 사용하지 않는다.
}

/*
    승진과 징계를 위한 promote()와 demote() 메서드는 단순히 setSalary()메서드를 호출하여
    급여를 변경한다.

    정수 파라미터에 대한 디폴트 지정은 함수 선언부 에서만 할 수 있다.

    이때문에 함수의 정의부가 있는 소스파일에서는 디폴트값이 보이지 않는다.
    (.h파일에서는 디폴트값 선언이 안되어있다. 물론 거기서 해줄수도 있다.)
*/
void Records::Employee::promote(int raiseAmount)
{
    setSalary(getSalary() + raiseAmount);
}

void Records::Employee::demote(int demeritAmount)
{
    setSalary(getSalary() - demeritAmount);
}

/*
    채용과 해고를 위한 hire() fire()메서드는 단순히 mHired데이터 멤버값을
    true, false로 바꾼다.
*/
void Records::Employee::hire()
{
    mHired = true;
}

void Records::Employee::fire()
{
    mHired = false;
}
/*
    display 메서드는 콘솔에 종업원 객체의 현재 상태를 출력한다.
    물론,  Employee클래스 구현의 일부이기 때문에, get set같은 메서드를 사용하지 않고,
    멤버데이터에 직접접근이 가능하다.

    그러나, 클래스 내부에서라도 가독성을 위해 get set을 사용하면 좋다.
*/
void Records::Employee::display() const
{
    cout << "Employee: " << getLastName() << ", " << getFirstName() << endl;
    cout << "-------------------------" << endl;
    cout << (mHired ? "Current Employee" : "Former Employee") << endl;
    cout << "Employee Number: " << getEmployeeNumber() << endl;
    cout << "Salary: $" << getSalary() << endl;
    cout << endl;
}
/*
    밑의 게터, 세터 메서드가 멤버 데이터값을 리턴하거나 변경하는 작업을 담당한다.

    이런 메서드가 별로 옳지 않은것 처럼 보이지만, 보통 이러한 게터 세터를 사용하는편이 좋다.

    게터와 세터가 있으면, 해당 변수 이용시점에 디버그를 위한 브레이크 포인트를 걸 수 있고,
    클래스에서 데이터를 저장하는 방식을 바꾸고 싶을때, 게터와 세터만 수정하면 된다는 장점이 있다.
*/
void Records::Employee::setFirstName(const std::string& firstName)
{
    mFirstName = firstName;
}

const std::string& Records::Employee::getFirstName() const
{
    return mFirstName;
}

void Records::Employee::setLastName(const std::string& lastName)
{
    mLastName = lastName;
}

const std::string& Records::Employee::getLastName() const
{
    return mLastName;
}

void Records::Employee::setEmployeeNumber(int employeeNumber)
{
    mEmployeeNumber = employeeNumber;
}

int Records::Employee::getEmployeeNumber() const
{
    return mEmployeeNumber;
}

void Records::Employee::setSalary(int newSalary)
{
    mSalary = newSalary;
}

int Records::Employee::getSalary() const
{
    return mSalary;
}

bool Records::Employee::getIsHired() const
{
    return mHired;
}

Test.

/*
	클래스 정의를 작성하면서 클래스에 대한 테스트를 별도의 독립코드로 수행하면 편리하다.

	Employee 클래스에 대한 테스트 코드를  main 에 담아. 작성이 완료되고

	어느정도 작동할것 이라는 생각이 들면, 테스트 코드를 주석해제하여. 테스트를 한다.

	테스트가 끝나면, 다시 주석으로 감싸 main()함수가 함꼐 컴파일되어 실제 사용할 

	main함수와 충돌하지 않도록 한다.
*/

#pragma once
#include <iostream>
#include "Employee.h"

using namespace std;
using namespace Records;

int main()
{
	cout << "Testing the Employee Class." << endl;
	Employee emp;
	emp.setFirstName("John");
	emp.setLastName("Doe");
	emp.setEmployeeNumber(71);
	emp.setSalary(50000);
	emp.promote();
	emp.promote(50);
	emp.hire();
	emp.display();

	return 0;
}

EmployeeTest


Database

 

 데이터베이스 클래스는 std::vector 클래스를 이용하여 Employee객체들을 담는다.

 

Database.h

/*
	데이터 베이스 클래스는 std::vector 클래스를 이용해 Employee 객체들을 담는다.

	데이터베이스는 새로운 종업원에 대해 종업원 번호를 자동으로 부여해줄 수 있어야 한다.

	이를 위해 시작번호를 정의하는 상수 변수를 선언한다.
*/
#pragma once
#include <iostream>
#include <vector>
#include "Employee.h"
/*
	데이터베이스는 새로운 종업원을 등록할 때 단순히 이름(first name)과 성(last name)만 기입하면 된다.

	종업원 등록 함수는 편의상 등록 오나료 후 새로 생성된 Employee 객체를 리턴한다.

	외부에서는 getEmployee() 매서드로 종업원 객체를 얻을 수 있는데, 이 메서드는 종업원을 식별하기 위해
	
	종업원 번호를 입력받거나 성과 이름을 입력받을 수 있도록 두 가지 버전으로 만든다.
*/
namespace Records {
	const int kFirstEmployeeNumber = 1000;
	
	class Database
	{
	public:
		Database();
		Employee& addEmployee(const std::string& firstName,
			const std::string& lastName);
		Employee& getEmployee(int employeeNumber);
		Employee& getEmployee(const std::string& firstName,
			const std::string& lastName);

		/*
			데이터베이스는 모든 종업원 레코드의 중앙 저장소 이기 때문에 전체 종업원에 대한 목록은
			물론, 고용중인 종업원, 퇴직한 종업원 목록을 구분하여 출력할 수 있어야 한다.
		*/

		void displayAll() const;
		void displayCurrent() const;
		void displayFormer() const;

		/*
			mEmployees는 Employee 객체들을 담는다. mNextEmployeeNumber 변수는 새로운 종업원이 고용되었을 때
			할당할 고유번호를 지정한다.
		*/
	private:
		std::vector<Employee> mEmployees;
		int mNextEmployeeNumber;
	};
}

 

Database.cpp

#include <iostream>
#include <stdexcept>
#include "Database.h"

using namespace std;
namespace Records {
    // 시작시 고유 번호의 시작값을 설정한다.
    Records::Database::Database() : mNextEmployeeNumber(kFirstEmployeeNumber)
    {
    }
    /*
        새로운 Employee객체를 생성하고 종업원 정보를 설정한 뒤, 데이터베이스에 추가한다.
        새로운 종업원이 추가되면, mNestEmployeeNumber를 1증가시켜, 다음 번호중복이 발생하지 않도록 한다.
    */
    Employee& Records::Database::addEmployee(const std::string& firstName, const std::string& lastName)
    {
        Employee theEmployee;
        theEmployee.setFirstName(firstName);
        theEmployee.setLastName(lastName);
        theEmployee.setEmployeeNumber(mNextEmployeeNumber++);
        theEmployee.hire();
        mEmployees.push_back(theEmployee);
        return mEmployees[mEmployees.size() - 1];
    }
    /*
        구간 지정 for루프를 통해, 인자로 주어진 조건에 맞는 종업원이 있는지 검사하고,
        존재하지 않으면 익셉션을 발생한다.
    */
    Employee& Records::Database::getEmployee(int employeeNumber)
    {
        for (auto& employee : mEmployees) {
            if (employee.getEmployeeNumber() == employeeNumber) {
                return employee;
            }
        }
    }

    Employee& Records::Database::getEmployee(const std::string& firstName, const std::string& lastName)
    {
        for (auto& employee : mEmployees) {
            if (employee.getFirstName() == firstName &&
                employee.getLastName() == lastName) {
                return employee;
            }
        }
    }
    /*
        display또한, 해당되는 객체의 모든 항목을 display호출하여 종업원 정보를 콘솔에 출력한다.
    */
    void Records::Database::displayAll() const
    {
        for (const auto& employee : mEmployees) {
            employee.display();
        }
    }

    void Records::Database::displayCurrent() const
    {
        for (const auto& employee : mEmployees) {
            if (employee.getIsHired())
                employee.display();
        }
    }

    void Records::Database::displayFormer() const
    {
        for (const auto& employee : mEmployees) {
            if (!employee.getIsHired())
                employee.display();
        }
    }
}

Test

DataBaseTest

 


UserInterface

 

프로그램의 마지막부분은, 사용자가 종업원 데이터베이스를 편리하게 이용할 수 있게 해주는 메뉴방식의 사용자 인터페이스 이다.

 

UserInterface.cpp

#pragma once
/*
	프로그램의 마지막 부분은 사용자가 종업원 데이터베이스를 편리하게 이용할 수 있게 해주는 메뉴 방식의
	사용자 인터페이스 이다.
*/

/*
	main 함수는 메뉴를 출력하는 루프로 이루어진다.

	선택한 메뉴 항목을 실행하고 다시 메뉴로 돌아오기를 반복한다.

	각 행동은 별도의 함수로 분리하여. 정의하되 종업원 정보의 출력처럼

	간단한것 은 해당 명령 케이스에 바로 구현한다.
*/

#include <iostream>
#include <stdexcept>
#include <exception>
#include "Database.h"
using namespace std;
using namespace Records;

int displayMenu();
void doHire(Database& db);
void doFire(Database& db);
void doPromote(Database& db);
void doDemote(Database& db);

int main()
{
	Database employeeDB;
	bool done = false;
	
	while (!done) {
		int selection = displayMenu();
		switch (selection) {
		case 1:
			doHire(employeeDB);
			break;
		case 2:
			doFire(employeeDB);
			break;
		case 3:
			doPromote(employeeDB);
			break;
		case 4:
			employeeDB.displayAll();
			break;
		case 5:
			employeeDB.displayCurrent();
			break;
		case 6:
			employeeDB.displayFormer();
			break;
		case 0:
			done = true;
			break;
		default:
			cerr << "Unknown command." << endl;
			break;
		}
	}
	return 0;
}
/*
	메뉴를 출력하고, 사용자의 선택을 기다린다.

	이때, 사용자가 충실하게 메뉴의 요구 조건에 응답한다고 가정한다.

	숫자를 입력하라 했을 때, 무조건 숫자를 입력한다고 가정한다.
*/
int displayMenu()
{
	int selection;
	cout << endl;
	cout << "Employee Database" << endl;
	cout << "-----------------" << endl;
	cout << "1) Hire a new employee" << endl;
	cout << "2) Fire an employee" << endl;
	cout << "3) Promote an employee" << endl;
	cout << "4) List all employees" << endl;
	cout << "5) List all current employees" << endl;
	cout << "6) List all former employees" << endl;
	cout << "0) Quit" << endl;
	cout << endl;
	cout << "--->";
	cin >> selection;
	return selection;
}
/*
	doHire 함수는 새로운 종업원의 이름을 사용자로부터 받고, 데이터베이스에 추가한다.

	만약 에러가 발생하면 메시지를 출력하고 작업을 반복한다.
*/
void doHire(Database& db)
{
	string firstName;
	string lastName;

	cout << "First name?";
	cin >> firstName;
	cout << "Last name?";
	cin >> lastName;

	try {
		db.addEmployee(firstName, lastName);
	}
	catch (const std::exception& exception) {
		cerr << "Unable to add new employee: " << exception.what() << endl;
	}
}

/*
	해고와 승진을 위한 doFire()와 doPromote() 함수는 데이터베이스에 주어진 고유 번호의 종업원이 있는지 물어보고
	Employee 객체를 얻어와서 그 객체의 메서드를 이용해 작업을 수행한다.
*/
void doFire(Database& db)
{
	int employeeNumber;
	cout << "Employee number? ";
	cin >> employeeNumber;
	try {
		Employee& emp = db.getEmployee(employeeNumber);
		emp.fire();
		cout << "Employee " << employeeNumber << " terminated." << endl;
	}
	catch (const std::exception& exception) {
		cerr << "Unable to terminate employee: " << exception.what() << endl;
	}
}

void doPromote(Database& db)
{
	int employeeNumber;
	int raiseAmount;

	cout << "Employee number? ";
	cin >> employeeNumber;
	cout << "How much of a raise? ";
	cin >> raiseAmount;
	try {
		Employee& emp = db.getEmployee(employeeNumber);
		emp.promote(raiseAmount);
	}
	catch (const std::exception& exception) {
		cerr << "Unable to terminate employee: " << exception.what() << endl;
	}
}

void doDemote(Database& db)
{

}

UserInterface.Runtime

 

각 객체와 코드뭉치에 대한 개념요약이 끝났다.

728x90