도서 관리 프로그램 – 3. BookManager 클래스와 이벤트 형식 정의 [MFC]

이번에는 도서 관리 프로그램에서 도서 관리를 담당할 BookManager 클래스를 정의합시다.

그리고 대화 상자에 의해 도서 관리 상태의 변화를 알고자 하는 대화상자에게 이를 통보하기 위한 이벤트 형식도 정의합시다.

유튜브에 무료 동영상 강의를 업로드하였습니다.

이벤트 형식 정의

도서 관리 상태 변화에 관한 이벤트는 도서 추가, 도서 삭제, 도서 변경 이벤트를 제공합니다.

이벤트 형식은 순수 가상 메서드를 멤버로 갖는 클래스로 정의합시다.

#pragma once
class Book;
class AddBookEventHandler
{
public:
	virtual void AddedBook(Book *book) = 0;
};

class RemoveBookEventHandler
{
public:
	virtual void RemovedBook(int bno) = 0;
};

class ModifyBookEventHandler
{
public:
	virtual void ModifiedBook(Book *book) = 0;
};

뒤쪽에서는 메인 대화상자는 도서 추가 이벤트와 변경 이벤트를 수신하여 처리하는 이벤트 핸들러를 정의할 것입니다.

자세히 보기 대화상자에서는 도서 추가 이벤트와 삭제 이벤트를 수신하여 처리하는 이벤트 핸드러를 정의할 것입니다.

변경 대화상자에서는 도서 추가 이벤트와 도서 삭제 이벤트를 수신하여 처리하는 이벤트 핸들러를 정의할 것입니다.

BookManager.h

도서 관리자는 BookManager 이름의 클래스로 정의합시다.

도서는 맵으로 보관할게요. 키는 도서 번호(int)이고 값은 도서 개체(Book *)입니다.

현재 관리하는 도서 번호 목록은 리스트(NList)로 제공할게요.

도서 추가, 삭제, 변경에 관한 이벤트 핸들러는 리스트로 보관할게요.(ABHList, RBHList, MBHList)

BookManager는 단일체 패턴으로 제공하기로 합시다.

단일체는 정적 멤버 (bm)로 캡슐화합니다.

단일체를참조하는 정적 메서드(GetBookManager)를 제공합니다.

생성자는 private 접근 지정으로 정보 은닉합니다.

다음에 추가할 도서 번호를 반환하는 메서드(GetNextBookNo)를 제공합니다.

도서를 추가하는 메서드(AddBook)을 제공합니다.

현재 보관하는 도서 번호 목록을 확인할 수 있게 메서드(GetBookNoList)를 제공합니다.

특정 도서 정보를 얻어오는 메서드(GetBook)을 제공합니다.

도서 정보를 수정하는 메서드(ModifyBook)와 삭제하는 메서드(RemoveBook)를 제공합니다.

저장 메서드(Save)와 로드 메서드(Load)를 제공합니다.

#pragma once
#include "Book.h"
#include "BookEventHandler.h"
#include <map>
#include <list>
using std::list;

typedef list<int> NList;
typedef NList::iterator NIter;
using std::map;
typedef map<int, Book*> BMap;
typedef BMap::iterator BIter;
typedef list<AddBookEventHandler*> ABHList;
typedef ABHList::iterator ABIter;
typedef list<RemoveBookEventHandler*> RBHList;
typedef RBHList::iterator RBIter;
typedef list<ModifyBookEventHandler*> MBHList;
typedef MBHList::iterator MBIter;

class BookManager
{
	BMap book_map;
	int last_bno;//가장 최근에 부여한 도서 번호
	ABHList abhandlers;
	RBHList rbhandlers;
	MBHList mbhandlers;
	static BookManager bm;//단일체 개체
public:
	static BookManager& GetBookManager();//단일체 개체를 반환
	int GetNextBookNo();
	bool AddBook(CString title, CString content, CString image, COleDateTime pubdate);
	void GetBookNoList(NList* bno_list);
	Book* GetBook(int no);
	bool ModifyBook(int bno, CString content, CString image, COleDateTime pubdate);
	bool RemoveBook(int bno);
	void AddABEventHandler(AddBookEventHandler* abeh);//도서 추가 이벤트 핸들러 등록
	void AddRBEventHandler(RemoveBookEventHandler* rbeh);//도서 삭제 이벤트 핸들러 등록
	void AddMBEventHandler(ModifyBookEventHandler* mbeh);//도서 변경 이벤트 핸들러 등록
	void Save();
	void Load();
private:	
	BookManager();
};



BookManager.cpp

단일체로 캡슐화한 정적 멤버를 선언합니다.

BookManager BookManager::bm;

단일체를 참조하는 정적 메서드를 제공합니다.

BookManager& BookManager::GetBookManager()
{	
	return bm;
}

생성자에서는 최근에 발급한 도서 번호를 0으로 설정합니다.

그리고 이를 반환하는 메서드를 제공합니다.

