Java:如何干净地处理大量字段及其封装?

发布于 2024-07-17 14:01:58 字数 1531 浏览 5 评论 0原文

假设我的任务是编写某种角色扮演游戏。 这意味着,例如,我想要跟踪 Character GameCharacter 及其统计数据,例如智力、伤害加值或生命值。

我非常担心,到项目结束时,我可能最终会处理大量字段 - 对于每个字段,我都必须确保它们遵循一组非常相似的约束和行为(例如,我希望它们限制在最小值和最大值之间;我希望能够区分“基本值”和“临时奖金”;我希望能够在不通过 setter 和 getter 的情况下增加和减少) 。 突然间,对于每个字段,我都需要一个(两个?) getter 和四个 setter,也许还需要几个重置器! 即使对于 10 个字段,这也意味着有很多相似的方法,哎呀。

为了保持干燥,我已经开始将处理这些统计数据的逻辑封装在 Field 类中,以便我可以编写诸如 intelligence.applyBonus(10)hitpoints 之类的代码.get() (它负责返回的值在范围内)等。我什至花了这么大的篇幅来创建类来将这些字段分组在一起,但这不是现在的重点。

现在,我在将 Field“插入”GameCharacter 时遇到了这个问题:大多数 Java 教科书都说每个类都应该有带有公共 getter 和 setter 的私有字段。 这在理论上听起来不错,而且我已经围绕 int; 构建了整个类。 然而,当您发现自己调用 getter 来获取... getter 时,这个想法听起来并不那么可靠:

thisCharacter.getIntelligence().get() //eeek

我宁愿直接访问该字段。 也许这是我的Python/VB [1]“背景”,但对我来说它更干净、更清晰、更直接:

thisCharacter.intelligence.get()

公共字段的(理论上)问题是我放弃了对它的所有控制; 例如,在代码库中的某个其他点,不幸的是,可能会发生以下情况:

thisCharacter.intelligence = somethingThatReallyIsNull;

听起来像是一个微妙的错误...但是...我的意思是,我真的应该担心吗? 我从来没有打算直接分配 Field [2],我已经在 J​​avadoc 中记录了这不是应该做的事情,但我仍然是新来的,所以我有点困惑。

所以我想听听您对这个话题的看法。 封装的优势是否如此巨大,以至于我应该继续使用 getter getter 和 setter getter 等等...或者我应该采取健康的封装措施并将 Field 保留为 public字段?


[1] 是的,我知道。 我一直在努力忘记。 但我们最近也看到了一些 C# 和伙计,属性不是很好吗? 那好吧。

[2] 构造函数除外! 而且 getter 无法将我从错误的构造函数中拯救出来。

Let's say I am tasked with coding some kind of an RPG. This means that, for example, I'll want to track a Character GameCharacter and stats thereof, like intelligence, damage bonuses or hitpoints.

I'm positively scared that by the end of the project I may end up with handling with very a high number of fields - and for each I would have to make sure they follow a very similar set of constraint and behaviours (for example, I want them to be bounded between a min and a max; I want to be able to distinguish between a "base value" and a "temporary bonus"; I want to be able to increment and decrement both without going through a setters and getters). Suddenly, for every field I would need one (two?) getter and four setters and maybe a couple resetters too! Even for 10 fields that means a LOT of methods all alike, eek.

For DRYness I have started encapsulating the logic of messing with those stats in Field classes, so that I could write code such as intelligence.applyBonus(10) or hitpoints.get() (which takes care the value returned is in range), etc. I have even gone to such a length to create classes to group those fields together, but that's not the point right now.

Now, I hit in this problem while "plugging" Field into GameCharacter: most Java textbooks say that each class should have private fields with public getters and setters. That sounds good in theory, and I've already built a whole class around an int; however, the idea doesn't sound as solid when you find yourself calling a getter to get... a getter:

thisCharacter.getIntelligence().get() //eeek

I'd much rather access the field directly. Maybe it's my Python/VB [1] "background", but for me it is cleaner, clearer and more straightforward:

thisCharacter.intelligence.get()

The (theoretical) problem with public fields is that I'm giving up all control on it; for example at some other point in the codebase, by misfortune, the following might happen:

thisCharacter.intelligence = somethingThatReallyIsNull;

Sounds like a subtle bug... but... I mean, should I really worry about that? I for one never plan to assign the Field directly [2], I have documented in the Javadoc that this is not something that should be done, but still I am new here so I'm a bit torn.

