15

对于我的软件开发编程课程,我们应该为 RSS 提要制作一个“提要管理器”类型的程序。这是我如何处理 FeedItems 的实现。

很好很简单:

struct FeedItem {
    string title;
    string description;
    string url;
}

我为此被标记了,“正确”的示例答案如下:

class FeedItem
{
public:
    FeedItem(string title, string description, string url);

    inline string getTitle() const { return this->title; }
    inline string getDescription() const { return this->description; }
    inline string getURL() const { return this->url; }

    inline void setTitle(string title) { this->title = title; }
    inline void setDescription(string description){ this->description = description; }
    inline void setURL(string url) { this->url = url; }

private:
    string title;
    string description;
    string url;
};

现在对我来说,这似乎很愚蠢。老实说,我不敢相信我被标记了,当这和我做的完全一样的事情时,我做的事情要多得多。


它让我想起在 C# 中人们总是这样做的:

public class Example
{
    private int _myint;

    public int MyInt
    {
        get
        {
            return this._myint;
        }
        set
        {
            this._myint = value;
        }
    }
}

我的意思是我知道他们为什么这样做,也许稍后他们想验证 setter 中的数据或在 getter 中增加它。但是,为什么你们不这样做,直到出现这种情况?

public class Example
{
    public int MyInt;
}

抱歉,这是一种咆哮,而不是真正的问题,但冗余让我抓狂。为什么 getter 和 setter 在不需要的时候如此受欢迎?

4

13 回答 13

14

这是“最佳实践”和风格的问题。

  • 您永远不想直接公开您的数据成员。您总是希望能够控制它们的访问方式。我同意,在这种情况下,这似乎有点荒谬,但它旨在教你这种风格,让你习惯它。
  • 它有助于为类定义一致的接口。你总是知道如何得到一些东西 --> 调用它的 get 方法。

然后还有可重用性问题。说,在路上,你需要改变当有人访问数据成员时发生的事情。您可以在不强制客户端重新编译代码的情况下做到这一点。您可以简单地更改类中的方法并保证使用新逻辑。

于 2009-11-11T02:15:44.080 回答
13

这是关于这个主题的一个很好的长 SO 讨论:为什么使用 getters 和 setters

您想问自己的问题是“从现在起 3 个月后,当您意识到FeedItem.url 确实需要验证但它已经直接从 287 个其他类中引用时会发生什么?”

于 2009-11-11T02:14:35.680 回答
7

在需要之前这样做的主要原因是版本控制。

