您如何避免吸气剂和吸气剂?


85

我在以oo方式设计类时遇到了一些困难。我已经读过对象暴露了他们的行为,而不是数据。因此,给定类的方法应该是“动词”或对对象执行的操作,而不是使用getter / setter修改数据。例如,在“帐户”对象,我们将有方法Withdraw()Deposit(),而不是setAmount()等请参见:为什么getter和setter方法是邪恶的

因此,例如,给定一个Customer类,该类保留了大量有关客户的信息,例如Name,DOB,Tel,Address等,那么如何避免使用getter / setter来获取和设置所有这些属性?一个人可以写哪种“行为”类型的方法来填充所有数据?


3
是否可能重复使用“纯旧数据”类?
蚊蚋


8
我要指出的是,如果您必须处理Java Beans规范则将有getter和setter。许多事情都使用Java Beans(jsps中的表达语言),而要避免这种情况将很……具有挑战性。

4
...并且,与MichaelT相反:如果您不使用JavaBeans规范(我个人认为应该避免使用它,除非您在必要的上下文中使用对象),那么就不需要“获取”对获取器的影响,特别是对于没有任何相应设置器的属性。我想调用的方法name()Customer是明确的,或者更清晰,比调用的方法getName()
丹尼尔·普里登

2
@Daniel Pryden:name()可能意味着要设置还是要获取?...
IntelliData,2015年

Answers:


55

如很多答案和评论中所述,DTO 在某些情况下适当且有用的,尤其是在跨边界传输数据时(例如,序列化为JSON以通过Web服务发送)。对于此答案的其余部分,我或多或少会忽略它,而谈论领域类,以及如何设计它们以最小化(如果不消除)getter和setter,并在大型项目中仍然有用。我也不会谈论为什么删除getter或setter或何时删除,因为这是他们自己的问题。

例如,假设您的项目是象棋或战舰这样的棋盘游戏。您可能有多种在表示层(控制台应用程序,Web服务,GUI等)中表示此内容的方法,但您也有一个核心域。您可能有一个班级Coordinate,代表董事会中的职位。编写它的“邪恶”方法是:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(为了简洁起见,我将用C#而不是Java编写代码示例,因为我对此更加熟悉。希望这不是问题。概念相同,翻译应该简单。)

消除二传手:不变性

尽管公共获取者和设置者都可能存在问题,但设置者却是两者中更为“邪恶”的。它们通常也更容易消除。这个过程很简单,只需在构造函数中设置值即可。任何先前使对象发生突变的方法都应返回新结果。所以:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

请注意,这不能防止类中的其他方法使X和Y发生变化。要更严格地保持不变,可以使用readonlyfinal在Java中)。但是无论采用哪种方式(无论是使财产真正不可变,还是只是通过设置者防止直接的公共突变),都具有消除公共设置者的窍门。在绝大多数情况下,这很好。

删除吸气剂,第1部分:设计行为

上面的内容对于安装人员来说是一件好事,但是对于吸气者来说,我们实际上甚至在开始之前就已经将自己打倒了。我们的过程是考虑坐标是什么- 它代表的数据 - 并围绕它创建一个类。相反,我们应该从坐标需要的行为开始。顺便说一下,这个过程在TDD的帮助下进行了,在TDD中,我们仅在需要它们时才提取此类,因此我们从所需的行为开始并从那里开始工作。

因此,假设您发现自己第一个需要Coordinate进行碰撞检测的位置:您想检查两个部件是否占据了板上的相同空间。这是“邪恶”的方式(为简洁起见,省略了构造函数):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

这是个好方法:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

IEquatable为简化起见,缩写为实现)。通过设计行为而不是对数据建模,我们设法删除了吸气剂。

请注意,这也与您的示例有关。您可能正在使用ORM,或在网站等上显示客户信息,在这种情况下,某种CustomerDTO可能很有意义。但是,仅仅因为您的系统包括客户并且他们在数据模型中表示出来,并不意味着您应该Customer在您的域中拥有一个类。也许当您针对行为进行设计时,就会浮出水面,但是如果您要避免使用吸气剂,请不要先发制人。

删除吸气剂,第2部分:外部行为

