首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >特定的空子类

特定的空子类
EN

Software Engineering用户
提问于 2014-11-30 22:00:03
回答 5查看 2.6K关注 0票数 6

有空的子类,仅仅是为了指定,是一种糟糕的做法吗?

假设我有一个泛型产品类。有电气、电子和机械产品。我需要代表他们的所有,必须能够在某些操作中各不相同。我有两个选择:

  • 在product中添加一个名为"type“的成员,以指定它是电气产品、电子产品还是机械产品。
  • 创建产品的子类,为各种产品创建一个子类: ElectricalProduct、ElectronicProduct和MechanicalProduct。由于这些类在方法或成员(已经在父类中定义)上没有区别,所以它们都是空的。

我认为第二个选择是更好的方法,但这是正确的方法吗?我从未在任何应用程序或库中见过这样的东西。

你是怎么回事?有什么建议吗?

编辑:

下面是一个使用第二个选项的示例:https://gist.github.com/tsouzar/c2ffebbcdad30920bf06

我看不出更好的方法,但我不知道这是否是一个好的做法,有空的课程。

EN

回答 5

Software Engineering用户

回答已采纳

发布于 2014-12-04 15:09:15

现在您已经做了更充分的解释,似乎实际上没有子类特定的逻辑。在这种情况下,继承不是正确的选择。如果代码的其他部分真的只有很小的方式来处理产品,那么将代码放入产品类是错误的--这违反了将产品类与系统其他部分的知识隔离开来的做法。排序示例说明了这一点。产品不应该知道它们是如何排序、存储或显示的;如果对对象进行排序或呈现对象的代码发生了更改,则不需要重写产品类。

我很抱歉在这件事上怀疑你。我们看到这里有那么多人提出了基于糟糕的OO知识的问题,我认为这是我应该首先要求更多信息的情况。

,事实上.

如示例代码所示,方法重载选择实际上违背了关注点和干性分离等原则。add_product方法的共同点是什么?它将产品添加到适当的集合中。但是在示例代码中,这个单一的、基本相同的任务在三个不同的地方重复。这与if..then..else逻辑链几乎没有什么不同。

  • 逻辑是重复的,对共同行为的任何改变都必须在每个地方重复。错误很有可能也不会被发现。
  • 产品添加代码不需要知道哪些类型的产品是可用的。它所需要知道的是,有各种各样的产品,每一个应该添加到一个适当的集合。意识到这一点意味着每次添加新产品时都必须更改代码的这一部分。

如果您确实需要为每种类型的产品提供一个单独的集合,那么一个更好的方法就是拥有一个产品集合的地图。关键是产品类型。然后,您可以有一个product_add方法,它引用该映射并将产品添加到正确的位置。即使您发现需要不同的产品子类,这个方法也不需要知道。例如,对象类本身可能是映射的关键。

由于多个方法可能希望更新或从这些集合中读取,所以最好有一个存储对象的Products对象。无论它包含N个不同集合的映射,还是它知道如何按类型筛选/搜索的集合,都是一个实现细节,不需要涉及代码的其他部分。

摘要中的

由于实际上似乎没有特定于产品的代码,额外的空类不仅是无用的,它们还会使代码变得更加脆弱。“类型”成员更好。

  • 您的大部分代码甚至不需要知道有这样一个成员。
  • 在每个领域(应用程序中明显分开的部分),确定真正需要区分产品类型的几个部分。
  • 将它们隔离在特殊的助手类或函数中,这些类或函数将返回适当的产品(或执行与类型匹配的适当操作)。
  • 映射是非常有用的(它们不仅可以包含集合,还可以包含其他助手对象/函数,以便返回到其他代码,以便对该对象执行适当的操作),但是应该对特权代码进行隐藏,而特权代码确实需要知道产品类型。

如果您这样做,您的代码不仅会更干净、更优雅(首先,您将不受丑陋的if..then..else链的影响),如果您确实发现了对产品子类的真正需求,那么几乎所有现有代码都不会改变。即使他们只需要改变他们的内部实现,因为他们的代码仍然不关心产品类或它的任何子类。

在各自的领域中添加新的类来封装“我知道有什么样的产品”仍然有很多乐趣。一个挑战是区分那些特定于域的代码(例如,只在一个领域中相关的集合中的排序)和那些实际上是通用的代码。例如,所有有效产品类型的列表应该是一个枚举,可以由任何产品感知域特定代码获取。任何映射都应该使用该枚举作为其键的类型。任何将产品“类型”作为参数的函数都应该只接受该枚举类型的参数,以此类推。现在,在您的代码中您要在哪里定义这个枚举呢?

票数 2
EN

Software Engineering用户

发布于 2014-12-01 03:46:17

我认为你之所以在选择哪条道路上遇到困难,是因为这里有一个基本的设计缺陷。这里有贫血类,它本身就是一个OO反模式。打开类型的外部逻辑需要被纳入Product类家族。

代码的特点是,无论您走哪条路径,您都需要检查代码中其他位置的类型成员或instanceof。另一种感觉是,您将不断地查询Product对象中的数据,并根据数据做出决策(请参阅告诉-别-问)。这意味着很难修改或添加行为,因为您需要在许多不同的地方进行更改,而不是在适当的类中为每种类型保存该行为。

编辑:

添加的代码细节显示,您的问题有更多的细微差别。让我们首先看看我们所谈论的“类型”的产品。

传统上,每当我们听到“类型”这个词时,我们就跳到类型的对象概念(我会用大写来区分)。此类型与多态行为、继承、封装和所有其他面向对象的概念相关联.不过,在这里,我们不一定要描述一个类型。我们讨论的是一个称为类型的数据属性。行为实际上并不是根据类型而变化的,而是基于值。

作为一个与之比较的例子,我们可以看整数。我们有int,S,12。它们之间的行为完全相同,只有值不同。这意味着我们不应该有一个抽象的int类型,它被实现为int1具体类型和int2具体类型等等。对于所有ints,我们需要一个具体类型,以及它们之间交互的简单方法(比如排序的比较)。你对产品类型的看法是一样的。

如果您的产品具有或将具有不同的多态行为,则继承是一个有效的解决方案。但是,在这里,您的类型只是一个数据属性,就像价格和品牌一样,没有行为上的差异。因此,您应该有一个类型属性,就像您有一个价格属性和一个品牌属性一样。

理想情况下,您将希望使代码更开放-关闭,因此应该有一种方法来做到这一点尽可能优雅。我们可以创建一个枚举,其中包含存在的不同类型的产品,按照它们需要输出的顺序排序(如果您愿意的话,在product上使用静态std::set或类似的产品可能更容易使用)。product的type属性将指向枚举值。在product_manager类中,应该有一个映射/字典类型结构,从类型枚举映射到从枚举中的元素生成的相应向量,而不是三个命名向量。这样,该结构将在实例化时自动为每种类型添加向量。添加产品时,根据产品类型选择要将其添加到的向量。然后,您可以在地图上迭代,然后遍历每个向量,打印出按类型分组的产品。

伪码:

代码语言:javascript
复制
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 *),而不是重载的方法,这意味着它不会执行您的意愿。

票数 4
EN

Software Engineering用户

发布于 2014-12-02 16:46:21

我的观点是,添加类型成员是一个更好的解决方案。定义它更简单,允许您将该类型视为一个头等值,允许您将Product编码为一个普通的旧数据结构(它是这样的),无需RTTI即可工作,您可以使用一个switch而不是多个if/else来有效地基于该类型进行分支。使用继承,如果有人引入一个新的子类,您的代码就会中断,因为您只需要有限数量的产品类型。

票数 1
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/264171

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档