字段的行为与属性不同,尤其是在将它们用作左值时(通常不允许这样做,尤其是在 C# 中)。此外,如果您以后需要添加属性获取/设置例程,您将破坏您的 API - 您的类的用户将需要重写他们的代码以使用新版本。

预先执行此操作要安全得多。

顺便说一句,C# 3 使这更容易:

public class Example
{
    public int MyInt { get; set; }
}
于 2009-11-11T02:16:39.260 回答
6

我绝对同意你的看法。但在生活中,你或许应该做正确的事:在学校里,就是为了取得好成绩。在您的工作场所,它是为了满足规格。如果您想固执己见,那没关系,但请解释一下自己——在评论中涵盖您的基础,以尽量减少您可能受到的损害。

在您上面的特定示例中,我可以看到您可能想要验证 URL。也许你甚至想要清理标题和描述,但无论哪种方式,我认为这是你可以在课程设计的早期告诉的那种事情。在评论中说明您的意图和理由。如果你不需要验证,那么你就不需要 getter 和 setter,你是绝对正确的。

简单是有回报的,这是一个有价值的功能。永远不要虔诚地做任何事情。

于 2009-11-11T02:21:17.383 回答
4

如果某些东西是一个简单的结构,那么是的,这很荒谬,因为它只是数据。

这实际上只是对 OOP 开始的一种回归,那时人们还没有完全理解类的概念。没有理由拥有数百个 get 和 set 方法,以防万一有一天您可能将 getId() 更改为对哈勃望远镜的远程调用。

你真的想要顶层的功能,在底层它毫无价值。IE 你会有一个复杂的方法,它被发送一个纯虚拟类来处理,保证不管下面发生什么它仍然可以工作。只是将它随机放置在每个结构中是一个笑话,永远不应该为 POD 这样做。

于 2009-11-11T03:50:22.453 回答
3

也许这两个选项都有点错误,因为该类的两个版本都没有任何行为。如果没有更多的上下文,很难进一步评论。

http://www.pragprog.com/articles/tell-dont-ask

现在让我们想象一下,您的FeedItem课程非常受欢迎,并且被各地的项目所使用。您决定需要(正如其他答案所建议的那样)验证已提供的 URL。

快乐的日子,你为 URL 写了一个 setter。您编辑它,验证 URL 并在它无效时抛出异常。您发布了新版本的课程,每个使用它的人都很高兴。(让我们忽略已检查和未检查的异常以保持正常运行)。

除了,然后你接到一个愤怒的开发人员的电话。当他们的应用程序启动时,他们正在从文件中读取提要项列表。而现在,如果有人在配置文件中犯了一个小错误,你的新异常就会被抛出,整个系统不会启动,只是因为一个 frigging feed 项目是错误的!

您可能保持方法签名相同,但是您更改了接口的语义,因此它破坏了依赖代码。现在,您可以占据制高点并告诉他们正确地重新编写他们的程序,或者您谦虚地添加setURLAndValidate.

于 2009-11-11T02:25:40.510 回答
3

请记住,编码“最佳实践”通常会因编程语言的进步而过时。

例如,在 C# 中,getter/setter 概念已以属性的形式融入语言。C# 3.0 通过引入自动属性使这变得更容易,编译器会自动为您生成 getter/setter。C# 3.0 还引入了对象初始化器,这意味着在大多数情况下,您不再需要声明简单地初始化属性的构造器。

因此,执行您正在做的事情的规范 C# 方法如下所示:

class FeedItem
{
    public string Title { get; set; } // automatic properties
    public string Description { get; set; }
    public string Url { get; set; }
};

用法看起来像这样(使用对象初始化器):

FeedItem fi = new FeedItem() { Title = "Some Title", Description = "Some Description", Url = "Some Url" };

关键是您应该尝试了解针对您正在使用的特定语言的最佳实践或规范的做事方式,而不是简单地复制不再有意义的旧习惯。

于 2009-11-12T02:58:31.590 回答
1

作为一名 C++ 开发人员,我让我的成员始终是私有的,只是为了保持一致。所以我一直都知道我需要输入 px(),而不是 px

另外,我通常避免实现 setter 方法。我没有更改对象,而是创建了一个新对象:

p = Point(p.x(), p.y() + 1);

这也保留了封装。

于 2009-11-11T02:23:19.883 回答
1

绝对有一点封装变得荒谬。

代码中引入的抽象越多,您的前期教育和学习曲线成本就越高。

每个了解 C 的人都可以调试一个仅使用基本语言 C 标准库的 1000 行代码的可怕编写函数。不是每个人都能调试你发明的框架。必须权衡每个引入的级别封装/抽象与成本。这并不是说它不值得,但与往常一样,您必须为您的情况找到最佳平衡点。

于 2009-11-11T02:25:37.650 回答
1

软件行业面临的问题之一是可重用代码的问题。它是一个大问题。在硬件世界中,硬件组件设计一次,然后在您购买组件并将它们组合在一起以制造新事物时重复使用该设计。

在软件世界中,每次我们需要一个组件时,我们都会一次又一次地设计它。它非常浪费。

封装被提议作为一种技术来确保所创建的模块是可重用的。也就是说,有一个明确定义的接口,将模块的细节抽象出来,方便以后使用该模块。该接口还可以防止滥用对象。

您在类中构建的简单类并不能充分说明对定义良好的接口的需求。说“但是为什么你们不这样做,直到出现这种情况?” 不会在现实生活中工作。你在软件工程课程中学到的是设计其他程序员可以使用的软件。考虑到由 .net 框架和 Java API 提供的库的创建者绝对需要这个规则。如果他们认为封装太麻烦,这些环境几乎无法使用。

遵循这些准则将在未来产生高质量的代码。为该领域增加价值的代码,因为从中受益的不仅仅是您自己。

最后一点,封装还可以充分测试一个模块并合理地确保它可以工作。如果没有封装,您的代码的测试和验证将会更加困难。

于 2009-11-11T02:26:27.497 回答
1

当然,Getter/Setter 是很好的做法,但它们编写起来很乏味,更糟糕的是,阅读起来也很乏味。

有多少次我们读过一个包含六个成员变量和伴随的 getter/setter 的类,每个都有完整的 @param/@return HTML 编码,著名的无用注释,例如“获取 X 的值”、“设置X', '获取 Y 的值', '设置 Y 的值', '获取 Z 的值', '设置 Zzzzzzzzzzzzz 的值。扑通!

于 2009-11-11T05:28:59.237 回答
1

这是一个非常常见的问题:“但是为什么你们不这样做,直到出现这种情况?”。原因很简单:通常不稍后修复/重新测试/重新部署它会便宜得多,而是第一次就做好。旧的估计说维护成本是 80%,而大部分维护正是您所建议的:只有在有人出现问题后才做正确的事。第一次做对可以让我们专注于更有趣的事情并提高工作效率。

马虎的编码通常是非常无利可图的——你的客户不满意,因为产品不可靠,而且他们在使用它时效率不高。开发人员也不高兴——他们将 80% 的时间花在打补丁上,这很无聊。最终,您最终可能会失去客户和优秀的开发人员。

于 2010-10-23T16:22:21.860 回答
0

我同意你的观点,但在这个系统中生存很重要。在学校时,假装同意。换句话说,被贬低对你是有害的,因为你的原则、观点或价值观而被贬低是不值得的。

此外,在团队或雇主工作时,假装同意。以后,自己创业,按照自己的方式去做。当你尝试他人的方式时,对他们保持冷静的开放态度——你可能会发现这些经历会重新塑造你的观点。

封装在理论上很有用,以防内部实现发生变化。例如,如果每个对象的 URL 成为计算结果而不是存储值,那么 getUrl() 封装将继续工作。但我怀疑你已经听说过它的这一面。

于 2009-11-11T02:16:58.260 回答