所以上面的是一个良好的开端,但迟早你可能会碰到,你有哪些与一类,这在某种程度上取决于该类的状态相关联的行为的情况,但不属于类。这种行为通常存在于应用程序的服务层中。

以我们的Coordinate示例为例,最终您将需要向用户展示您的游戏,这可能意味着要在屏幕上绘画。例如,您可能有一个UI项目,该项目Vector2用于表示屏幕上的一个点。但是,让Coordinate班级负责将坐标从屏幕上的点转换为屏幕上的点是不合适的,因为这会将各种表示形式的关注点带入您的核心领域。不幸的是,这种情况是OO设计中固有的。

第一种选择是非常普遍的选择,它只是暴露该死的吸气剂,并对此大声疾呼。这具有简单的优点。但是,由于我们正在谈论避免使用吸气剂,因此,为了争辩而说,我们拒绝这一方法,然后看看还有哪些其他选择。

第二种选择.ToDTO()在类上添加某种方法。无论如何,都可能需要(或类似的)方法,例如,当您要保存游戏时,需要捕获几乎所有状态。但是,为您的服务执行此操作与直接访问getter之间的区别或多或少是出于美观目的。它仍然具有同样的“邪恶”。

第三种选择 -使用Zoran Horvat在几个Pluralsight视频中提出的建议-使用访问者模式的修改版本。这是模式的非常不寻常的用法和变化,我认为人们的里程会因是否增加复杂性而不是真正获得收益或是否会很好地适应情况而发生巨大变化。这个想法本质上是使用标准的访问者模式,但是让Visit方法将所需的状态作为参数,而不是所访问的类。示例可以在这里找到。

对于我们的问题,使用此模式的解决方案是:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

如您所知,_x并且_y不再真正封装了。我们可以通过创建一个IPositionTransformer<Tuple<int,int>>直接将它们返回的提取它们。根据口味,您可能会觉得这使整个运动毫无意义。

但是,对于公共获取者而言,以错误的方式做事非常容易,只需直接拉出数据并违反Tell,Do n't Ask即可使用。而使用此模式实际上更容易以正确的方式进行操作:当您要创建行为时,将自动从创建与其关联的类型开始。违反TDA的行为将非常明显,并且可能需要采用更简单,更好的解决方案。在实践中,这些要点使正确的方法(OO)比吸气剂鼓励的“邪恶”方法容易得多。

最后,即使最初并不很明显,实际上也可能存在一些方法来公开足够多的行为,从而避免暴露状态。例如,使用我们的前一个版本的Coordinate唯一公共成员Equals()(实际上,它需要完整的IEquatable实现),您可以在表示层中编写以下类:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

事实证明,也许令人惊讶的是,所有我们的行为确实从一个坐标需要实现我们的目标是平等的检查!当然,此解决方案适合于此问题,并假设可接受的内存使用/性能。这只是适合此特定问题领域的一个示例,而不是一般解决方案的蓝图。

再次,关于在实践中这是否不必要的复杂性,意见会有所不同。在某些情况下,可能不存在这样的解决方案,或者它可能过于怪异或复杂,在这种情况下,您可以恢复到上述三种。


漂亮回答!我想接受,但首先要发表一些意见:1。我确实认为toDTO()很棒,因为您不访问get / set,这使您可以更改分配给DTO的字段而不会破坏现有代码。2.说客户有足够的行为来证明使其成为一个实体,怎么会ü访问道具对它们进行修改,例如地址/电话变更等
IntelliData

@IntelliData 1.当您说“更改字段”时,您是说要更改类定义或更改数据?可以通过删除公共设置器但保留获取器来避免后者,因此dto方面无关紧要。前者并不是公众获得者“邪恶”的真正原因。例如,请参阅developers.stackexchange.com/questions/157526/…
本·亚伦森2015年

@IntelliData 2.如果不了解其行为,则很难回答。但是可能的答案是:您不会。可能一个什么样的行为Customer类有一个需要能够变异它的电话号码吗?也许客户的电话号码发生了变化,而我需要将该更改保留在数据库中,但这些都不是提供行为的域对象的责任。这是一个数据访问问题,很可能将由DTO(例如存储库)处理。
Ben Aaronson