BookManager::BookManager()
{
	last_bno = 0;
}
int BookManager::GetNextBookNo()
{
	return last_bno+1;
}

도서 추가 메서드를 정의합시다.

최근 발급한 도서 번호를 1 증가한 후에 도서 개체를 생성한 후 map에 설정합니다.

그리고 도서 추가 이벤트를 수신하기 위해 등록한 핸들러 리스트를 순차 방문하여 도서 추가 사실을 통보합니다.

bool BookManager::AddBook(CString title, CString content, CString image, COleDateTime pubdate)
{
	last_bno++;
	Book* book = new Book(last_bno, title, content, image, pubdate);
	book_map[last_bno] = book;
	/*
	* 도서 추가한 정보를 알고자 하는 이들에게 통보하는 코드가 필요할 듯
	*/
	ABIter seek = abhandlers.begin();
	ABIter end = abhandlers.end();
	for (; seek != end; ++seek)
	{
		(*seek)->AddedBook(book);//도서 추가 사실을 통보한다.
	}
	return true;
}

관리하는 도서 번호를 제공하는 메서드를 정의합시다.

map을 인덱스 연산으로 사용할 때 없는 키로 접근하면 값이 0으로 보관하는 특징이 있습니다. 따라서 전체 목록을 조회할 때 값이 0이 아닌지 확인하는 과정이 필요합니다.

void BookManager::GetBookNoList(NList* bno_list)
{
	Book* book = 0;
	BIter seek = book_map.begin();
	BIter end = book_map.end();
	for (; seek != end; ++seek)
	{
		book = (*seek).second;//값
		if (book)//값이 유효하다면
		{
			bno_list->push_back((*seek).first);//book->GetNo()
		}		
	}
}

도서 번호를 입력 인자로 받아 도서 개체를 반환하는 메서드도 정의합시다.

Book* BookManager::GetBook(int no)
{
	return book_map[no];	
}

도서 변경 메서드를 정의합시다.

입력 인자로 전달받은 도서 번호로 book_map에서 도서 개체를 찾습니다.

도서 개체가 있을 때 입력 인자로 받은 정보로 도서의 정보를 수정합니다.

그리고 도서 정보 수정 이벤트를 수신하는 이벤트 핸들러 목록을 순차 방문하여 도서 정보 수정 사실을 통보합니다.

bool BookManager::ModifyBook(int bno, CString content, CString image, COleDateTime pubdate)
{
	Book* book = book_map[bno];
	if (book)
	{		
		book->SetContent(content);
		book->SetImage(image);
		book->SetPubdate(pubdate);
		MBIter seek = mbhandlers.begin();
		MBIter end = mbhandlers.end();
		for (; seek != end; ++seek)
		{
			(*seek)->ModifiedBook(book);
		}
		return true;
	}
	return false;
}

도서 삭제 메서드도 제공합시다.

도서 번호로 도서 개체를 참조하여 있으면 삭제합니다.

그리고 book_map에 해당 도서 번호를 키로 값은 0으로 설정합니다.

도서 삭제 이벤트를 수신하는 이벤트 핸들러 목록을 순차 방문하여 도서 정보 수정 사실을 통보합니다.

bool BookManager::RemoveBook(int bno)
{
	Book* book = book_map[bno];
	if (book)
	{
		delete book;
		book_map[bno] = 0;
		RBIter seek = rbhandlers.begin();
		RBIter end = rbhandlers.end();
		for (; seek != end; ++seek)
		{
			(*seek)->RemovedBook(bno);
		}
		return true;
	}
	return false;
}

이벤트 핸들러를 등록하는 메서드도 정의합시다.

void BookManager::AddABEventHandler(AddBookEventHandler* abeh)
{
	abhandlers.push_back(abeh);
}
void BookManager::AddRBEventHandler(RemoveBookEventHandler* rbeh)
{
	rbhandlers.push_back(rbeh);
}
void BookManager::AddMBEventHandler(ModifyBookEventHandler* mbeh)
{
	mbhandlers.push_back(mbeh);
}

저장(Save) 메서드를 정의합시다.

파일을 열고 CArchive를 store 모드로 생성합니다.

book_map을 순차 방문하여 값이 유효할 때 직렬화 메서드를 호출합니다.

작업을 마친 후 CArchive 개체와 파일 개체를 닫습니다.

void BookManager::Save()
{
	CFile cf(TEXT("data.abc"), CFile::modeCreate | CFile::modeWrite);
	CArchive ar(&cf, CArchive::store);

	BIter seek = book_map.begin();
	BIter end = book_map.end();
	Book* book = 0;
	for (; seek != end; ++seek)
	{
		book = (*seek).second;
		if (book)
		{
			book->Serialize(ar);
		}
	}
	ar.Close();
	cf.Close();
}

로드 메서드를 정의합시다.

파일을 읽기 모드로 열기를 시도합니다.

만약 예외가 발생하면 메서드를 종료합니다.

파일을 입력인자로 load 모드로 CArchive 개체를 생성합니다.

