有空的子类,仅仅是为了指定,是一种糟糕的做法吗?
假设我有一个泛型产品类。有电气、电子和机械产品。我需要代表他们的所有,必须能够在某些操作中各不相同。我有两个选择:
我认为第二个选择是更好的方法,但这是正确的方法吗?我从未在任何应用程序或库中见过这样的东西。
你是怎么回事?有什么建议吗?
编辑:
下面是一个使用第二个选项的示例:https://gist.github.com/tsouzar/c2ffebbcdad30920bf06
我看不出更好的方法,但我不知道这是否是一个好的做法,有空的课程。
发布于 2014-12-04 15:09:15
现在您已经做了更充分的解释,似乎实际上没有子类特定的逻辑。在这种情况下,继承不是正确的选择。如果代码的其他部分真的只有很小的方式来处理产品,那么将代码放入产品类是错误的--这违反了将产品类与系统其他部分的知识隔离开来的做法。排序示例说明了这一点。产品不应该知道它们是如何排序、存储或显示的;如果对对象进行排序或呈现对象的代码发生了更改,则不需要重写产品类。
我很抱歉在这件事上怀疑你。我们看到这里有那么多人提出了基于糟糕的OO知识的问题,我认为这是我应该首先要求更多信息的情况。
如示例代码所示,方法重载选择实际上违背了关注点和干性分离等原则。add_product方法的共同点是什么?它将产品添加到适当的集合中。但是在示例代码中,这个单一的、基本相同的任务在三个不同的地方重复。这与if..then..else逻辑链几乎没有什么不同。
如果您确实需要为每种类型的产品提供一个单独的集合,那么一个更好的方法就是拥有一个产品集合的地图。关键是产品类型。然后,您可以有一个product_add方法,它引用该映射并将产品添加到正确的位置。即使您发现需要不同的产品子类,这个方法也不需要知道。例如,对象类本身可能是映射的关键。
由于多个方法可能希望更新或从这些集合中读取,所以最好有一个存储对象的Products对象。无论它包含N个不同集合的映射,还是它知道如何按类型筛选/搜索的集合,都是一个实现细节,不需要涉及代码的其他部分。
摘要中的
由于实际上似乎没有特定于产品的代码,额外的空类不仅是无用的,它们还会使代码变得更加脆弱。“类型”成员更好。
如果您这样做,您的代码不仅会更干净、更优雅(首先,您将不受丑陋的if..then..else链的影响),如果您确实发现了对产品子类的真正需求,那么几乎所有现有代码都不会改变。即使他们只需要改变他们的内部实现,因为他们的代码仍然不关心产品类或它的任何子类。
在各自的领域中添加新的类来封装“我知道有什么样的产品”仍然有很多乐趣。一个挑战是区分那些特定于域的代码(例如,只在一个领域中相关的集合中的排序)和那些实际上是通用的代码。例如,所有有效产品类型的列表应该是一个枚举,可以由任何产品感知域特定代码获取。任何映射都应该使用该枚举作为其键的类型。任何将产品“类型”作为参数的函数都应该只接受该枚举类型的参数,以此类推。现在,在您的代码中您要在哪里定义这个枚举呢?
发布于 2014-12-01 03:46:17
我认为你之所以在选择哪条道路上遇到困难,是因为这里有一个基本的设计缺陷。这里有贫血类,它本身就是一个OO反模式。打开类型的外部逻辑需要被纳入Product类家族。
代码的特点是,无论您走哪条路径,您都需要检查代码中其他位置的类型成员或instanceof。另一种感觉是,您将不断地查询Product对象中的数据,并根据数据做出决策(请参阅告诉-别-问)。这意味着很难修改或添加行为,因为您需要在许多不同的地方进行更改,而不是在适当的类中为每种类型保存该行为。
添加的代码细节显示,您的问题有更多的细微差别。让我们首先看看我们所谈论的“类型”的产品。
传统上,每当我们听到“类型”这个词时,我们就跳到类型的对象概念(我会用大写来区分)。此类型与多态行为、继承、封装和所有其他面向对象的概念相关联.不过,在这里,我们不一定要描述一个类型。我们讨论的是一个称为类型的数据属性。行为实际上并不是根据类型而变化的,而是基于值。
作为一个与之比较的例子,我们可以看整数。我们有int,S,1和2。它们之间的行为完全相同,只有值不同。这意味着我们不应该有一个抽象的int类型,它被实现为int1具体类型和int2具体类型等等。对于所有ints,我们需要一个具体类型,以及它们之间交互的简单方法(比如排序的比较)。你对产品类型的看法是一样的。
如果您的产品具有或将具有不同的多态行为,则继承是一个有效的解决方案。但是,在这里,您的类型只是一个数据属性,就像价格和品牌一样,没有行为上的差异。因此,您应该有一个类型属性,就像您有一个价格属性和一个品牌属性一样。
理想情况下,您将希望使代码更开放-关闭,因此应该有一种方法来做到这一点尽可能优雅。我们可以创建一个枚举,其中包含存在的不同类型的产品,按照它们需要输出的顺序排序(如果您愿意的话,在product上使用静态std::set或类似的产品可能更容易使用)。product的type属性将指向枚举值。在product_manager类中,应该有一个映射/字典类型结构,从类型枚举映射到从枚举中的元素生成的相应向量,而不是三个命名向量。这样,该结构将在实例化时自动为每种类型添加向量。添加产品时,根据产品类型选择要将其添加到的向量。然后,您可以在地图上迭代,然后遍历每个向量,打印出按类型分组的产品。
伪码:
enum product_type { electrical, electronic, mechanical }
class product {
//...
get_type() {
return this.type;
}
//...
}
class product_manager {
map<product_type, vector<product>> products;
product_manager() {
foreach(p_type in product_type) {
products.insert(p_type, new vector<product>);
}
}
//...
add_product(product p) {
products.at(p.get_type()).append(p);
}
//...
print_products() {
foreach(p_type in product_type) {
foreach(product in products.at(p_type)) {
print_product(product);
}
}
}
//...
}如果您不想在其他地方使用该类型,则可以将其设置为私有类型,并使product_manager成为product的朋友。
我还想简要介绍一下您发布的示例代码。
//在实际应用程序中,我们不知道实例化过程,因此不知道产品的类型。test::electrical_product *ep =新test::electrical_product;test::electronic_product *etp =新test::electronic_product;test::mechanical_product *mp =新test::mechanical_product;// set产品成员值pm.add_product(ep);pm.add_product(etp);pm.add_product(mp);pm.print_electrical_products();pm.print_electronic_products();pm.print_mechanical_products();
使用具有不同参数类型的方法重载来确定要调用的实现。问题是,在实际应用程序中,所有产品都是abstract_product *类型的。编译器根据对象的编译时类型路由方法调用,这意味着添加的任何产品都将发送到方法product_manager.add_product(abstract_product *),而不是重载的方法,这意味着它不会执行您的意愿。
发布于 2014-12-02 16:46:21
我的观点是,添加类型成员是一个更好的解决方案。定义它更简单,允许您将该类型视为一个头等值,允许您将Product编码为一个普通的旧数据结构(它是这样的),无需RTTI即可工作,您可以使用一个switch而不是多个if/else来有效地基于该类型进行分支。使用继承,如果有人引入一个新的子类,您的代码就会中断,因为您只需要有限数量的产品类型。
https://softwareengineering.stackexchange.com/questions/264171
复制相似问题