@IntelliData保持Customer域对象的数据相对较新(与db同步)是管理其生命周期的问题,这也不是它自己的责任,并且可能最终会驻留在存储库,工厂或IOC容器中,或者无论实例化Customers。
Ben Aaronson

2
真的很喜欢“ 行为设计”的概念。这为底层的“数据结构”提供了信息,并有助于避免过于常见的贫乏,难以使用的类。加一。
radarbob

71

避免setter的最简单方法是在对象上载时将值交给构造方法new 当您想使对象不可变时,这也是通常的模式 也就是说,现实世界中的情况并不总是那么清楚。

的确,方法应该与行为有关。但是,某些对象(例如客户)主要是用来保存信息的。 那些对象从getter和setter中受益最大;如果根本不需要这种方法,我们将完全消除它们。

进一步阅读
何时需要使用Getter和Setters


3
那么,为什么所有关于“盖特/塞特斯”的宣传都是邪恶的?
IntelliData 2015年

46
只是应该更了解的软件开发人员通常的回应。对于您链接的文章,作者使用短语“ getter和setters是邪恶的”来引起您的注意,但这并不意味着该声明绝对正确。
罗伯特·哈维

12
@IntelliData有人会告诉您Java本身是邪恶的。

18
@MetaFightsetEvil(null);

4
当然,评估是邪恶的化身。或近亲。
主教

58

拥有一个公开数据而不是行为的对象是非常好的。我们只是称其为“数据对象”。该模式以数据传输对象或值对象之类的名称存在。如果对象的目的是保存数据,则getter和setter有效访问数据。

那么为什么有人会说“ getter和setter方法是邪恶的”呢?您会看到很多东西-某人采用了在特定上下文中完全有效的准则,然后删除该上下文以获取更准确的标题。例如,“ 偏重于继承而不是继承 ”是一个很好的原则,但是很快就会有人删除上下文并写下“ 为什么扩展是邪恶的 ”(嘿,同一作者,真是巧合!)或“ 继承是邪恶的,必须是销毁 ”。

如果您查看文章的内容,实际上它有一些有效的要点,那么它只是延伸了一点,以使其成为点击链接标题。例如,该文章指出,不应公开实现细节。这是封装和数据隐藏的原理,这是OO中的基础。但是,根据定义,getter方法不会公开实现细节。对于客户数据对象,名称地址等属性不是实现细节,而是对象的全部用途,应该是公共接口的一部分。

阅读您链接到的文章的续篇,以了解他如何建议在不使用恶意设置程序的情况下,在“雇员”对象上实际设置“名称”和“工资”之类的属性。原来他使用的是带有“导出器”的模式,该模式中填充了名为add Name的方法,添加了 Salary,这反过来又设置了相同名称的字段...因此最后,他最终只使用了setter模式,只是不同的命名约定。

这就像认为您通过在保持相同实现的情况下将它们重命名为仅一个东西来避免单例的陷阱一样。


哎呀,所谓的专家似乎说事实并非如此。
IntelliData

8
话又说回来,有人说“从不使用OOP”:有害.cat
v.org / software

7
FTR,我认为更多的“专家”仍然教导无意义地为所有事物创建吸气剂/设定器,而不是根本没有创造任何东西。海事组织,后者是较少误导的建议。
大约

4
@leftaroundabout:好的,但是我建议在“始终”和“从不”之间使用“适当时使用”的中间立场。
JacquesB 2015年

3
我认为问题在于许多程序员将每个对象(或太多对象)都转换为DTO。有时它们是必需的,但由于它们将数据与行为分开,因此应尽可能避免使用它们。(假设您是虔诚的OOPer)
user949300

11

要从Customer数据对象转换-class,我们可以问自己以下有关数据字段的问题:

我们要如何使用{data field}?{data field}在哪里使用?是否可以并且应该将{data field}的使用移至该类?

例如:

  • 目的是Customer.Name什么?

    可能的答案,在登录网页中显示名称,在邮寄给客户的邮件中使用该名称。

    导致方法:

    • Customer.FillInTemplate(…)
    • Customer.IsApplicableForMailing(…)
  • 目的是Customer.DOB什么?

    验证客户的年龄。在客户生日那天有折扣。邮件。

    • Customer.IsApplicableForProduct()
    • Customer.GetPersonalDiscount()
    • Customer.IsApplicableForMailing()

