我正在更新旧的C ++代码,并且遇到了设计问题,需要有关最佳行动方案的建议。代码处理几何数据。目前,代码定义了许多处理元素类型的全局常量:
#define TETRAHEDRON 0
#define HEXAHEDRON 1
每个常量都有与之相关的信息,该信息保持不变,并且当前由一个类处理,在我们的拓扑中。
int Topology::nodesPerElement(int topType)
{
switch(topType) {
case TETRAHEDRON:
return 4;
break;
case HEXAHEDRON:
return 8;
break;
}
}
Topology类有许多这些函数,它们只需打开全局常量即可找出相关信息。有许多元素类型,并且switch语句引入了许多错误,这些错误不考虑所有元素类型。如果添加了元素类型,则需要修复所有这些方法。我需要一种更好的方法来保持关联信息的类型。
枚举是对此设计的改进,但它没有解决将数据与枚举关联的问题。
为简单起见,我想避免为每种类型实例化类,因为每个类只包含不会改变的静态数据。
我真正需要的是一个“静态类”,它保存这些信息并执行如下伪代码:
class Tetrahedron : public TopType {
static const int nodesPerElement = 4;
static const std::string name = "Tet";
etc...
}
拓扑中的每个方法都变得微不足道:
int Topology::nodesPerElement(TopType topType)
{
return topType.nodesPerElement;
}
有没有办法在C ++中执行此操作?我已经考虑过删除枚举并为每个拓扑类型分别设置子拓扑类,但我从其他人那里得到的反馈是,它太复杂了。我希望我的问题足够清楚。
答案 0 :(得分:8)
创建一个包含对象应支持的所有属性的基类,以及一个用于设置这些属性的私有构造函数。您不需要派生类,然后:您可以使用静态公共对象来创建所需的具有所需属性的对象。
class TopologyObject
{
private:
int numberVertices;
int numberFaces;
// etc.
public:
int getVertices() { return numberVertices; };
int getFaces() { return numberFaces; };
protected:
TopologyObject(int vertices, int faces) :
numberVertices(vertices),
numberFaces(faces)
{};
public:
static TopologyObject Tetrahedron = new TopologyObject(4, 4);
// etc.
}
您可以通过TopologyObject::Tetrahedron
访问具有所有属性的四面体。
如果您根据对象类型决定需要更复杂的变量行为,那么您确实需要派生类和虚拟方法来实现可重写行为。
答案 1 :(得分:3)
除非您的拓扑类型具有不同的运行时行为(比如绘制自己),否则我同意您的同行认为子分类是过度的。报告nodesPerElement和name之类的静态属性几乎不是运行时行为。
除非你没有告诉我们关于拓扑的整个故事,否则你需要的是一个简单的属性映射。使用std::map
将拓扑类型代码与拓扑属性结构相关联。这种重构类似于Replace Subclass with Fields。
以下是一些可能作为灵感的代码:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const struct {Topology::Code code; Topology topo;} topoTable_[] =
{
{Topology::tetrahedron, {4, "Tetrahedron"}},
{Topology::hexahedron, {6, "Hexahedron"}}
};
};
class TopologyMap // Singleton
{
public:
static TopologyMap lookup(Topology::Code code)
{
return Topology(instance().doLookup(code));
}
private:
typedef std::map<Topology::Code, Topology> Map;
Map map_;
TopologyMap()
{
// Initialize map with constant property table
size_t tableSize = sizeof(topoTable_) / sizeof(topoTable_[0]);
for (size_t row=0; row<tableSize; ++row)
{
map_[topoTable_[row].code] = topoTable_[row].topo;
}
}
static TopologyMap& instance()
{
static TopologyMap instance;
return instance;
}
const Topology& doLookup(Topology::Code code) const
{
Map::const_iterator match = map_.find(code);
assert(match != map_.end());
return match->second;
}
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
输出:
shape1 is a Tetrahedron with 4 nodes per element.
shape2 is a Hexahedron with 6 nodes per element.
如果拓扑代码从零开始并且是连续的,那么您可以使用简单的数组索引而不是映射。但是,如果有人对拓扑代码枚举感到困惑,则数组索引会更容易出错。以下是使用数组索引的相同示例:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron, CODE_COUNT};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const Topology topoTable_[] =
{
{4, "Tetrahedron"},
{6, "Hexahedron"}
};
};
class TopologyMap // Singleton
{
public:
static Topology lookup(Topology::Code code)
{
assert(code < Topology::CODE_COUNT);
return topoTable_[code];
}
private:
TopologyMap() {} // Non-instantiable
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
请注意,由于存储和检索Topology
的详细信息已封装在TopologyMap
中,因此我无需重写Shape
和main
中的任何代码。
答案 2 :(得分:2)
我不确定是谁建议你避免每个Toplogy类型的派生类。在我看来,这个问题是派生类的尖叫。
除非您需要大量此类课程。
答案 3 :(得分:2)
您可以拥有除静态成员变量之外的其他类。这是一种封装属性数据的好方法。
如果你不愿意这样做,traits可能会得到你想要的东西。
答案 4 :(得分:1)
我个人认为存储此信息的最佳方法是创建一个通用的Shape
类。然后,不是编写所有这些静态变量,而是将它们放在文件/数据库中,并在启动程序时从数据存储中加载形状信息。
答案 5 :(得分:1)
如果你的目标是避免类实例化,你不能使用记录吗?
但实际上,你应该把这个大便归类。
答案 6 :(得分:1)
如果topType是连续的并且开始为0,那么你可以只维护一个结构数组和索引,而不是尝试拥有类和子类。这样,您需要的唯一代码更改是
你的拓扑类型可以被建模为结构的一个实例(即没有方法等),类+派生类是过度的,IMO。
答案 7 :(得分:1)
由于(显然)所有相关数据在编译时都可用,因此一种可能性是使用枚举以及模板和专业化来完成工作:
enum { tetrahedron, hexahedron };
template <int type>
struct nodes_per_element { int operator()() const {
throw std::invalid_argument("Attempt to use unknown shape");
};
template <>
struct nodes_per_element<tetrahedron> { int operator()() const { return 4; } };
template <>
struct nodes_per_element<hexahedron> { int operator()() const { return 8; } };
您可以使用它:int x = nodes_per_element<hexahedron>()();
如果您尝试将其用于没有专门化的值,那将调用非专用模板,这将抛出异常,暂停程序和(通常)显示一条消息,说明您试图使用未知形状。当然,您可以自定义显示方式(如果有的话)。
这应该可以快速显示由于尚未定义的值而导致问题的位置。
另一个显而易见的可能性是为你将要使用的每个形状定义一个结构,并使用形状的名称作为数据的索引以及名称来创建这些结构的数组。您想要的特定数据将是结构的成员。对于您给出的每个元素的节点,看起来像:
struct shape_data {
int nodes_per_element;
std::string name;
};
shape_data data[] = {
{4, "Tetrahedron"},
{8, "Hexahedron" }
};
检索数据类似于:
shape_data &s = data[hexahedron];
std::cout << "A " << s.name << " has " << s.nodes_per_element << "nodes per element.\n";
答案 8 :(得分:1)
看了之前的答案,我决定添加自己的答案。
对我来说,我需要这样的设计有两件事:
这可以很容易做到,所以这里是我的一些代码:
class Solid
{
typedef std::vector<Solid> solids_type;
public:
Solid(std::string name, size_t faces, size_t nodes):
mName(name), mFaces(faces), mNodes(nodes)
{
}
///
/// Properties
///
const std::string& getName() const { return mName; }
size_t getFaces() const { return mFaces; }
size_t getNodes() const { return mNodes; }
///
/// Collection Handling
///
static bool Add(Solid solid); // only add if it's not already there.
///
/// struct Predicate: std::unary_function<Solid,bool>
///
template <class Predicate>
static const Solid* Get(Predicate pred)
{
solids_type::const_iterator it =
std::find_if(Solids().begin(), Solids().end(), pred);
return it == Solids().end()) ? 0 : &(*it);
} // Get
///
/// Some Predicates
///
class ByName: std::unary_function<Solid,bool>
{
public:
ByName(std::string name): mName(name) {}
bool operator()(const Solid& s) const { return s.getName() == mName; }
private:
std::string mName;
};
class ByFaces; /// ...
class ByNodes; /// ...
private:
/// Properties
std::string mName;
size_t mFaces;
size_t mNodes;
/// Collection
static solids_type& Solids()
{
static solids_type MSolids;
return MSolids;
}
}; // class Solid
因此,现在我们可以:
// in tetrahedron.cpp
namespace
{
bool gTetrahedron = Solid::Add(Solid("Tetrahedron", 4, 4));
}
// in main.cpp
int main(int argc, char* argv[])
{
const Solid* myTetra = Solid::Get(Solid::ByFaces(4));
assert(myTetra->getName() == "Tetrahedron");
assert(myTetra->getFaces() == 4);
assert(myTetra->getNodes() == 4);
return 0;
} // main
现在我们已经实现了目标:
我们也可以想象:
答案 9 :(得分:0)
这正是虚拟功能的用途。经典的方法是:
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
class Tetrahedrom : public Topology
{
public:
virtual nodesPerElement() const { return 4; }
// etc
}
// etc
但是如果你真的厌恶重新实现访问器方法(而不仅仅是定义变量),你可以使用模板执行以下操作(尽管它的确非常冗长):
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
template<typename T>
class ConcreteTopology : public Topology
{
public:
virtual int nodesPerElement() const { return T::nodesPerElement; }
// etc
};
struct Tetrahedron_Data {
int nodesPerElement = 4;
// etc
};
typedef ConcreteTypology<Tetraheadron_Data> Tetrahedron;
// etc