2009-11-11 54 views
11

对于我的软件开发编程类,我们应该为RSS提要制作一个“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他们为什么这样做,也许以后他们要验证在二传手的数据或在吸气剂中增加它。但是,为什么你们不这样做呢?UNTIL那种情况出现了吗?

public class Example 
{ 
    public int MyInt; 
} 

对不起,这是一种咆哮,并不是一个真正的问题,但冗余令我很生气。为什么当不需要的时候,吸气人员和安装人员如此热爱?

+6

如果你认为这是荒谬的,你还没有使用的库与具有返回单个Manager实例与返回与包含SQL字符串属性返回类映射类的单一方法的一种方法的单身工厂类声明。哦,每个中间阶层都有一个界面。 – 2009-11-11 02:31:54

+0

如果你这样做,直到它需要改变它已经太晚了。你有一个成员标题(这是一个字符串)。但有时候我们需要将实现改为char数组。然后不幸的是,你的实现被卡住了(每个人都已经在使用title(和它的公共API的一部分),你不能改变它们,所以你不能改变你的实现。您实现私有部分不外露,因而是可以改变的。 – 2009-11-11 04:04:29

+7

个人而言,我恨geters/setter方法(它的Java一样,而不是C++等)。你为什么要与对象的内部摆弄,它看起来并不像它是有道理的一旦创建就更改这些成员(但这取决于用法要求)我会让所有成员都是私有的,构造函数来创建它们并查看对象的使用方式。添加unnesacery getters/setters几乎与具有公共变量。 – 2009-11-11 04:12:42

回答

14

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

  • 你不想直接暴露数据成员。你总是希望能够控制它们的访问方式。在这种情况下,我同意这看起来有点荒谬,但它意在教会你这种风格,以便你习惯它。
  • 它有助于为类定义一致的接口。你总是知道如何去找东西 - >调用它的get方法。

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

+1

是的,我明白,标准接口是一个很好的论点。但考虑一下C#的例子,如果需求出现,两者之间的变化并不难,而且界面保持不变。在我的C++示例中,FeedItem永远不会需要额外的开销,它只是保存一些数据的一种便捷方式,而不是根本就不采取行动。 – y2k 2009-11-11 02:19:20

+1

也许关于你的C#示例,不过对于你的C++示例,你正在做出假设。良好软件工程的关键在于设计代码,以便在假设发生变化时轻松更改代码。尽管看起来有点荒谬,但说实话,在这种情况下,我认同你,可能没有必要,对你的代码有哪些假设可能会改变?也许这些网址是以相对的方式提供给你的,但你的代码假设它们是绝对的,改变这个类比改变使用它的3k程序容易得多:-) – 2009-11-11 02:23:16

+0

这通常不是什么大问题。如果您确实需要更改某人访问数据成员时发生的情况,则仍然可以更改该类接口以使其成为私有成员并提供getter方法。然后编译器会指出你需要修改代码的其余部分。没有太多的工作。 – StackedCrooked 2009-11-11 02:29:48

13

下面是关于这个问题的一个很好的讨论:Why use getters and setters

你要问自己的问题是:“这是怎么回事,当你意识到FeedItem.url确实需要进行验证,从现在的情况发生3个月但它已经直接从其他287类引用?”

+1

那么在这种情况下,我在存储URL之前验证了URL。该结构只是保存数据的一种便捷方式,而不是从内部执行。感谢那个链接太顺口了。 – y2k 2009-11-11 02:20:48

+0

您应该考虑(以面向对象的方式)谁(哪个类)负责验证URL。我认为FeedItem类验证URL是合理的,因为它是唯一可以保证URL始终有效的类。 – 2009-11-11 02:39:22

+1

每个人都说要验证URL会错过RSS提要的要点。没有必要验证网址!!!!!!!!!!只需显示供稿发布者说的内容,如果供稿发布者上的网址无效,而不在我身上。 – y2k 2009-11-11 09:26:41

7

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

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

预先做这件事更安全。

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

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

确实。在C#中,创建属性和创建公共字段一样简单,所以不妨做正确的事。 – 2010-06-18 13:45:40

+0

但是在C++中,你实际上可以写属性,可以作为左值使用。看[这里](http://stackoverflow.com/a/3634540/531179)一个例子。 – ulidtko 2012-03-19 03:48:33

0

我同意你的看法,但对系统生存是很重要的。在学校时,假装同意。换句话说,被降低对你是有害的,不值得为你的原则,意见或价值而加以标记。

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

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

+3

_“在学校时,假装同意,并且在工作时......假装同意,然后开始自己的事业并按照自己的方式行事”_下一句应该是“......和__然后___你会发现为什么其他人都是这样做的。“ – 2009-11-11 02:20:57

+1

嗯,我不感兴趣。有可能OP有一点关于封装的问题,有时候过于执行。 – 2009-11-11 02:21:30

+3

衷心不同意。如果你正在和一位教授合作,让你认定你对简单的好处的看法 - 找一位不同的教授。如果你正在和一个不愿意接受简单好处的雇主一起工作,那么找一个不会让你陷入纠结路径的雇主来调试。只要确保你始终准备好从讨论中找出那个 - 你是错的。 – Kzqai 2009-11-11 03:57:24

4

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

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

简单支付,这是一个有价值的功能。从不虔诚地做任何事情。

+0

当在罗马,入乡随俗做:) – StackedCrooked 2009-11-11 02:25:31

+1

有时候,这是在拍一部关于一类设计决定一个显著的因素。只要有意识就可以了。有时候这只是错误的,这种罗马式的做事方式就是事情失控的原因。然后你简化事情并解释你自己。有人可能会用蝙蝠击中你,你必须纠正自己,但我认为整个过程对于系统来说依然健康。 – wilhelmtell 2009-11-11 02:29:51

1

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

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

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

这也保留了封装。

+0

Eew。拿那个,约定。 – 2009-11-11 11:41:57

+2

对于不可变的值类型+1! – Tom 2009-11-12 05:27:19

1

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

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

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

+0

避免不必要的复杂性是一个有趣的说法,但它并没有站出来反对的反对意见认为,getter和setter提出一个很常见的界面和几乎所有的程序员都熟悉它。 – ulidtko 2012-03-19 04:03:32

3

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

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

现在让我们想象一下,你的FeedItem类已成为奇妙流行,到处都是正在使用的项目。您决定需要(如其他答案所建议的)验证已提供的网址。

快乐的日子里,你已经写了一个URL的setter。你编辑这个,验证URL并且抛出一个异常,如果它是无效的。你释放你的新版本,每个使用它的人都很高兴。 (让我们忽略检查与未检查的异常,以保持这一点)。

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

您可能已经保持方法签名相同,但是您已更改接口的语义,因此它打破了相关的代码。现在,您可以选择高地并告诉他们重新编写他们的程序,或者虚心地添加setURLAndValidate

+1

好点:如果这只是一个POD类,封装的价值会大大减少。 – itowlson 2009-11-11 02:34:26

+1

也许,而不是举行一个字符串,URL本身应该是验证自己的内容,因此,无论是FeedItem也不是这个类的任何客户端将需要验证它的类的实例。如果他们已经被给了一个URL实例,那么他们知道他们可以依赖它的有效性,因为它在实例构建时会被检查。这就是我如何将接近它 - 一个URL/URI不只是一个POD,它有严格的语法和可能提供这样的方法为getScheme()的gethostname(),getQueryString()等 – 2010-03-30 23:29:32

1

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

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

作为确保创建的模块可重复使用的技术,提出了封装。也就是说,有一个明确定义的接口,可以抽象出模块的细节,并且可以在稍后使用该模块。该界面还可以防止对象的误用。

您在课堂上构建的简单类没有充分说明对定义良好的界面的需求。说:“但是,为什么你们不这样做,直到出现这种情况?”将不会在现实生活。你在软件工程课程中学到的东西是设计其他程序员可以使用的软件。考虑到如.net框架和Java API提供的库的创建者完全需要这样的规范。如果他们认为封装太麻烦了,这些环境几乎不可能合作。

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

最后一点,封装还可以充分测试一个模块并合理确定它的工作原理。没有封装,测试和验证你的代码会更困难。

+1

这似乎是一个反尽管如此,原型制作视图。 – Kzqai 2009-11-11 03:52:08

+0

@Tchalvak不一定。目标是实现良好的封装类。没有理由不能以增量的方式来完成。 – 2009-11-11 04:17:52

4

如果事情是一个简单的结构,那么是可笑的,因为它只是数据。

这实际上仅仅是OOP开始的倒退,人们仍然没有得到类的想法。没有理由有数以百计的获取和设置方法,以防万一您可能将getId()有一天改为对哈勃望远镜的远程调用。

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

+0

但是在适当的OOP方法中,你可能永远不会使用结构。您似乎认为很少需要额外的代码才能访问成员,但事实并非如此。 底线,你的IDE可以自动生成默认的getter/setter和编译器可以优化出来。编写这个评论花费的时间比在10个类上自动创建getter/setter更长。 – 2009-11-11 11:40:06

+3

“适当的面向对象方法”的问题是并非所有的数据都是对象。有时候他们只是...数据。一些信息本身没有任何行为,但被系统的其他部分使用。如果它不是标量值,它必须是某种聚合结构,它将我们置于POD结构中。 – Tom 2009-11-12 05:29:14

1

吸气剂/固化剂当然是很好的做法,但是他们的写作很枯燥,甚至更糟糕。

有多少次我们读了一个有六个成员变量和伴随的getter/setter的类,每个类都有完整的hog @ param/@ return HTML编码,像'get the value of X'这样着名的无用评论'设置X'的值,'获取Y的值','设置Y的值','获取Z的值','设置Zzzzzzzzzzzzz的值。扑通!

+0

你不需要写它们。获得体面的IDE或插件。 – 2009-11-11 11:43:26

+5

@John:如果你的getters和setter非常愚蠢,你的IDE可以为你写它们,那么你只是证明了原来的海报的重点。明确的获取者和设置者的整个概念是根本上有缺陷的,只要人类可能尽快从软件工程中清除。 – 2009-11-12 03:35:17

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" }; 

点是你应该试着学习最好的做法或规范的做法是针对你正在使用的特定语言,而不是简单地复制那些不再有意义的旧习惯。

+0

+1。虽然C++在语言中没有属性语法(它们可以用一些模板进行模拟,但仍然可以),但是Bjarne似乎完全拒绝了它的需要,这是非常可惜的。 – ulidtko 2012-03-19 03:53:52

1

这是一个很常见的问题:“为什么你不人只是这样做,直到这种情况出现?”。 原因很简单:通常稍后不修复/重新测试/重新部署会便宜得多,但要在第一时间正确执行。 旧的估计表明,维护成本是80%,大部分的维护正是你所说的:只有在有人遇到问题时才做正确的事情。第一次做对,可以让我们专注于更有趣的事情,并提高生产力。

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