根据注释,示例对象Customer(既是数据对象又是具有自己职责的“真实”对象),范围太广;即它具有太多的属性/职责。这导致依赖于许多组件Customer(通过读取其属性)或Customer依赖于许多组件。也许存在不同的客户观点,也许每个人都有自己独特的类别1

  • Account在进行货币交易时,客户可能仅用于:

    • 帮助人们识别他们的汇款交给了正确的人;和
    • Account

    此客户不需要像场DOBFavouriteColourTel,也许甚至没有Address

  • 客户在用户登录到银行网站的上下文中。

    相关字段为:

    • FavouriteColour,可能以个性化主题的形式出现;
    • LanguagePreferences
    • GreetingName

    可以使用单个方法来捕获这些属性,而不是使用具有getter和setter的属性:

    • PersonaliseWebPage(模板页面);
  • 在市场营销和个性化邮件环境中的客户。

    这里不依赖于数据对象的属性,而是从对象的职责开始;例如:

    • IsCustomerInterestedInAction(); 和
    • GetPersonalDiscounts()。

    该客户对象具有一个FavouriteColour属性和/或一个Address属性的事实变得无关紧要:也许实现使用了这些属性;但是它可能还会使用一些机器学习技术,并使用以前与客户的互动来发现客户可能会对哪些产品感兴趣。


1.当然,CustomerAccount类是示例,对于一个简单的示例或家庭作业练习,拆分此客户可能会过大,但是通过拆分示例,我希望演示将数据对象转换为对象的方法。责任会起作用。


4
赞成,因为您实际上是在回答问题:)但是,很明显,所提出的解决方案比仅仅具有gettes / setter还要糟糕-例如FillInTemplate显然打破了关注点分离原则。这只是表明问题的前提是有缺陷的。
JacquesB 2015年

@Kasper van den Berg:通常情况下,当您在客户中拥有许多属性时,您将如何初始设置它们?
IntelliData 2015年

2
@IntelliData,您的值很可能来自数据库,XML等。客户或CustomerBuilder读取它们,然后使用(即在Java中)私有/包/内部类访问或(ick)反射进行设置。这并不完美,但是您通常可以避免公开场合。(有关更多详细信息,请参见我的答案)
user949300

6
我认为客户阶层不应该了解这些事情。
CodesInChaos 2015年

怎么样Customer.FavoriteColor呢?
2015年

8

TL; DR

  • 为行为建模是好的。

  • 为good(!)抽象建模更好。

  • 有时需要数据对象。


行为与抽象

有几个避免使用getter和setter方法的原因。如您所述,一种方法是避免对数据建模。这实际上是次要原因。更大的原因是提供抽象。

在您的例子中,有一个清楚的银行帐户:一种setBalance()方法真的很糟糕,因为设置余额不是帐户应使用的用途。帐户的行为应尽可能从其当前余额中抽象出来。在决定是否无法取款时,可以考虑余额,可以访问当前余额,但是修改与银行帐户的交互不应要求用户计算新余额。这就是帐户应自行执行的操作。

甚至一对deposit()withdraw()方法都不是模拟银行帐户的理想方法。更好的方法是只提供一个transfer()以另一个帐户和金额作为参数的方法。这将使帐户类可以轻松地确保您不会在系统中意外地创建/销毁资金,它将提供非常有用的抽象,并且实际上将为用户提供更多的见解,因为它将强制使用特殊帐户来赚/投资/亏钱(请参阅双重记帐)。当然,并非每次使用帐户都需要这种抽象级别,但是绝对值得考虑您的类可以提供多少抽象。

请注意,提供抽象和隐藏数据内部并不总是一回事。几乎所有应用程序都包含实际上只是数据的类。元组,字典和数组是常见的示例。您不想向用户隐藏点的x坐标。您可以/应该使用一点来进行抽象。


客户阶层

客户当然是您系统中的实体,应该尝试提供有用的抽象。例如,它可能应该与购物车相关联,并且购物车和客户的组合应允许进行购买,这可能会启动诸如向其发送所请求的产品,向他收费(考虑到他选择的付款)之类的操作。方法),等等。

