我正在尝试解析具有嵌套结构的二进制文件格式。在过程伪代码中,过程将是这样的:
// A structure contains:
// tag | oneof(a, b, c) | oneof(oneof(aa, ab, ac), oneof(ba, bb, bc), oneof(ca, cb, cc))
PROCEDURE parse() {
RECORD read_type;
read_tag(read_type);
if (read_type == TYPE_A) {
read_a(read_type);
if (read_type == TYPE_AA) {
read_aa();
} else if (read_type == TYPE_AB) {
read_ab();
} else if (read_type == TYPE_AC) {
read_ac();
}
} else if (read_type == TYPE_B) {
// see above
} else if (read_type == TYPE_C) {
// see above
}
}
如果没有来自其父对象 A 的上下文,则无法解释诸如 AA 之类的外部结构,而这又需要其标签/ header 来解释。使用这些结构时,操作包含 A、AA 等的结构是有意义的,但绝不能只操作结构的 A 或 AA 部分。
我的问题是如何为这个过程创建一个类模型。结构应该是:
class Base;
class A: Base;
class B: Base;
class C: Base;
class AA: A;
class AB: A;
class AC: A;
// ...
在这种情况下,AA 可以这样构造:
AA::AA(): A() {
read_aa();
}
A::A(): Base() {
read_a();
}
Base::Base() {
read_tag();
}
但是,问题是如果不先构造基对象,就不可能知道要构造什么派生对象。这可以通过使用构造函数 AA::AA(A*) 复制构造其父级来解决,但这似乎是不必要的低效率。此外,这需要一个外部工厂函数,例如:
Base *read_object() {
Base *base = new Base();
if (b->tag_type == TYPE_A) {
A *a = new A(base);
if (a->tag_type == TYPE_AA) {
return new AA(a);
} else if (a->tag_type == TYPE_AB) {
// ...
} else if (a->tag_type == TYPE_AC) {
// ...
}
} else if (b->tag_type == TYPE_B) {
// ...
} else if (b->tag_type == TYPE_C) {
// ...
}
}
另一种选择是让类引用结构的子区域,例如:
class CompleteStructure;
class StructureA;
class StructureB;
class StructureC;
class StructureAA;
class StructureAB;
class StructureAC;
// ...
class CompleteStructure {
union {StructureA a, StructureB b, StructureC c} sub;
}
class StructureA {
CompleteStructure *parent;
union {StructureAA aa, StructureAB ab, StructureAC ac} sub;
}
class StructureAA {
StructureA *parent;
}
在这种情况下,构造函数 CompleteStructure::CompleteStructure() 将读取标记,然后构造 StructureA、StructureB 或 StructureC 之一,后者将依次构造自己的子结构。这样做的问题是每个子结构都需要对其父结构的显式引用,以便“转换”层次结构并实现其方法/功能。
这些方法中的一种在空间/时间效率和“清洁度”方面是否优于另一种?是否有更好的第三种方法?
编辑: 为了回应下面的两个答案,问题是关于解析和对象行为的。我最初的目标只是从文件中读取结构,打印出它们的字段,然后以相同的顺序将它们写回磁盘。稍后,将有其他目标,例如找到 A 派生结构的所有实例并按特定字段对它们进行排序或检查结构的非法组合(例如同时具有 BA 和 BB)。
编辑2: 这是我引用的结构之一的示例架构(具有通用字段名称)。 u8/16/32指的是整数类型,sz是C字符串,大写的名字是需要读取的字段,常量的前缀是下划线。
DEF AA {
// Identifies and deliminates complete records.
TAG {
u32 SYNC_CODE = 0xFFFFFFFF;
}
// Metadata for high level identification of data.
A {
u32 TYPE = __TYPE_A;
u16 CATEGORY = __CATEGORY_1; // A defines the "category" of the following file data
u32 NUM_OF_KV_PAIRS;
for (int i = 0; i < NUM_OF_KV_PAIRS; ++i) { // unspecified metadata
sz KEY;
sz VALUE;
}
u8 HAS_EXTENSION_FLAG = true; // indicates presence of next record
if (!HAS_EXTENSION_FLAG) {
DEFAULT_PARAMS; // legacy
}
}
// Indicates a specific data layout and version.
AA {
u32 TYPE = __TYPE_AA;
u8[16] ACCESS_KEY;
u32 NUM_OFFSETS;
for (int i = 0; i < NUM_OFFSETS; ++i) {
// stuff
}
}
}
最佳答案
如果没有更具体的问题描述,很难回答某种方法是否在效率方面更好。您可以在下面找到一些值得思考的东西。
要点 1:在考虑类设计时,还值得检查所需的行为,而不仅仅是数据。事实上,用于存储的二进制格式可能暗示也可能不暗示层次结构,当然应该考虑在内,但这不应该是主要问题。
例如,假设我们有一个 Person
类,它有一个 height
字段和一个 Rectangle
类,它也有一个 高度
字段。他们都共享一些数据,但只有这些信息使他们彼此无关。如果我们定义上下文并说我们想在屏幕上绘制它们,那么 height
字段突然具有更具体的含义。现在继承 Drawable
可能更有意义。
您的问题是我们将如何使用它们?如果我们有 {A, B}
或 {AA, BB}
甚至 {A, BB}
的列表,我们可以执行哪些常见操作>?我们能以某种方式一起管理它们吗?这是您应该考虑的重要一点。
第 2 点:您说“操纵包含 A、包含 AA 等的结构是有意义的,但绝不只是操纵结构的 A 或 AA 部分”。所以我理解 AA
is-a A
,但反过来也是如此。如果是这种情况,那么将 Base, A, B, C
作为抽象类并且只能直接实例化最后一层 AA, BB
等是有意义的.
第 3 点:另一方面,如果不同的结构只定义一些数据而不定义某些行为,那么使用组合而不是继承可能会更好。例如。我们会在它们上调用一个方法,比如对数据进行操作的 process()
吗?还是我们想将结构本身用作数据?
class X {
Base base;
A a;
AA aa;
process() {
// this is different than calling base.process() + a.process() + aa.process()
// do we need one over the other? both?
process(base) + process(a) + process(aa);
}
}
第4点:关于读取时实例化的顺序,这个应该没有问题。也许您可以在临时存储信息时读取信息,并且仅在知道其完整类型(即达到最后一层)后才实例化一个类。
希望对你有帮助
关于java - 解析嵌套结构和对象模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17889873/