具有数据成员的抽象基类
Posted
技术标签:
【中文标题】具有数据成员的抽象基类【英文标题】:Abstract Base Class with Data Members 【发布时间】:2009-11-19 02:54:39 【问题描述】:如果我正在创建一个抽象基类,并且从它派生的类将具有一些相同的数据成员,最好将这些成员设为抽象基类中的私有成员并为它们提供受保护的访问权限?或者不打扰,只是将数据成员放在派生类中。这是用 C++ 编写的。
【问题讨论】:
【参考方案1】:在 OOP 设置中要问的主要问题是:这些数据属于哪里?
在继承关系中,数据(和功能)应在其或多或少不变的最高阶段定义。这促进了最大的模块化和代码重用。例如,假设有两个类:
class Human;
class Student : public Human;
在添加数据成员“m_Arms”时,我们根据以下问题确定“人类”级别是定义数据、其用途及其对派生类的可见性的最佳位置:
-
人类的特化是否需要人类手臂或多或少的不变行为?即他们能做一些“普通”人类通常做不到的事情吗? -(确定公共数据)。
学生(或其他可能的人类专业)是否需要直接访问它? (确定对子类的可见性)。
如果可见,哪些功能是常用的? (确定相关的常用功能)
应该从基类的角度考虑上下文——即使有一个额外的 is-a-Human 类可以做一些额外的事情,那么它也需要访问数据。例如如果出于某种原因,您选择 Robocop 类:public Human,您需要直接访问他的大腿才能将枪存放在里面。在这种架构下,Thigh 需要对 Human 的所有子类可见。
可以使用相同的数据模块化、功能模块化和可见性原则来优化架构。例如,在定义 Robocop 类时,可以将基类 Human 进一步提取,如下所示,以允许更改可见性,并随之更改功能。
class Human;
class NormalHuman : public Human; //declare Thigh private here.
class SuperHuman : public Human; //continue using Thigh as protected.
此外,Arms 本身可能是多态的,允许(请原谅意外的反乌托邦解释)基于工厂的架构使用人类部件模块化组装不同类型的人类。
【讨论】:
我同意;在 [许多] 情况下,在 [抽象] 基类中具有公共或受保护成员是合适的,以供派生类和/或此类派生类的用户使用。一个给定概念的本质部分可以很好地在基类中定义,即使该基类将依赖派生类来实现特定功能。有人可能会说,将这种内在和特殊功能混合在一起是一种代码味道,也许我的鼻子已经塞满了……无论如何,对于支持这种方法的这种模糊/抽象(没有双关语)的解释,+1。【参考方案2】:如果数据属于派生类,让派生类做它想要包含该数据的事情。
通过将这些数据放在基类中(不是私有的),你强制每个派生类都拥有它。例如,不应强制派生类做任何事情除非他们需要填写数据成员。基类定义派生类必须做什么,而不是它们应该如何做。
如果您发现可能有一个共同的主题,您可以创建一个包含这些成员和实现的派生类,然后将其作为那些想要使用它的人的基类。例如:
struct car
virtual ~car()
virtual unsigned year(void) const = 0;
virtual const std::string make(void) const = 0;
// Dodge cars can feel free to derive from this instead, it's just a helper
struct dodge_car
virtual ~car()
virtual unsigned year(void) const = 0;
const std::string make(void) const
static const std::string result = "Dodge";
return result;
等等。但是你看,任何派生类仍然可以选择实现整个汽车接口。这也提高了代码的清洁度。通过保持你的接口是一个真正的接口,实现细节不会成为阻碍。
您的基类使用的任何变量都应该是私有的,因为派生类不需要知道它是如何工作的,就像派生类的用户不需要知道派生类的内部是如何工作的一样。
【讨论】:
每个派生类都会有这些数据成员并且需要拥有它们。我知道数据成员需要是私有的,但假设每个派生类都有这些数据成员,我假设我也应该提供受保护的访问权限。 我仍然建议您制作纯抽象的基类,然后再提供一个辅助类。如果您的设计发生变化,您不必担心将这些成员强制使用新类,他们可以使用纯抽象类。 FWIW,做最少的工作来获得正确的虚拟 dtor 会占用与评论一样多的空间。 我可能会使用强制成员来实现 ABC,仅仅是因为 a)我知道派生的类将始终具有这些数据成员,并且 b)我是唯一一个在工作的人代码以及谁可能会修改它。但是,这是非常有用的建议,如果我将来遇到无法确定派生类约束的情况,我一定会考虑这个建议。 @R.佩特:你疯了吗,我保存了一行:P 好的,好的,我会添加它们。 @Person,没问题,尽管我仍然建议做一个接口和一个单独的公共基类。 :) 试试吧,它可以让你的代码更干净,通过分离接口和实现。【参考方案3】:如何将成员设为私有并提供受保护的访问权限?
派生类不能访问基类的私有成员。 派生类 A 和派生类 B 都需要您所说的那些数据成员吗?如果是,则将它们放入基类并使其受保护。
我知道,我实际上想发表评论,但我不知道如何发表评论。我可能需要更多的声望吗?
【讨论】:
不是答案。也许您打算发表评论? :) 他可能有,但声望低于 50 的成员不能发布 cmets。【参考方案4】:在编写基类时,不要考虑您的某些派生类会做什么,而要考虑所有它们必须做什么。换句话说,想想基类本身和它做出的保证——它的接口。
C++ 没有单独的“接口定义”概念,只是为此重用类。 (或避免在模板中输入。)因此,请注意编写抽象接口类的方式,以免对实现施加限制。
我没有回答“是”或“否”,因为您没有提供足够的信息,答案取决于其他细节;但如果你遵循我简要列出的指导方针,你的状态就会很好。
【讨论】:
【参考方案5】:在基类中包含一些数据(以及实现,即方法)并没有错。
基类可以是虚拟的,因为只有一个方法必须在派生类中实现。将这些[基类的]变量和方法设为私有、受保护甚至是公共的,取决于具体情况。
例如,基类可以有一个公共方法、一个受保护的方法和/或数据,以及一些私有方法。
【讨论】:
以上是关于具有数据成员的抽象基类的主要内容,如果未能解决你的问题,请参考以下文章
具有类继承和具有基类作为派生类中的数据成员之间有明显的区别吗?