要注意的是,您提到的所有数据不仅与客户相关联,而且所有这些数据都是可变的。客户可能会移动。他们可能会更改其信用卡公司。他们可能会更改其电子邮件地址和电话号码。哎呀,他们甚至可能改变自己的名字和/或性别!因此,功能齐全的客户类别确实必须提供对所有这些数据项的完全修改访问。

尽管如此,设置者仍可以/应该提供非平凡的服务:他们可以确保电子邮件地址的格式正确,邮政地址的验证等。同样,“获取者”可以提供高级服务,例如以该Name <user@server.com>格式提供电子邮件地址使用名称字段和存放的电子邮件地址,或提供格式正确的邮政地址等。当然,这种高级功能的意义在很大程度上取决于您的用例。这可能是完全的矫kill过正,或者可能需要另一个班级做对。选择抽象级别并非易事。


听起来不错,尽管我在性爱方面不同意...;)
IntelliData

6

尝试扩大卡巴斯基的答案,最容易抱怨和消除二传手。用一种相当模糊的,挥舞着的(希望是幽默的)论点:

Customer.Name何时会更改?

很少。也许他们结婚了。或进入证人保护。但是在这种情况下,您还需要检查并可能更改其居住地,近亲以及其他信息。

DOB何时会更改?

仅在初始创建时或在数据输入错误时。或者,如果他们是Domincan棒球选手。:-)

常规的普通设置器不能访问这些字段。也许您有一个Customer.initialEntry()方法,或者Customer.screwedUpHaveToChange()需要特殊权限的方法。但是没有公共Customer.setDOB()方法。

通常,从数据库,REST API,一些XML等中读取客户。有一个方法Customer.readFromDB(),或者,如果您对SRP /关注点分离比较严格,则可以有一个单独的构建器,例如CustomerPersister带有read()方法的对象。在内部,它们以某种方式设置字段(我更喜欢使用包访问或内部类YMMV)。但是再次,避免公众的二传手。

(由于问题的附录已有所改变...)

假设您的应用程序大量使用关系数据库。具有Customer.saveToMYSQL()Customer.readFromMYSQL()方法将是愚蠢的。这会导致与具体的,非标准的,可能会更改实体的不希望有的耦合。例如,当您更改架构,或更改为Postgress或Oracle。

然而,国际海事组织,这是完全可以接受的情侣客户到一个抽象的标准ResultSet。一个单独的帮助器对象(我将其称为CustomerDBHelper,可能是的子类AbstractMySQLHelper)了解与数据库的所有复杂连接,了解棘手的优化细节,了解表,查询,联接等...(或使用像Hibernate这样的ORM)来生成ResultSet。您的对象使用ResultSet,这是一个抽象标准,不太可能更改。当您更改基础数据库或更改架构时,Customer不会更改,而CustomerDBHelper更改。如果幸运的话,只有AbstractMySQLHelper会更改,它会自动为Customer,Merchant,Shipping等进行更改。

这样,您可以(也许)避免或减少对获取器和设置器的需求。

并且,Holob文章的要点是将以上内容与使用getter和setter进行所有操作并更改数据库的情况进行比较和对比。

同样,假设您使用大量XML。IMO,可以将您的客户与抽象标准耦合,例如Python xml.etree.ElementTree或Java org.w3c.dom.Element。客户从中获取并设置自己。同样,您可以(也许)减少对获取器和设置器的需求。


您是否建议使用构建器模式?
IntelliData

Builder有助于使对象的构造更轻松,更健壮,并且,如果您愿意,可以使对象不可变。但是,它仍然(部分地)暴露出以下事实:基础对象具有DOB字段,因此,它并不是所有的一切。
user949300

1

具有getter和setter的问题可能是由于一个类可以以一种方式在业务逻辑中使用,但是您可能还具有帮助器类来对数据库或文件或其他持久性存储中的数据进行序列化/反序列化。

由于存在多种存储/检索数据的方式,并且您希望将数据对象与它们的存储方式脱钩,因此可以通过公开这些成员或通过getter和getter访问这些成员来“破坏”封装。设置者,几乎和公开它们一样糟糕。

有多种解决方法。一种方法是将数据提供给“朋友”。尽管友谊不是继承的,但是可以通过任何向好友请求信息的序列化程序(即基本序列化程序“转发”信息)来克服。

