Дизайн-патерни: Будівельник (С++)

3 хв. читання

В минулій статті ми розглянули дизайн-патерн «Абстрактна фабрика». Як згадувалося – це породжуючий патерн.

Загалом породжуючих дизайн-патернів є 5:

Продовжуючи тему, розглянемо наступний породжуючий патерн зі списку – «Будівельник».

Будівельник (Builder)

Призначення: створення об'єкта в залежності з заданими конфігураціями.

Власне назва «Будівельник» говорить сама за себе. Цей патерн дозволяє «будувати» об'єкт. Під словом «будувати» в даному контексті варто розуміти встановлення того чи іншого атрибуту об'єкта, тим самим створюючи об'єкт з певним набором атрибутів.

Реальне використання такого патерну присутнє, наприклад, в іграх. Є загальний клас «ворог», що містить атрибут ворога damage – скільки шкоди він може завдати головному герою. І у відповідності від загальної концепції ворога можна побудувати багато різних ворогів, які можуть завдавати більше або менше шкоди в залежності від значення атрибута damage. І для того щоб створити таких ворогів доречним буде патерн «Будівельник».

Припустимо, ви вдарилися в столярство і хочете змайструвати власний стіл. Ви знаєте, що стіл може бути різного дизайну, але кожен стіл має такі атрибути як ширина, довжина, висота, форма і т.п. Власне створимо такий клас Table:

class Table
{
	friend ostream& operator<<(ostream& os, const Table& tb);

public:
	Table();
	~Table();

	void output();

	string m_shape, m_material;
	double m_height, m_width, m_long;
};

В даному класі є атрибути, які задають параметри стола – форму, матеріал та ін. І поки ви не повідбивали собі пальці молотком в намаганні створити щось більш-менш схоже на стіл, спершу доведеться визначитися з матеріалом, формою та розмірами.

Отже, нам необхідний клас-будівельник, який би задавав ці параметри:

class TableBuilder
{
public:
	TableBuilder();
	~TableBuilder();

protected:
	Table* table;

public:
	virtual void setShape() = 0;
	virtual void setHeight() = 0;
	virtual void setWidth() = 0;
	virtual void setLong() = 0;
	virtual void setMaterial() = 0;

	void createNewTable();
	Table getReadyTable();
};

Клас TableBuilder є абстрактним, оскільки містить чисті віртуальні функції, які задають значення для атрибутів класу Table. Також створений вказівник на об'єкт класу Table та оголошені дві функції, перша з котрих виділяє пам'ять для об'єкта table, а друга повертає його копію:

void TableBuilder::createNewTable()
{
	table = new Table();
}

Table TableBuilder::getReadyTable()
{
	return *table;
}

Тепер нам необхідно оголосити похідні класи, які встановлюватимуть атрибути для об'єкта table. В даному випадку створимо дві можливі реалізації стола – овальний стіл та журнальний столик. Однак варіантів може бути стільки завгодно.

Овальний стіл:

#include "TableBuilder.h"

class OvalTableBuilder : public TableBuilder
{
public:
	OvalTableBuilder();
	~OvalTableBuilder();

	virtual void setShape();
	virtual void setHeight();
	virtual void setWidth();
	virtual void setLong();
	virtual void setMaterial();
};

Журнальний столик:

#include "TableBuilder.h"

class MagazineTableBuilder : public TableBuilder 
{
public:
	MagazineTableBuilder();
	~MagazineTableBuilder();

	virtual void setShape();
	virtual void setHeight();
	virtual void setWidth();
	virtual void setLong();
	virtual void setMaterial();
};

Приклад визначення віртуальних функцій, що задають значення атрибутів, наступний:

void OvalTableBuilder::setShape()
{
	table->m_shape = "Oval";
}

void OvalTableBuilder::setHeight()
{
	table->m_height = 0.7;
}

void OvalTableBuilder::setWidth()
{
	table->m_width = 1.0;
}