무한 루프에서 도서 개체를 생성(입력 인자가 CArchive 참조인 생성자)하여 book_map에 설정합니다.

도서 추가 이벤트도 통보합니다.

예외가 발생하면 무한 루프를 탈출합니다.

모든 작업이 끝나면 CArchive 개체와 파일 개체를 닫습니다.

void BookManager::Load()
{
	CFile* pcf;
	try
	{
		pcf =new CFile(TEXT("data.abc"), CFile::modeRead);
	}
	catch (...)
	{
		return;
	}
	CArchive ar(pcf, CArchive::load);	
	while (true)
	{
		try
		{
			Book* book = new Book(ar);
			book_map[book->GetNo()] = book;
			last_bno = book->GetNo();
			ABIter seek = abhandlers.begin();
			ABIter end = abhandlers.end();
			for (; seek != end; ++seek)
			{
				(*seek)->AddedBook(book);//도서 추가 사실을 통보한다.
				 
			}			
		}
		catch (...)
		{
			break;
		}
	}

	ar.Close();
	pcf->Close();
	delete pcf;
}

다음은 BookManager.cpp 소스 코드 내용입니다.

#include "pch.h"
#include "BookManager.h"
BookManager BookManager::bm;
BookManager& BookManager::GetBookManager()
{	
	return bm;
}
BookManager::BookManager()
{
	last_bno = 0;
}
int BookManager::GetNextBookNo()
{
	return last_bno+1;
}
bool BookManager::AddBook(CString title, CString content, CString image, COleDateTime pubdate)
{
	last_bno++;
	Book* book = new Book(last_bno, title, content, image, pubdate);
	book_map[last_bno] = book;
	/*
	* 도서 추가한 정보를 알고자 하는 이들에게 통보하는 코드가 필요할 듯
	*/
	ABIter seek = abhandlers.begin();
	ABIter end = abhandlers.end();
	for (; seek != end; ++seek)
	{
		(*seek)->AddedBook(book);//도서 추가 사실을 통보한다.
	}
	return true;
}
void BookManager::GetBookNoList(NList* bno_list)
{
	Book* book = 0;
	BIter seek = book_map.begin();
	BIter end = book_map.end();
	for (; seek != end; ++seek)
	{
		book = (*seek).second;//값
		if (book)//값이 유효하다면
		{
			bno_list->push_back((*seek).first);//book->GetNo()
		}		
	}
}
Book* BookManager::GetBook(int no)
{
	return book_map[no];	
}
bool BookManager::ModifyBook(int bno, CString content, CString image, COleDateTime pubdate)
{
	Book* book = book_map[bno];
	if (book)
	{		
		book->SetContent(content);
		book->SetImage(image);
		book->SetPubdate(pubdate);
		MBIter seek = mbhandlers.begin();
		MBIter end = mbhandlers.end();
		for (; seek != end; ++seek)
		{
			(*seek)->ModifiedBook(book);
		}
		return true;
	}
	return false;
}
bool BookManager::RemoveBook(int bno)
{
	Book* book = book_map[bno];
	if (book)
	{
		delete book;
		book_map[bno] = 0;
		RBIter seek = rbhandlers.begin();
		RBIter end = rbhandlers.end();
		for (; seek != end; ++seek)
		{
			(*seek)->RemovedBook(bno);
		}
		return true;
	}
	return false;
}
void BookManager::AddABEventHandler(AddBookEventHandler* abeh)
{
	abhandlers.push_back(abeh);
}
void BookManager::AddRBEventHandler(RemoveBookEventHandler* rbeh)
{
	rbhandlers.push_back(rbeh);
}
void BookManager::AddMBEventHandler(ModifyBookEventHandler* mbeh)
{
	mbhandlers.push_back(mbeh);
}
void BookManager::Save()
{
	CFile cf(TEXT("data.abc"), CFile::modeCreate | CFile::modeWrite);
	CArchive ar(&cf, CArchive::store);

	BIter seek = book_map.begin();
	BIter end = book_map.end();
	Book* book = 0;
	for (; seek != end; ++seek)
	{
		book = (*seek).second;
		if (book)
		{
			book->Serialize(ar);
		}
	}
	ar.Close();
	cf.Close();
}
void BookManager::Load()
{
	CFile* pcf;
	try
	{
		pcf =new CFile(TEXT("data.abc"), CFile::modeRead);
	}
	catch (...)
	{
		return;
	}
	CArchive ar(pcf, CArchive::load);	
	while (true)
	{
		try
		{
			Book* book = new Book(ar);
			book_map[book->GetNo()] = book;
			last_bno = book->GetNo();
			ABIter seek = abhandlers.begin();
			ABIter end = abhandlers.end();
			for (; seek != end; ++seek)
			{
				(*seek)->AddedBook(book);//도서 추가 사실을 통보한다.
				 
			}			
		}
		catch (...)
		{
			break;
		}
	}

	ar.Close();
	pcf->Close();
	delete pcf;
}