您的类可以具有通用的“ fromMetadata”或“ toMetadata”方法。From-metadata构造一个对象,因此很可能是构造函数。如果它是动态类型的语言,则元数据是该语言的相当标准,并且可能是构造此类对象的主要方法。

如果您的语言是专门用于C ++的,则解决此问题的一种方法是拥有一个公共的“结构”数据,然后让您的类将这个“结构”的实例作为成员,实际上是您将要存储的所有数据/检索以存储在其中。然后,您可以轻松编写“包装器”以多种格式读取/写入数据。

如果您的语言是不带“结构”的C#或Java,则可以执行类似的操作,但是结构现在是第二类。数据或常量的“所有权”没有真正的概念,因此,如果给出包含数据的类的实例且该实例全部公开,则持有的任何内容都可以对其进行修改。您可以“克隆”它,尽管这样做可能会很昂贵。或者,您可以使此类具有私有数据,但使用访问器。这为类的用户提供了一种获取数据的回旋方式,但这不是与类的直接接口,实际上是存储类数据的一个细节,这也是一个用例。


0

OOP关于封装和隐藏对象内部的行为。对象是黑匣子。这是一种设计事物的方法。在很多情况下,该资产不需要知道另一组件的内部状态,而最好不必知道它。您可以主要通过接口或在具有可见性的对象内部实施该想法,并仅注意允许的动词/操作可供调用者使用。

这对于某些问题很有效。例如,在用户界面中对单个UI组件进行建模。当您与文本框打交道时,您只会在设置文本,获取文本或收听文本更改事件时被打扰。通常,您不会对光标的位置,用于绘制文本的字体或键盘的使用方式感兴趣。封装在这里提供了很多。

相反,当您调用网络服务时,您将提供明确的输入。通常存在语法(例如JSON或XML),并且调用服务的所有选项都没有理由被隐藏。这个想法是,您可以按照自己的方式调用服务,并且数据格式是公开的和已发布的。

在这种情况下,或者您在其他许多情况下(例如访问数据库),您实际上都在处理共享数据。因此,没有理由将其隐藏,相反,您想使其可用。可能存在读/写访问或数据检查一致性的问题,但在此核心是公开的核心概念。

对于这样的设计要求,您要避免封装并使事物公开而明了,则要避免对象。您真正需要的是元组,C结构或其等效对象,而不是对象。

但是它也发生在Java之类的语言中,您唯一可以建模的就是对象或对象数组。他们自己的对象可以容纳一些本机类型(int,float ...),仅此而已。但是对象也可以像具有公共字段之类的简单结构一样工作。

因此,如果您对数据进行建模,则只需使用对象内部的公共字段即可,因为您不需要更多。您不使用封装,因为您不需要它。这是用多种语言完成的。在Java中,从历史上看,一个标准上升了,在其中使用getter / setter至少可以具有读/写控制(例如,不添加setter),并且使用自省API的工具和框架将查找getter / setter方法并将其用于自动填充内容或在自动生成的用户界面中将这些内容显示为可修改的字段。

您还可以在setter方法中添加一些逻辑/检查参数。

实际上,几乎没有理由使用getter / setter,因为它们通常用于对纯数据建模。使用对象的框架和开发人员确实希望getter / setter所做的仅是设置/获取字段。实际上,使用getter / setter所做的工作不比使用公共领域做更多。

但这是旧习惯,旧习惯很难消除...如果您没有盲目地将吸气剂/塞料器放到任何地方,如果他们缺乏更好地了解它们的本质和背景的话,您甚至可能受到同事或老师的威胁。不。

