728x90
프로그래밍의 코드작성 기초에 대해 다루기 때문에, 한번에 축약하여 작성한다.
https://github.com/ChoinCola/C-STLSubProject1
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;
}
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
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)
{
}
각 객체와 코드뭉치에 대한 개념요약이 끝났다.
728x90
'전문가를 위한 C++정리' 카테고리의 다른 글
2. 문자열의 활용 2.1 동적 문자열 2.1.2 문자열 리터럴 (0) | 2024.01.12 |
---|---|
2. 문자열의 활용 2.1 동적 문자열 2.1.1 C스타일 문자열 (0) | 2024.01.12 |
1. C++와 STL 부딪혀보기 1.4 표준 라이브러리 1.4.1 std::vector (0) | 2024.01.11 |
1. C++와 STL 부딪혀보기 1.3 객체지향 언어로서의 C++ 1.3.1 클래스의 정의 (0) | 2024.01.11 |
1. C++와 STL 부딪혀보기 1.2 C++ 언어 심화 탐구 1.2.6 타입 추론2 (0) | 2024.01.10 |