封装有没有变得荒谬的地方?
在我的软件开发编程课程中,我们应该为 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 在不需要的时候却如此受欢迎?
For my software development programming class we were supposed to make a "Feed Manager" type program for RSS feeds. Here is how I handled the implementation of FeedItems.
Nice and simple:
struct FeedItem {
string title;
string description;
string url;
}
I got marked down for that, the "correct" example answer is as follows:
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;
};
Now to me, this seems stupid. I honestly can't believe I got marked down, when this does the exact same thing that mine does with a lot more overhead.
It reminds me of how in C# people always do this:
public class Example
{
private int _myint;
public int MyInt
{
get
{
return this._myint;
}
set
{
this._myint = value;
}
}
}
I mean I GET why they do it, maybe later on they want to validate the data in the setter or increment it in the getter. But why don't you people just do THIS UNTIL that situation arises?
public class Example
{
public int MyInt;
}
Sorry this is kind of a rant and not really a question, but the redundancy is maddening to me. Why are getters and setters so loved, when they are unneeded?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
这是“最佳实践”和风格的问题。
还有可重用性问题。比如说,将来您需要更改当有人访问数据成员时发生的情况。您无需强制客户端重新编译代码即可做到这一点。您可以简单地更改类中的方法并保证使用新的逻辑。
It's an issue of "best practice" and style.
Then there's also the reusability issue. Say, down the road, you need to change what happens when somebody accesses a data member. You can do that without forcing clients to recompile code. You can simply change the method in the class and guarantee that the new logic is utilized.
这是关于这个主题的一个很好的长讨论:为什么使用 getters 和 setters。
您想问自己的问题是“从现在起 3 个月后,当您意识到
FeedItem.url
确实 需要验证但它已被直接从 287 引用时,会发生什么其他班级?”Here's a nice long SO discussion on the subject: Why use getters and setters.
The question you want to ask yourself is "What's going to happen 3 months from now when you realize that
FeedItem.url
does need to be validated but it's already referenced directly from 287 other classes?"在需要之前执行此操作的主要原因是为了版本控制。
字段的行为与属性不同,特别是当将它们用作左值时(通常不允许这样做,尤其是在 C# 中)。另外,如果您稍后需要添加属性获取/设置例程,您将破坏您的 API - 您的类的用户将需要重写他们的代码才能使用新版本。
提前这样做会更安全。
顺便说一句,C# 3 使这变得更容易:
The main reason to do this before its needed is for versioning.
Fields behave differently than properties, especially when using them as an lvalue (where it's often not allowed, especially in C#). Also, if you need to, later, add property get/set routines, you'll break your API - users of your class will need to rewrite their code to use the new version.
It's much safer to do this up front.
C# 3, btw, makes this easier:
我完全同意你的观点。但在生活中,你可能应该做正确的事:在学校,就是取得好成绩。在你的工作场所,它是为了满足规格。如果你想固执己见,那也没关系,但一定要解释一下自己——在评论中表达你的观点,以尽量减少你可能受到的伤害。
在上面的特定示例中,我可以看到您可能想要验证 URL。也许您甚至想清理标题和描述,但无论哪种方式,我认为这是您可以在课程设计的早期告诉我们的事情。在评论中陈述你的意图和理由。如果您不需要验证,那么您就不需要 getter 和 setter,您是绝对正确的。
简单是有好处的,这是一个很有价值的功能。永远不要虔诚地做任何事情。
I absolutely agree with you. But in life you should probably do The Right Thing: in school, it's to get good marks. In your workplace it's to fulfill specs. If you want to be stubborn, then that's fine, but do explain yourself -- cover your bases in comments to minimize the damage you might get.
In your particular example above I can see you might want to validate, say, the URL. Maybe you'd even want to sanitize the title and the description, but either way I think this is the sort of thing you can tell early on in the class design. State your intentions and your rationale in comments. If you don't need validation then you don't need a getter and setter, you're absolutely right.
Simplicity pays, it's a valuable feature. Never do anything religiously.
如果某个东西是一个简单的结构,那么是的,它很荒谬,因为它只是数据。
这实际上只是 OOP 刚开始时的倒退,当时人们仍然根本不了解类的概念。没有理由拥有数百个 get 和 set 方法,以防有一天您可能将 getId() 更改为对哈勃望远镜的远程调用。
你确实想要顶层的功能,但在底层它毫无价值。 IE 中,您将拥有一个复杂的方法,该方法被发送给一个纯虚拟类来处理,保证无论下面发生什么情况它仍然可以工作。仅仅将其随机放置在每个结构中是一个笑话,并且永远不应该对 POD 这样做。
If something's a simple struct, then yes it's ridiculous because it's just DATA.
This is really just a throwback to the beginning of OOP where people still didn't get the idea of classes at all. There's no reason to have hundreds of get and set methods just in case you might change getId() to be an remote call to the hubble telescope some day.
You really want that functionality at the TOP level, at the bottom it's worthless. IE you would have a complex method that was sent a pure virtual class to work on, guaranteeing it can still work no matter what happens below. Just placing it randomly in every struct is a joke, and it should never be done for a POD.
也许这两个选项都有点错误,因为这两个版本的类都没有任何行为。如果没有更多背景,很难进一步发表评论。
请参阅http://www.pragprog.com/articles/tell-dont-ask
现在让我们想象一下您的
FeedItem
类已经变得非常流行并且正在被各地的项目所使用。您决定需要(正如其他答案所建议的那样)验证已提供的 URL。快乐的日子,您已经为 URL 编写了 setter。您可以编辑此内容,验证 URL,如果无效则抛出异常。您发布了该类的新版本,每个使用它的人都很高兴。 (让我们忽略检查异常与非检查异常,以保持正常运行)。
除非,然后你接到愤怒的开发人员的电话。当他们的应用程序启动时,他们正在从文件中读取提要项目列表。现在,如果有人在配置文件中犯了一个小错误,就会抛出新的异常,并且整个系统无法启动,只是因为一个该死的提要项出错了!
您可能保持方法签名相同,但更改了接口的语义,因此它破坏了相关代码。现在,您可以占据制高点并告诉他们正确地重写程序,或者您谦虚地添加
setURLAndValidate
。Maybe both options are a bit wrong, because neither version of the class has any behaviour. It's hard to comment further without more context.
See http://www.pragprog.com/articles/tell-dont-ask
Now lets imagine that your
FeedItem
class has become wonderfully popular and is being used by projects all over the place. You decide you need (as other answers have suggested) validate the URL that has been provided.Happy days, you have written a setter for the URL. You edit this, validate the URL and throw an exception if it is invalid. You release your new version of the class and everyone one using it is happy. (Let's ignored checked vs unchecked exceptions to keep this on-track).
Except, then you get a call from an angry developer. They were reading a list of feeditems from a file when their application starts up. And now, if someone makes a little mistake in the configuration file your new exception is thrown and the whole system doesn't start up, just because one frigging feed item was wrong!
You may have kept the method signature the same, but you have changed the semantics of the interface and so it breaks dependant code. Now, you can either take the high-ground and tell them to re-write their program right or you humbly add
setURLAndValidate
.请记住,编码“最佳实践”通常会因编程语言的进步而变得过时。
例如,在 C# 中,getter/setter 概念已以属性的形式融入到语言中。 C# 3.0 通过引入自动属性使这变得更容易,编译器会自动为您生成 getter/setter。 C# 3.0 还引入了对象初始值设定项,这意味着在大多数情况下,您不再需要声明仅初始化属性的构造函数。
因此,执行您正在执行的操作的规范 C# 方法将如下所示:
用法将如下所示(使用对象初始值设定项):
重点是您应该尝试了解最佳实践或规范的执行方式是什么针对您正在使用的特定语言,而不是简单地复制不再有意义的旧习惯。
Keep in mind that coding "best practices" are often made obsolete by advances in programming languages.
For example, in C# the getter/setter concept has been baked into the language in the form of properties. C# 3.0 made this easier with the introduction of automatic properties, where the compiler automatically generates the getter/setter for you. C# 3.0 also introduced object initializers, which means that in most cases you no longer need to declare constructors which simply initialize properties.
So the canonical C# way to do what you're doing would look like this:
And the usage would look like this (using object initializer):
The point is that you should try and learn what the best practice or canonical way of doing things are for the particular language you are using, and not simply copy old habits which no longer make sense.
作为一名 C++ 开发人员,我将我的成员设置为私有,只是为了保持一致。所以我总是知道我需要输入 px(),而不是 px
另外,我通常避免实现 setter 方法。我没有更改对象,而是创建了一个新对象:
这也保留了封装性。
As a C++ developer I make my members always private simply to be consistent. So I always know that I need to type p.x(), and not p.x.
Also, I usually avoid implementing setter methods. Instead of changing an object I create a new one:
This preserves encapsulation as well.
绝对有一个点,封装会变得荒谬。
代码中引入的抽象越多,您的前期教育和学习曲线成本就越高。
每个了解 C 的人都可以调试仅使用基本语言 C 标准的写得很糟糕的 1000 行函数图书馆。不是每个人都可以调试您发明的框架。每个引入的级别封装/抽象都必须权衡成本。这并不是说它不值得,但一如既往,您必须找到适合您情况的最佳平衡点。
There absolutely is a point where encapsulation becomes ridiculous.
The more abstraction that is introduced into code the greater your up-front education, learning-curve cost will be.
Everyone who knows C can debug a horribly written 1000 line function that uses just the basic language C standard library. Not everyone can debug the framework you've invented. Every introduced level encapsulation/abstraction must be weighed against the cost. That's not to say its not worth it, but as always you have to find the optimal balance for your situation.
软件行业面临的问题之一是可重用代码的问题。这是一个大问题。在硬件世界中,硬件组件被设计一次,然后当您购买组件并将它们组合在一起以制造新东西时,可以重复使用该设计。
在软件世界中,每次我们需要一个组件时,我们都会一次又一次地设计它。其非常浪费。
封装被提议作为一种确保所创建的模块可重用的技术。也就是说,有一个明确定义的接口,它抽象了模块的细节,并使以后更容易使用该模块。该接口还可以防止对象的误用。
您在类中构建的简单类并不能充分说明对定义良好的接口的需求。说“但是你们为什么不这样做直到这种情况出现呢?”在现实生活中不起作用。您在软件工程课程中学习的是设计其他程序员能够使用的软件。考虑到 .net 框架和 Java API 提供的库的创建者绝对需要这种纪律。如果他们认为封装太麻烦,那么这些环境几乎不可能使用。
遵循这些准则将在未来产生高质量的代码。为该领域增加价值的代码,因为不仅仅是您自己会从中受益。
最后一点,封装还可以充分测试模块并合理地确保其正常工作。如果没有封装,代码的测试和验证将会变得更加困难。
One of the problems that the software industry faces is the problem of reusable code. Its a big problem. In the hardware world, hardware components are designed once, then the design is reused later when you buy the components and put them together to make new things.
In the software world every time we need a component we design it again and again. Its very wasteful.
Encapsulation was proposed as a technique for ensuring that modules that are created are reusable. That is, there is a clearly defined interface that abstracts the details of the module and make it easier to use that module later. The interface also prevents misuse of the object.
The simple classes that you build in class do not adequately illustrate the need for the well defined interface. Saying "But why don't you people just do THIS UNTIL that situation arises?" will not work in real life. What you are learning in you software engineering course is to engineer software that other programmers will be able to use. Consider that the creators of libraries such as provided by the .net framework and the Java API absolutely require this discipline. If they decided that encapsulation was too much trouble these environments would be almost impossible to work with.
Following these guidelines will result in high quality code in the future. Code that adds value to the field because more than just yourself will benefit from it.
One last point, encapsulation also makes it possible to adequately test a module and be resonably sure that it works. Without encapsulation, testing and verification of your code would be that much more difficult.
Getters/Setters 当然是很好的实践,但是它们写起来很乏味,更糟糕的是读起来很乏味。
有多少次我们读过一个包含六个成员变量和附带的 getter/setter 的类,每个变量都带有完整的 @param/@return HTML 编码,众所周知的无用注释,例如“获取 X 的值”、“设置 x 的值” X', '获取Y的值', '设置Y的值', '获取Z的值', '设置Zzzzzzzzzzzzz的值。扑通!
Getters/Setters are, of course, good practice but they are tedious to write and, even worse, to read.
How many times have we read a class with half a dozen member variables and accompanying getters/setters, each with the full hog @param/@return HTML encoded, famously useless comment like 'get the value of X', 'set the value of X', 'get the value of Y', 'set the value of Y', 'get the value of Z', 'set the value of Zzzzzzzzzzzzz. thump!
这是一个非常常见的问题:“但是你们为什么不这样做直到这种情况出现呢?”。
原因很简单:通常情况下,不稍后修复/重新测试/重新部署它,而是第一次就把它做好会便宜得多。
旧的估计表明维护成本为 80%,其中大部分维护正是您所建议的:仅在有人出现问题后才做正确的事情。第一次就把事情做好可以让我们专注于更有趣的事情并提高工作效率。
草率的编码通常是非常无利可图的——您的客户会因为产品不可靠而不满意,并且在使用它时效率低下。开发人员也不高兴——他们花了 80% 的时间做补丁,这很无聊。最终你可能会同时失去客户和优秀的开发人员。
This is a very common question: "But why don't you people just do THIS UNTIL that situation arises?".
The reason is simple: usually it is much cheaper not to fix/retest/redeploy it later, but to do it right the first time.
Old estimates say that maintenance costs are 80%, and much of that maintenance is exactly what you are suggesting: doing the right thing only after someone had a problem. Doing it right the first time allows us to concentrate on more interesting things and to be more productive.
Sloppy coding is usually very unprofitable - your customers are unhappy because the product is unreliable and they are not productive when the are using it. Developers are not happy either - they spend 80% of time doing patches, which is boring. Eventually you can end up losing both customers and good developers.
我同意你的观点,但在这个体系中生存很重要。在学校时,假装同意。换句话说,被打分对你来说是有害的,并且因为你的原则、观点或价值观而被打分是不值得的。
此外,在团队或雇主工作时,假装同意。后来,开始自己的事业,按照自己的方式去做。当你尝试别人的方法时,要冷静地以开放的态度对待他们——你可能会发现这些经历会重塑你的观点。
理论上,封装在内部实现发生变化的情况下很有用。例如,如果每个对象的 URL 成为计算结果而不是存储值,则 getUrl() 封装将继续工作。但我怀疑你已经听说过这一面。
I agree with you, but it's important to survive the system. While in school, pretend to agree. In other words, being marked down is detrimental to you and it is not worth it to be marked down for your principles, opinions, or values.
Also, while working on a team or at an employer, pretend to agree. Later, start your own business and do it your way. While you try the ways of others, be calmly open-minded toward them -- you may find that these experiences re-shape your views.
Encapsulation is theoretically useful in case the internal implementation ever changes. For example, if the per-object URL became a calculated result rather than a stored value, then the getUrl() encapsulation would continue to work. But I suspect you already have heard this side of it.