封装是否荒谬?

时间:2009-11-11 02:12:27

标签: c# c++ encapsulation yagni

对于我的软件开发编程课,我们应该为RSS feed制作一个“Feed Manager”类型的程序。以下是我处理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;
        }
    }
}

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

public class Example
{
    public int MyInt;
}

对不起,这是一种咆哮,而不是一个真正的问题,但冗余让我很难过。为什么吸气者和制定者在不需要的时候如此受欢迎?

13 个答案:

答案 0 :(得分:14)

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

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

然后还有可重用性问题。说,在未来的道路上,您需要更改有人访问数据成员时发生的情况。您可以在不强制客户端重新编译代码的情况下执行此操作。您只需更改类中的方法并确保使用新逻辑。

答案 1 :(得分:13)

这是关于这个主题的长篇SO讨论:Why use getters and setters

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

答案 2 :(得分:7)

在需要之前执行此操作的主要原因是版本控制。

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

预先做到这一点要安全得多。

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

public class Example
{
    public int MyInt { get; set; }
}

答案 3 :(得分:4)

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

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

简单性付出,这是一个有价值的功能。绝不做任何虔诚的事。

答案 4 :(得分:4)

如果某个东西是一个简单的结构,那么它是荒谬的,因为它只是DATA。

这实际上只是对OOP开始的回归,人们仍然根本没有理解课程。没有理由拥有数百种get和set方法,以防万一你可能有一天将getId()改为对远程望远镜的远程调用。

你真的希望TOP级别的功能,在底部它是毫无价值的。 IE你将有一个复杂的方法,发送一个纯虚拟类来工作,保证它仍然可以工作,无论下面发生什么。只是将它随机地放在每个结构中都是一个笑话,它永远不应该用于POD。

答案 5 :(得分:3)

也许这两个选项都有点不对,因为这两个版本都没有任何行为。没有更多的背景,很难进一步评论。

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

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

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

除此之外,你会接到愤怒的开发者的电话。他们在应用程序启动时从文件中读取了一个feeditems列表。而现在,如果有人在配置文件中犯了一个小错误,则抛出新的异常并且整个系统无法启动,只是因为一个frigging feed项是错误的!

您可能保持方法签名相同,但您已更改了接口的语义,因此它会破坏相关代码。现在,您可以采取行动,告诉他们重新编写他们的程序,或者谦虚地添加setURLAndValidate

答案 6 :(得分: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" };

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

答案 7 :(得分:1)

作为一名C ++开发人员,我使我的成员始终保持私密,只是为了保持一致。所以我总是知道我需要输入p.x(),而不是p.x。

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

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

这也保留了封装。

答案 8 :(得分:1)

绝对有一个封装变得荒谬的地方。

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

每个知道C的人都可以调试一个只使用基本语言C标准库的可怕编写的1000行函数。不是每个人都可以调试你发明的框架。每个引入的级别封装/抽象必须与成本进行权衡。这并不是说它不值得,但一如既往,你必须找到适合你情况的最佳平衡。

答案 9 :(得分:1)

软件行业面临的一个问题是可重用代码的问题。这是一个大问题。在硬件领域,硬件组件只需设计一次,然后当您购买组件并将它们组合在一起制作新东西时,设计将在以后重复使用。

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

封装被认为是一种确保创建的模块可重用的技术。也就是说,有一个明确定义的接口,它抽象出模块的细节,并使以后更容易使用该模块。该界面还可以防止滥用对象。

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

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

最后一点,封装还可以充分测试模块,并确保其正常工作。如果没有封装,测试和验证代码会更加困难。

答案 10 :(得分:1)

Getters / Setters当然是一种很好的做法,但写作更乏味,更糟糕的是阅读。

我们读了几次有六个成员变量和伴随的getter / setter的类,每个都有完整的hog @ param / @返回HTML编码,着名无用的评论,如'获取X的值','设置X'的值,'得到Y的值','设置Y的值','得到Z的值','设置Zzzzzzzzzzzzz的值。扑通!

答案 11 :(得分:1)

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

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

答案 12 :(得分:0)

我同意你的观点,但在系统中生存是很重要的。在学校时,假装同意。换句话说,被记下来对你有害,不值得你记下你的原则,意见或价值观。

此外,在团队或雇主工作时,假装同意。之后,开始自己的事业并按照自己的方式行事。当你尝试别人的方式时,要冷静地对待他们 - 你可能会发现这些经历重塑了你的观点。

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