So I would like to hear what your take on this topic. Are the advantages of encapsulation so massive that I should go ahead and have getter getters and setter getters and so on... or should I take encapsulation in healthy measures and leave the Field as a public field?


[1] Yes, I know. I've been trying to forget. But we've just recently seen also a bit of C# and man, aren't properties sweet. Oh well.

[2] except in Constructors! And a getter won't save me from a faulty constructor.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

痕至 2024-07-24 14:01:58

听起来您正在考虑 if(player.dexterity > monster.dexterity) Attacker = Player。 您需要更多地思考 if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon())。 不要乱搞纯粹的统计数据,表达你的实际意图,然后围绕他们应该做的事情来设计你的课程。 无论如何,统计数据只是一种逃避。 你真正关心的是玩家是否可以或不能执行某些操作,或者他们的能力/能力与某些参考。 考虑“玩家角色是否足够强大”(player.canOpen(trapDoor)),而不是“角色是否具有至少 50 的力量”。

Sounds like you're thinking in terms of if(player.dexterity > monster.dexterity) attacker = player. You need to think more like if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()). Don't mess around with bare stats, express your actual intention and then design your classes around what they're supposed to do. Stats are a cop-out anyway; what you really care about is whether a player can or cannot do some action, or their ability/capacity versus some reference. Think in terms of "is the player character strong enough" (player.canOpen(trapDoor)) instead of "does the character have at least 50 strength".

不奢求什么 2024-07-24 14:01:58

我的经验是,在您需要很多字段的情况下,字段的数量、性质、命名和类型非常灵活,并且可能在您可能需要的项目的整个生命周期中发生变化某种地图而不是字段。

例如,有一个从键到值的属性映射。

提供获取和设置属性的公共调用,但不要让每个人都使用它们(或确保他们不使用它们)。 相反,创建类来表示您感兴趣的每个属性,并且该类提供用于操作该属性的所有函数。 例如,如果您有力量,则可以有一个“StrengthManipulation”类,该类初始化为特定的玩家对象,然后提供 getter、setter(全部具有适当的验证和例外),也许还可以使用奖金计算力量等这样

做的优点之一是可以将属性的使用与玩家类别分离。 因此,如果您现在添加智力属性,则不必处理和重新编译仅操纵力量的所有内容。

至于直接访问字段,这是一个坏主意。 当您访问 VB 中的字段时(至少在旧的 VB 中),您通常会调用属性 getter 和 setter,而 VB 只是为您隐藏 () 调用。 我的观点是,您必须适应您所使用的语言的约定。 在 C、C++、Java 等语言中,有字段和方法。 调用一个方法应该总是有 () 来明确它是一个调用,并且可能会发生其他事情(例如,您可能会得到一个异常)。 无论哪种方式,Java 的好处之一是其更精确的语法和风格。

VB 到 Java 或 C++ 就像发短信到研究生科学写作。

顺便说一句:一些可用性研究表明,最好不要为构造函数提供参数,而是在需要时构造并调用所有 setter。

My experience is that in situations where you need a lot of fields, the number, nature, naming, and types of fields are so flexible and likely to change throughout the lifetime of your project that you would likely need some sort of map instead of fields.

For example have an attribute map from keys to values.

Provide public calls for getting and setting the attributes, but don't let everybody use them (or make sure they don't). Instead, create classes to represent each attribute you are interested in, and that class provides all the functions for manipulating that attribute. For example, if you have Strength, you could have a "StrengthManipulation" class that is initialized to a specific Player object, and then provides getters, setters (All with appropriate validation and exceptions), and perhaps things like calculating strength with bonuses, etc.

One advantage of this is that you decouple the use of your attributes from your player class. So if you now add an Intelligence attribute, you don't have to deal and recompile everything that manipulates only strength.

As for accessing fields directly, it's a bad idea. When you access a field in VB (at least in old VBs), you usually call a property getter and setter and VB simply hides the () call for you. My view is that you have to adapt to the conventions of the language that you are using. In C, C++, Java and the like you have fields and you have methods. Calling a method should always have the () to make it clear that it is a call and other things may happen (e.g., you could get an exception). Either way, one of the benefits of Java is its more precise syntax and style.

VB to Java or C++ is like Texting to graduate school scientific writing.

BTW: Some usability research shows that it's better to not have parameters to constructors and rather construct and call all the setters if you need them.

梦与时光遇 2024-07-24 14:01:58

Steve Yegge 有一篇非常有趣(如果很长)的博客文章,涵盖了这些问题:通用设计模式

Steve Yegge had a very interesting (if lengthy) blog post that covered these issues: The Universal Design Pattern.

自演自醉 2024-07-24 14:01:58

对我来说,“thisCharacter”很可能有一个“情报”对象来处理幕后的情报,但我质疑这是否应该公开。 您应该只公开 thisCharacter.applyInt 和 thisCharacter.getInt 而不是处理它的对象。 不要那样暴露你的实现。

To me it looks like 'thisCharacter' might well have an 'intelligence' object for dealing with intelligence behind the scenes, but I question whether that should be public at all. You should just expose thisCharacter.applyInt and thisCharacter.getInt instead of the object that deals with it. Dont expose your implementation like that.

日暮斜阳 2024-07-24 14:01:58

保持您的字段私有! 您永远不想暴露太多的 API。 在未来的版本中,您始终可以将私有的内容变为公开的,但反之则不然。

想象一下您会将其融入到下一款 MMORPG 中。 你会有很大的空间出现错误、错误和不必要的邪恶。 确保不可变属性是最终的。

想象一下 DVD 播放器,它具有简约的界面(播放、停止、菜单),但内部却有如此多的技术细节。 您需要隐藏程序中所有不重要的内容。

Keep your fields private! You never want to expose too much of your API. You can always make something that was private public, but not the other way round, in future releases.

Think as if you'll make that into the next MMORPG. You'd have a lot of scope for bugs, errors, and unnecessary evil. Ensure immutable properties are final.

Think of a DVD player, with its miniamilistic interface (play, stop, menu), and yet such technicality inside. You'll want to hide everything non-vital in your program.

别在捏我脸啦 2024-07-24 14:01:58

听起来您的主要抱怨并不是 setter/getter 方法的抽象,而是使用它们的语言语法。 即,您更喜欢 C# 样式属性之类的东西。

如果是这种情况,那么 Java 语言可以为您提供的东西就相对较少。 直接字段访问很好,直到您需要切换到 getter 或 setter,然后您将需要进行一些重构(如果您控制整个代码库,可能没问题)。

当然,如果需要 Java 平台,但不需要该语言,那么还有其他选择。 例如,Scala 有一个非常好的属性语法,以及许多对此类项目有用的其他功能。 最重要的是,它在 JVM 上运行,因此您仍然可以获得与使用 Java 语言编写它相同的可移植性。 :)

It sounds like your main complaint isn't so much with the abstraction of setter/getter methods, but with the language syntax for using them. Ie, you'd prefer something like C# style properties.

If this is the case, then the Java language has relatively little to offer you. Direct field access is fine, until you need to switch to a getter or setter, and then you will have some refactoring to do (possibly ok, if you control the whole codebase).

Of course, if the Java platform is a requirement, but the language isn't then there are other alternatives. Scala has a very nice property syntax, for example, along with lots of other features that could be useful for such a project. And best of all, it runs on the JVM, so you still get the same portability that you'd get by writing it in the Java language. :)

毅然前行 2024-07-24 14:01:58

您在这里看到的是复合模型的单层。

您可能想要添加向模型添加抽象的方法,而不是仅仅将其作为一组较低级别的模型。

这些字段应该是最终字段,因此即使您确实将它们公开,您也不会意外地将 null 分配给它们。

所有 getter 都存在“get”前缀,因此这可能更像是最初的外观,而不是新问题。

What you appear to have here is a single layer of a composite model.

You might want to add methods that add abstractions to the model, rather than just having it as a set of lower level models.

The fields should be final, so even if you did make them public you couldn't accidentally assign null to them.

The "get" prefix is present for all getters, so it's probably more the initial look than a new problem as such.

情定在深秋 2024-07-24 14:01:58

封装的优势是否如此巨大,以至于我应该继续使用 getter getter 和 setter getter 等等……或者我应该采取健康的措施进行封装并将该领域保留为公共领域?

IMO,封装与将 getter/setter 包装在私有字段周围无关。 在小剂量或编写通用库时,这种权衡是可以接受的。 但是,当在像您所描述的那样的系统中不进行检查时,它是一个 反模式

getter/setter 的问题在于它们在具有这些方法的对象与系统的其余部分之间创建了过于紧密的耦合。

真正封装的优点之一是它减少了对 getter 和 setter 的需求,从而在此过程中将对象与系统的其余部分解耦。

与其通过 setIntelligence 公开 GameCharacter 的实现,为什么不为 GameCharacter 提供一个能够更好地反映其在游戏系统中的角色的接口呢?

例如,而不是:

// pseudo-encapsulation anti-pattern
public class GameCharacter
{
  private Intelligence intelligence;

  public Intelligence getIntelligence()
  {
    return intelligence
  }

  public void setIntelligence(Intelligence intelligence)
  {
    this.intelligence = intelligence;
  }
}

为什么不尝试这个?:

// better encapsulation
public class GameCharacter
{
  public void grabObject(GameObject object)
  {
    // TODO update intelligence, etc.
  }

  public int getIntelligence()
  {
    // TODO
  }
}

或者甚至更好:

// still better
public interface GameCharacter
{
  public void grabObject(GameObject object); // might update intelligence
  public int getIntelligence();
}

public class Ogre implements GameCharacter
{
  // TODO: never increases intelligence after grabbing objects
}

换句话说,游戏角色可以抓取游戏对象。 每个 GameCharacter 抓取相同 GameObject 的效果可以(并且应该)有所不同,但细节完全封装在每个 GameCharacter 实现中。

请注意,游戏角色现在如何负责处理其自身的智能更新(范围检查等),例如,这可能在它获取游戏对象时发生。 设置器(以及您注意到的拥有它的并发症)已经消失。 根据具体情况,您也许可以完全放弃 getIntelligence 方法。 Allen Holub 将这一想法得出了逻辑结论,但这种方法似乎并不常见。

Are the advantages of encapsulation so massive that I should go ahead and have getter getters and setter getters and so on... or should I take encapsulation in healthy measures and leave the Field as a public field?

IMO, encapsulation has nothing to do with wrapping a getter/setter around a private field. In small doses, or when writing general-purpose libraries, the tradeoff is acceptable. But when left unchecked in a system like the one you're describing, its a an antipattern.

The problem with getters/setters is that they create overly tight coupling between the object with those methods and the rest of the system.

One of the advantages of real encapsulation is that it reduces the need for getters and setters, decoupling your object from the rest of the system in the process.

Rather than exposing the implementation of GameCharacter with setIntelligence, why not give GameCharacter an interface that better reflects it's role in the game system?

For example, instead of:

// pseudo-encapsulation anti-pattern
public class GameCharacter
{
  private Intelligence intelligence;

  public Intelligence getIntelligence()
  {
    return intelligence
  }

  public void setIntelligence(Intelligence intelligence)
  {
    this.intelligence = intelligence;
  }
}

why not try this?:

// better encapsulation
public class GameCharacter
{
  public void grabObject(GameObject object)
  {
    // TODO update intelligence, etc.
  }

  public int getIntelligence()
  {
    // TODO
  }
}

or even better:

// still better
public interface GameCharacter
{
  public void grabObject(GameObject object); // might update intelligence
  public int getIntelligence();
}

public class Ogre implements GameCharacter
{
  // TODO: never increases intelligence after grabbing objects
}

In other word, a GameCharacter can grab GameObjects. The effect of each GameCharacter grabbing the same GameObject can (and should) vary, but the details are fully encapsulated within each GameCharacter implementation.

Notice how the GameCharacter is now in charge of handling its own intelligence update (range-checking, etc), which can happen as it grabs GameObjects, for example. The setter (and the complications you note with having it) have disappeared. You might be able to dispense with the getIntelligence method altogether, depending on the situation. Allen Holub takes this idea to its logical conclusion, but but that approach doesn't seem to be very common.

纵情客 2024-07-24 14:01:58

除了 Uri 的答案(我完全支持)之外,我还想建议您考虑在 data 中定义属性映射。 这将使您的程序极其灵活,并且会剔除许多您甚至没有意识到不需要的代码。

例如,一个属性可以知道它在屏幕上绑定到哪个字段,它在数据库中绑定到哪个字段,属性更改时要执行的操作列表(重新计算命中%可能适用于强度和敏捷... )

这样做,每个属性都没有代码来将类写入数据库或将其显示在屏幕上。 您只需迭代属性并使用存储在其中的信息即可。

同样的事情也适用于技能——事实上,属性和技能可能源自同一个基类。

沿着这条路走下去后,您可能会发现自己有一个非常严肃的文本文件来定义属性和技能,但是添加新技能将非常简单:

Skill: Weaving
  DBName: BasketWeavingSkill
  DisplayLocation: 102, 20  #using coordinates probably isn't the best idea.
  Requires: Int=8
  Requires: Dex=12
  MaxLevel=50

在某些时候,添加这样的技能不需要更改代码无论如何,这一切都可以很容易地在数据中完成,并且所有数据都存储在附加到类的单个技能对象中。 当然,您可以以相同的方式定义操作:

Action: Weave Basket
  text: You attempt to weave a basket from straw
  materials: Straw
  case Weaving < 1
    test: You don't have the skill!
  case Weaving < 10
    text: You make a lame basket
    subtract 10 straw
    create basket value 8
    improve skill weaving 1%
  case Weaving < 40
    text: You make a decent basket
    subtract 10 straw
    create basket value 30
    improve skill weaving 0.1%
  case Weaving < 50
    text: You make an awesome basket!
    subtract 10 straw
    create basket value 100
    improve skill weaving 0.01%
  case Weaving = 50
    text: OMG, you made the basket of the gods!
    subtract 10 straw
    create basket value 1000

虽然这个示例非常高级,但您应该能够想象如何完全不需要代码来完成它。 想象一下,如果您的“属性/技能”实际上是成员变量,那么在没有代码的情况下做类似的事情会有多困难。

In addition to Uri's answer (which I totally support), I'd like to suggest you consider defining the attribute map in data. This will make your program EXTREMELY flexible and will factor out a lot of code you don't even realize you don't need.

For instance, an attribute can know what field it binds to on the screen, what field it binds to in the database, a list of actions to execute when the attribute changes (a recalculate hit% might apply to both strength and dex...)

Doing it this way, you have NO code per attribute to write a class out to the DB or display it on the screen. You simply iterate over the attributes and use the information stored inside.

The same thing can apply to skills--in fact, attributes and skills would probably derive from the same base class.

After you go down this road, you'll probably find yourself with a pretty serious text file to define attributes and skills, but adding a new skill would be as easy as:

Skill: Weaving
  DBName: BasketWeavingSkill
  DisplayLocation: 102, 20  #using coordinates probably isn't the best idea.
  Requires: Int=8
  Requires: Dex=12
  MaxLevel=50

At some point, adding a skill like this would require no code change whatsoever, it could all be done in data pretty easily, and ALL the data is stored in a single skill object attached to a class. You could, of course, define actions the same way:

Action: Weave Basket
  text: You attempt to weave a basket from straw
  materials: Straw
  case Weaving < 1
    test: You don't have the skill!
  case Weaving < 10
    text: You make a lame basket
    subtract 10 straw
    create basket value 8
    improve skill weaving 1%
  case Weaving < 40
    text: You make a decent basket
    subtract 10 straw
    create basket value 30
    improve skill weaving 0.1%
  case Weaving < 50
    text: You make an awesome basket!
    subtract 10 straw
    create basket value 100
    improve skill weaving 0.01%
  case Weaving = 50
    text: OMG, you made the basket of the gods!
    subtract 10 straw
    create basket value 1000

Although this example is pretty advanced, you should be able to visualize how it would be done with absolutely no code. Imagine how difficult it would be to do anything like that without code if your "attributes/skills" were actually member variables.

冷月断魂刀 2024-07-24 14:01:58

考虑使用“建模”框架,例如 Eclipse 的 EMF。
这涉及使用 Eclipse EMF 编辑器之类的工具来定义对象,
或者在纯 XML 中,或者在 Rational Rose 中。 这并不像听起来那么困难。
您定义属性、约束等。然后框架生成
给你的代码。 它将 @ generated 标签添加到它添加的部分,例如
getter 和 setter 方法。 您可以自定义代码,然后进行编辑
手动或通过一些 GUI,并重新生成 java 文件。

Consider using a "modeling" framework, such as Eclipse's EMF.
This involves defining your objects using something like Eclipse EMF editor,
or in plain XML, or in Rational Rose. It's not as difficult as it sounds.
You define attributes, constraints etc. Then the framework generates
the code for you. It adds @generated tags to parts it added such as
getter and setter methods. You can customize the code, and later edit
it either by hand or via some GUI, and regenerate the java files.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文