После принятия Образец прототипа в игру, это приятно улучшило удобство сопровождения моего кода.
Тем не менее, я начал бояться, что когда фактический объект стать более детализированными,
Я склонен кодировать соответствующий прототип стать более похожим на копию фактический объект.
(примечание № v.2 в коде)
class Prototype{ //aka. "fake object"public: Vec3 position;
//bool isShootable; //(#v.2) //#X
//float delayBetweenShoot; //(#v.2)
//float hp; //(#v.2)
};
class Actual{ //aka. "actual object"public: PhysicObject* rigidBody=nullptr;
//bool isShootable; //(#v.2) //#X
//float delayBetweenShoot; //(#v.2)
//float hp; //(#v.2)
};
int main(){ // simplify
Prototype prototype;
prototype.position = Vec3(1,2,3);
//^ end prototype creation
//v these lines are inside a function, but it simply works like this
PhysicObject* phy=new PhysicObject(prototype.position);
Actual actual;
actual.rigidBody=phy;
//actual.isShootable =prototype.isShootable; //(#v.2) #Y
//actual.delayBetweenShoot=prototype.delayBetweenShoot; //(#v.2)
//actual.hp =prototype.hp; //(#v.2)
gameLogic.add(actual); //roughly speaking
}
Есть два плохих сигнала (# v.2): —
1. повторяющийся код в Prototype
против Actual
(#ИКС)
2. утомительное копирование полей. (#Y)
Таким образом, я думаю, что что-то начинает идти не так.
Этот шаблон, естественно, может вызвать новые проблемы с ремонтопригодностью.
В реальной ситуации actual2
содержит другой actual1
,
Чтобы принять Prototype Pattern, я использую соответствующий prototype2
внутри другого соответствующего prototype1
: —
class Actual1{
//... some fields e.g. A1 a1; A2 a2; A3 a3;
};
class Actual2{
//... some other field e.g. B1 B2 B3
Actual1 actual1;
};
class Prototype1{
//... some fields e.g. very similar to A1 A2 A3
};
class Prototype2{
//... some other field e.g. very similar to B1 B2 B3
Prototype1 prototype1;
};
Я думаю, что было бы хорошей идеей заключить повторяющуюся часть в единую структуру с именем Settings
,
class Settings{
bool isShootable;
float delayBetweenShoot;
float hp;
};
class Prototype{ //aka. "fake object"public: Vec3 position;
Settings settings; //(#v.2)
};
class Actual{ //aka. "real object"public: PhysicObject* rigidBody=nullptr;
Settings settings; //(#v.2)
};
Однако это может увеличить неблагоприятное сцепление (то есть клей или сильную связь) между Prototype
а также Actual
, Таким образом, это может снова привести к новой проблеме ремонтопригодности.
Вы можете избежать ненужного дублирования, подклассифицируя свои «фактические данные» из прототипа. Например:
struct Prototype {
bool isShootable = false;
float delayBetweenShoot = DEFAULT_DELAY;
float hp = DEFAULT_HP;
Vec3 position = STARTING_POSITION;
...
};
struct Actual : Prototype {
PhysicObject* rigidBody;
Actual() : Prototype(), rigidBody(new PhysicObject(position)) {}
}
int main() {
Actual actual;
// now you can access these methods
if (actual.isShootable) {
...
}
...
}
Ваша интуиция верна в том, что, группируя «общие» поля вместе, вы увеличиваете связь между этими полями. В некотором смысле существует компромисс между связью и дублированием кода. Вам решать, какой приемлемый компромисс лучше всего подходит для вашего приложения.
Вероятно, использование разных типов (с последующей двойной бухгалтерией) — не лучшая интерпретация этого шаблона проектирования.
Ниже приведен подход, который позволяет избежать двойного учета и по-прежнему обладает преимуществами основной идеи — предварительно сконфигурировать образец или шаблон объекта, а затем использовать этот экземпляр для инициализации многих других экземпляров.
class A {
int32_t id;
bool shootable;
bool moveable;
bool destructable;
public:
// A template instance specific constructor.
A(bool shoot, bool move, bool destruct)
: id(-1)
, shootable(shoot)
, moveable(move)
, destructable(destruct)
{
}
// One or more "real" instance constructors.
A(int32_t idval, const A& source)
: id(idval)
, shootable(source.shootable)
, moveable(source.moveable)
, destructable(source.destructable)
{
}
// ...
};
int main(int argc, const char *argv[])
{
A kind(true,false,true);
A instance0(1,kind);
A instance1(2,kind);
return 0;
}
В качестве варианта вышеупомянутой идеи, вы также можете хранить ссылку на экземпляр шаблона и действительно использовать 2 типа.
class UnitType
{
int32_t hp;
bool ranged;
//...
};
class Unit
{
int32_t id;
const UnitType *type;
// more data
public:
Unit(int32_t idval, const UnitType* unitType)
: id(idval)
, type(unitType)
{
}
//...
};
Конечно, тогда UnitType
экземпляр не должен быть доступен для записи экземплярами. И однажды, например, currentHp
, у вас есть другая форма дублирования для обработки. Кроме того, вам необходимо обеспечить срок службы UnitType
экземпляр шаблона превышает таковой каждого Unit
Например, используя его.