C++ 原型模式会导致“之间的代码重复”;“实际对象”;及;原型;

C++ 原型模式会导致“之间的代码重复”;“实际对象”;及;原型;,c++,maintainability,prototype-pattern,C++,Maintainability,Prototype Pattern,在将原型模式应用到游戏中后,它很好地提高了代码的可维护性 然而,我开始担心,当实际对象变得更加细节化时, 我倾向于对对应的原型进行编码,使其更像实际对象的副本 (注意#代码中的v.2) 有两个坏信号(#v.2):- 1.原型中的重复代码与实际中的重复代码(#X) 2.乏味的字段复制。(#Y) 因此,我认为有些事情开始出错。 这种模式自然会导致新的可维护性问题 在实际情况中,actual2包含另一个actual1 为了采用原型模式,我在另一个对应的原型1中使用了相应的原型2:- class Act

在将原型模式应用到游戏中后,它很好地提高了代码的可维护性

然而,我开始担心,当实际对象变得更加细节化时,
我倾向于对对应的原型进行编码,使其更像实际对象的副本

(注意#代码中的v.2)

有两个坏信号(#v.2):-
1.原型中的重复代码与实际中的重复代码(#X)
2.乏味的字段复制。(#Y)

因此,我认为有些事情开始出错。
这种模式自然会导致新的可维护性问题

在实际情况中,
actual2
包含另一个
actual1

为了采用原型模式,我在另一个对应的
原型1
中使用了相应的
原型2
:-

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;
};
问题
  • (1) 原型模式创建新的可维护性问题是否常见
  • (2) 如果是,如何避免
  • (3) 如果没有,我错在哪里(特别是编码风格)。。。或者这种趋势(重复代码)根本不是问题(即我只是恐慌)
我糟糕的解决方案 我认为将重复部分封装到名为
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)
};

然而,它可能会增加
原型
实际
之间的不利内聚(即粘合或强关系)。因此,它可能会再次导致另一个新的可维护性问题。

您可以通过从原型中将“实际值”子类化来避免不必要的重复。例如:

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) {
        ...
    }
    ...
}

你的直觉是正确的,通过将“公共”字段组合在一起,你可以增加这些字段之间的耦合。从某种意义上讲,耦合和代码复制之间存在一种折衷。由您决定什么是最适合您的应用程序的可接受折衷方案。

您可以通过从原型中将“实际值”子类化来避免不必要的重复。例如:

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
实例的使用寿命。

可能使用不同的类型(以及随后的双重簿记)并不是该设计模式的最佳解释

请参阅下面的方法,它避免了双重簿记,并且仍然保留了基本思想的优点—预先配置一次示例或模板对象,然后使用该实例初始化许多其他实例

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
实例的使用寿命。

您能解释一下您对“原型模式”的理解吗?@Joel Cornett IMHO,“原型模式”是使用“虚拟、简化、重量轻、易于复制的对象”缓存我真正想要创建的对象的设置。我将其视为不同类和函数的可通过的复杂枚举-例如,无需创建实际的游戏对象。(如果有错,请纠正我):)也许我没有抓住重点,但我看不到这一切的好处。与实际对象有一个“模板实例”(类型相同)相比,不是这样,然后使用该实例初始化新实例,然后通过向新实例添加唯一数据来进一步配置新实例。另外,整个“原型”命名听起来有点像JavaScript程序员到C++;你能解释一下你对“原型模式”的理解吗?@Joel Cornett IMHO,“原型模式”是指使用“虚拟、简化、轻量级、易于复制对象”来缓存我真正想要创建的对象的设置。我将其视为不同类和函数的可通过的复杂枚举-例如,无需创建实际的游戏对象。(如果有错,请纠正我):)也许我没有抓住重点,但我看不到这一切的好处。与实际对象有一个“模板实例”(类型相同)相比,不是这样,然后使用该实例初始化新实例,然后通过向新实例添加唯一数据来进一步配置新实例。另外,整个“原型”命名听起来有点像JavaScript程序员到C++;感谢我对你的“权衡”一词印象深刻。这里有一点担心:我认为
实际
不是
原型
,例如
原型
可能有
位置
,但
实际
没有
位置
。例如,我可以允许设置