您可能需要更改语言才能使用所有这些吸气剂/设置器样板代码。(如C#或Lisp)。对我来说,获取者/设定者只是另一个十亿美元的错误。


6
C#属性与getter和setters相比,实际上并没有实现封装……
IntelliData,2015年

1
[gs] etters的优点是您可以执行通常不做的事情(检查值,通知观察者),模拟不存在的字段等,并且主要是可以在以后进行更改。使用龙目岛,样板消失了:@Getter @Setter class MutablePoint3D {private int x, y, z;}
maaartinus

1
@maaartinus当然[gs] etter可以做任何其他方法都可以做的事情。这意味着任何调用者代码都应该意识到他们设置的任何值都可能引发异常,被更改或可能发送更改通知...或其他任何内容。这个或多或少的指示者不是在提供对字段的访问,而是在提供任意代码。
Nicolas Bousquet 2015年

@IntelliData C#属性允许不编写1个无用的样板字符,也不必关心所有这些吸气剂/装填剂的内容……这已经比项目lombok更好。对我来说,仅具有getters / setter的POJO并不是在提供封装,而是发布一种数据格式,可以与服务交换自由地进行读取或写入。封装与设计要求相反。
Nicolas Bousquet 2015年

我真的不认为房地产真的很好。当然,您可以保存前缀和括号,即每个调用5个字符,但是1.它们看起来像字段,这很令人困惑。2.他们是您需要额外支持的另一件事。没什么大不了的,但是也没有什么大的优势(与Java + Lombok相比;纯Java显然处于亏损状态)。
maaartinus

0

因此,例如,给定一个Customer类,如果有关该客户的信息(例如姓名,DOB,电话,地址等)保持大量,那么如何避免使用getter / setter来获取和设置所有这些属性?一个人可以写哪种“行为”类型的方法来填充所有数据?

我认为这个问题很棘手,因为您担心填充数据的行为方法,但是我看不出Customer对象类打算封装什么行为的迹象。

不要混淆Customer为一个类的对象与“客户”作为一个用户/ 演员使用你的软件谁执行不同的任务。

当您说给定一个Customer类,如果有关该客户的信息很多,那么就其行为而言,看来您的Customer类几乎没有区别。一个Rock可以有颜色,可以给它起一个名字,可以有一个字段来存储其当前地址,但是我们不希望岩石有任何智能行为。

从有关吸气剂/塞特尔为邪恶的链接文章中:

OO设计流程以用例为中心:用户执行独立的任务,这些任务会有一些有用的结果。(登录不是用例,因为在问题领域缺少有用的结果。支付薪水是用例。)然后,一个OO系统实现播放构成用例的各种场景所需的活动。

在没有定义任何行为的情况下,将岩石引用为a Customer并不会改变以下事实:它只是一个您想要跟踪的具有某些属性的对象,并且您想玩什么技巧来摆脱吸气剂和二传手。岩石并不在乎它是否具有有效的名称,也不希望岩石知道地址是否有效。

您的订单系统可以将a Rock与采购订单相关联,只要Rock已定义了地址,那么系统的某些部分就可以确保将物料交付到岩石。

在所有这些情况下,Rock它只是一个数据对象,并且将继续成为一个数据对象,直到我们定义具有有用结果而不是假设的特定行为。


尝试这个:

当避免用两个可能不同的含义来重载“客户”一词时,它应该使概念更容易理解。

难道一个Rock对象下定单或者是东西,一个人确实通过点击UI元素,以触发系统中的行动呢?


客户是一个既做很多事情的演员,又碰巧有很多与之相关的信息。这是否有理由创建2个单独的类,其中1个作为角色,而1个作为数据对象?
IntelliData

@IntelliData跨层传递丰富的对象很棘手。如果要将对象从控制器发送到视图,则该视图需要了解对象的常规协定(例如JavaBeans标准)。如果通过电线发送对象,则JaxB或类似工具需要能够将其重新设置为哑对象(因为没有给他们完整的丰富对象)。丰富的对象对于处理数据非常有用,而对于传输状态则很差。相反,哑对象在处理数据方面较差,在传输状态方面则较差。

0

我在这里加2美分,提到说SQL的对象方法。

这种方法基于独立对象的概念。它具有实现其行为所需的所有资源。不需要告诉它如何完成工作-声明式请求就足够了。而且,对象绝对不必将其所有数据都保留为类属性。实际上,它们的来源并不重要,也不应该如此。

谈到聚合,不变性也不是问题。假设您有一系列可以聚合的状态: 将根汇总为传奇 将每个状态实现为独立对象是完全可以的。也许您可以走得更远:与您的域名专家聊天。他或她可能不会将此聚合视为某个统一实体。每个状态可能都有自己的含义,应该得到自己的对象。

最后,我想指出的是,对象查找过程与将系统分解为子系统非常相似。两者都是基于行为,没有别的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.