void OvalTableBuilder::setLong()
{
	tableBuilder->m_long = 2.0;
}

void OvalTableBuilder::setMaterial()
{
	table->m_material = "Tree";
}

Як можна зрозуміти, ми задаємо такі атрибути для стола, якщо хочемо щоб він був овальним. Таким чином встановлюємо атрибути та для журнального столика.

Отже, тепер ви можете побудувати стіл, який буде овальним чи журнальним. Для того, щоб вказати який саме стіл будувати, необхідний клас-директор. Це такий клас, об'єкти якого вибирають потрібну конфігурацію для будівельника. OvalTableBuilder і MagazineTableBuilder власне і є конфігураціями.

#include "TableBuilder.h"

class TableDirector
{
public:
	TableDirector();
	~TableDirector();

	void setTableBuilder(TableBuilder& _tb);
	void constructTable();
	Table getTable();

private:
	TableBuilder* m_tb;
};

Визначення класу наступне:

void TableDirector::setTableBuilder(TableBuilder& _tb)
{
	m_tb = &_tb;
}

void TableDirector::constructTable()
{
	m_tb->createNewTable();
	m_tb->setShape();
	m_tb->setHeight();
	m_tb->setWidth();
	m_tb->setLong();
	m_tb->setMaterial();
}

Table TableDirector::getTable()
{
	return m_tb->getReadyTable();
}

Клас TableDirector приймає і встановлює конфігурацію m_tb. В залежності від вибраної конфігурації викликаються відповідні методи, які встановлюють атрибути стола. Наприклад, вибравши OvalTableBuilder, передаємо об'єкт у функцію setTableBuilder(TableBuilder& _tb), що визначає об'єкт класу TableBuilder як об'єкт OvalTableBuilder. Адже об'єкти наслідуваних класів також є об'єктами базових класів.

У функції constructTable() створюється об'єкт стола, після чого викликаються функції з класу OvalTableBuilder для встановлення атрибутів цьому об'єкту. Остання функція повертає вже «побудований» об'єкт.

Це все використовується наступним чином:

#include "OvalTableBuilder.h"
#include "MagazineTableBuilder.h"
#include "TableDirector.h"

int main()
{
	TableBuilder* pO = new OvalTableBuilder();			// інстанціювання похідного класу OvalTableBuilder
	TableBuilder* pM = new MagazineTableBuilder();		// інстанціювання похідного класу MagazineTableBuilder
	TableDirector dir;							// інстанціювання класу-директора
	dir.setTableBuilder(*pO);					// встановлення конфігурації для стола як для овального
	dir.constructTable();						// побудувати стіл (створити об'єкт класу TableBuilder, встановити атрибут довжини...)
	Table table = dir.getTable();				// отримати готовий овальний стіл
	
	// аналогічно для журнального столика
	TableDirector dir2;
	dir2.setTableBuilder(*pM);
	dir2.constructTable();
	Table table2 = dir2.getTable();

	// вивід
	cout << table;
	cout << "_____________" << endl;
	table2.output();

    return 0;
}

На виході отримаємо значення атрибутів:

Дизайн-патерни: Будівельник (С++)

Підсумки

Використання патерну «Будівельник» застосовується для створення складного об'єкта, що має загальну концепцію, але може містити різні значення своїх даних.

Алгоритм створення будівельника наступний:

  1. Створити клас, що реалізує об'єкт, який може містити різні значення (приклад – клас Table);
  2. Створити клас будівельника, який створює об'єкт того класу, який необхідно побудувати, повертає його та оголошує функції для встановлення атрибутів об'єкта (TableBuilder);
  3. Створити похідні класи, кожен з яких визначає методи, що оголошені в базовому класі для встановлення атрибутів (OvalTableBuilder, MagazineTableBuilder);
  4. Створити клас-директор для встановлення певної конфігурації, будування та повернення «побудованого» об'єкту.

Завантажити вихідний код для даної статті можна тут.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація