代码之家  ›  专栏  ›  技术社区  ›  badp

JAVA:如何正确处理许多字段及其封装?

  •  9
  • badp  · 技术社区  · 16 年前

    比如说,我的任务是编写某种RPG。这意味着,例如,我要跟踪 Character GameCharacter 以及它的属性,比如智力,伤害加成或者击点。

    我非常害怕在项目结束时,我可能会处理大量的字段——对于每一个字段,我都必须确保它们遵循一组非常相似的约束和行为(例如,我希望它们在最小值和最大值之间有界;我希望能够区分“基值”和“速度”Ary bonus“我希望能够在不经过setter和getter的情况下增加和减少。”突然间,对于每一个领域,我都需要一个(两个?)盖特和四个二传手,也许还有几个零散的人!即使对于10个字段,这意味着许多方法都是相同的,eek。

    对于dryness,我已经开始封装处理这些统计信息的逻辑 Field 类,以便我可以编写代码,如 intelligence.applyBonus(10) hitpoints.get() (注意返回的值在范围内),等等,我甚至花了这么长的时间来创建类来将这些字段组合在一起,但现在还不是重点。

    现在,我在“插拔”时碰到了这个问题。 进入之内 游戏特性 大多数Java教科书都说每个类都应该有一个带有公共吸引子和设置器的私有字段。这在理论上听起来不错,而且我已经在 int 然而,当你发现自己在召唤一个能干的人来获得…吸气剂:

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

    我宁愿直接进入这个领域。也许这是我的python/vb[1]“背景”,但对我来说,它更清晰、更直接:

    thisCharacter.intelligence.get()
    

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

    thisCharacter.intelligence = somethingThatReallyIsNull;
    

    听起来像个小虫子…但是…我是说,我真的应该担心吗?我从来没有计划分配 直接地,我已经在javadoc中记录了这不是应该做的事情,但是我仍然是新来的,所以我有点痛苦。

    所以我想听听你对这个话题的看法。封装的优点是如此之大,以至于我应该继续进行,并有getter getter和setter getter等等…或者我应该采取健康的措施封装并离开 作为一个 public 字段?


    是的,我知道。我一直想忘记。但我们最近也看到了一些C和人,他们的财产不是甜蜜的。哦,好吧。

    [2]施工人员除外!一个getter不会把我从一个错误的构造函数中拯救出来。

    10 回复  |  直到 16 年前
        1
  •  8
  •   Uri    16 年前

    我的经验是在你需要 许多 在字段中,字段的数量、性质、命名和类型非常灵活,在项目的整个生命周期中都可能发生变化,因此您可能需要某种类型的映射而不是字段。

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

    提供获取和设置属性的公共调用,但不要让每个人都使用它们(或确保它们不使用)。相反,创建类来表示您感兴趣的每个属性,该类提供了操作该属性的所有函数。例如,如果你有力量,你可以有一个“强化hManipulation”类,它被初始化为一个特定的玩家对象,然后提供getter、setter(所有都有适当的验证和异常),也许还有一些东西,比如用奖金计算力量等等。

    这样做的一个好处是,可以将属性的使用与播放器类分离开来。所以,如果你现在添加了一个智能属性,你不需要处理和重新编译所有只处理强度的东西。

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

    VB到JAVA或C++就像是发短信给研究生院的科学写作。

    顺便说一句:一些可用性研究表明,最好不要给构造函数设置参数,如果需要的话,最好构造并调用所有的setter。

        2
  •  18
  •   erickson    16 年前

    听起来你在考虑 if(player.dexterity > monster.dexterity) attacker = player . 你得多想想 if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()) . 不要乱搞光秃秃的统计数据,表达你的实际意图,然后围绕他们应该做的事情设计你的课程。不管怎样,统计数据都是一种逃避;你真正关心的是一个玩家是否可以或不能做一些动作,或者他们的能力/能力与一些参考。从“玩家角色是否足够强大”的角度思考( player.canOpen(trapDoor) )而不是“角色至少有50种力量”。

        3
  •  4
  •   John Topley    16 年前

    Steve Yegge有一篇非常有趣(如果很长)的博客文章,涵盖了这些问题: The Universal Design Pattern .

        4
  •  2
  •   willcodejavaforfood    16 年前

    在我看来,“这个角色”很可能有一个处理幕后情报的“情报”对象,但我怀疑这是否应该公开。您应该只公开thischaracter.applyint和thischaracter.getint,而不是处理它的对象。不要这样暴露您的实现。

        5
  •  2
  •   Humphrey Bogart    16 年前

    保持你的领域隐私!您永远不想公开太多的API。在将来的发行版中,您可以始终将私有的内容公开,但不能反过来公开。

    想一想,就好像你要进入下一个MMORPG。你会有很大的空间去发现错误,错误和不必要的邪恶。确保不可变属性为最终属性。

    想想一个DVD播放器,它的迷你界面(播放,停止,菜单),但里面的技术性。你会想隐藏程序中所有不重要的东西。

        6
  •  1
  •   jsight TaherT    16 年前

    听起来你的主要抱怨不是太多关于setter/getter方法的抽象,而是关于使用它们的语言语法。例如,您更喜欢C样式的属性。

    如果是这种情况,那么Java语言就没有多少可以提供给你了。直接字段访问是可以的,直到您需要切换到getter或setter,然后您将有一些重构(如果控制整个代码库,可能是可以的)。

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

        7
  •  1
  •   Tom Hawtin - tackline    16 年前

    这里显示的是复合模型的单层。

    您可能希望添加方法,将抽象添加到模型中,而不仅仅将其作为一组较低级别的模型。

    这些字段应该是最终字段,因此即使您公开了它们,也不能意外地分配 null 对他们来说。

    “get”前缀适用于所有getter,因此它可能更多是初始外观,而不是新问题。

        8
  •  1
  •   Rich Apodaca    16 年前

    封装的优点是如此之大,以至于我应该继续进行,并有getter getter和setter getter等等…或者我应该采取健康的措施来封装并将该字段作为公共字段?

    在我看来,封装与围绕私有字段包装getter/setter无关。在小剂量的情况下,或者在编写通用库时,这种权衡是可以接受的。但如果在你描述的系统中不加检查,它将是一个 antipattern .

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

    真正封装的一个优点是它减少了对getter和setter的需求,从而将对象与流程中的其他系统分离开来。

    为什么不给游戏角色一个更好地反映其在游戏系统中角色的界面,而不是用setintelligence公开游戏角色的实现?

    例如,而不是:

    // 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
    }
    

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

    注意游戏角色现在如何处理自己的智能更新(范围检查等),例如,当它抓取游戏对象时,可能会发生这种情况。设定者(以及你注意到的复杂情况)已经消失了。根据具体情况,您可能可以完全免除getIntelligence方法。Allen Holub将这个想法 logical conclusion 但这种方法似乎并不常见。

        9
  •  0
  •   Bill K    16 年前

    除了URI的答案(我完全支持这个答案),我建议您考虑在数据中定义属性映射。这将使您的程序非常灵活,并且会产生许多您甚至没有意识到不需要的代码。

    例如,属性可以知道它在屏幕上绑定到什么字段,它在数据库中绑定到什么字段,当属性更改时要执行的操作列表(重新计算命中百分比可能同时应用于强度和dex…)

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

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

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

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

    在某种程度上,添加这样的技能将不需要任何代码更改,它可以很容易地在数据中完成,并且所有数据都存储在附加到类的单个skill对象中。当然,您可以用相同的方式定义操作:

    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
    

    尽管这个例子相当高级,但是您应该能够可视化地看到在完全没有代码的情况下如何完成它。想象一下,如果您的“属性/技能”实际上是成员变量,那么在没有代码的情况下做类似的事情是多么困难。

        10
  •  0
  •       16 年前

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