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

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

评论

是否有可能重复使用“普通旧数据”类?

另请参阅:何时使Getter和Setter合理化

我要指出的是,如果您必须处理Java Beans规范,则将有getter和setter。很多东西都使用Java Bean(jsps中的表达语言),而要避免这种情况可能会很困难。

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

@Daniel Pryden:name()可以表示要设置还是要获取?...

#1 楼

如很多答案和评论中所述,DTO在某些情况下是适当且有用的,尤其是在跨边界传输数据时(例如序列化为JSON以通过Web服务发送)。对于此答案的其余部分,我或多或少会忽略它,而谈论领域类,以及如何设计它们以最小化(如果不消除)getter和setter,并在大型项目中仍然有用。我也不会谈论为什么删除吸气剂或二传手,或者何时去除,因为那是他们自己的问题。
例如,假设您的项目是象棋或战舰这样的棋盘游戏。您可能有多种方法可以在表示层(控制台应用程序,Web服务,GUI等)中表示它,但是您也有一个核心域。您可能拥有的一类是Coordinate,它代表董事会中的一个职位。编写它的“邪恶”方法是:
public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(为简洁起见,我将用C#而不是Java编写代码示例,因为我对此更加熟悉。希望是没问题。概念是相同的,翻译也应该很简单。)
删除Setters:不变性
虽然公共getter和setter都可能有问题,但是Setter则是两者中的“邪恶”得多。它们通常也更容易消除。这个过程很简单,只需在构造函数中设置值即可。任何先前使对象发生突变的方法都应返回新结果。因此:
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变异类中的其他方法。要更严格地保持不变,可以使用readonly(Java中的final)。但是无论采用哪种方式(无论是使财产真正不可变,还是只是通过设置者防止直接的公共突变),都可以消除公共设置者。在绝大多数情况下,这都很好。
删除吸气剂,第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,或在网站等上显示客户信息,在这种情况下,某种形式的Customer DTO可能很有意义。但是,仅仅因为您的系统包括客户并且客户在数据模型中表示出来,并不意味着您应该在域中拥有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>>来提取它们。根据口味,您可能会觉得这使整个练习毫无意义。
但是,对于公众获取者来说,以错误的方式做事非常容易,只需直接提取数据并违反告诉,不要问。而使用此模式实际上更容易以正确的方式进行操作:当您要创建行为时,将自动从创建与其关联的类型开始。违反TDA的行为将非常明显,并且可能需要采用更简单,更好的解决方案。在实践中,这些要点比正确的方法要容易获得,而不是获得者所鼓励的“邪恶”方法。
最后,即使最初并不明显,实际上可能存在一些方法公开足够多的行为需求,以避免暴露状态。例如,使用我们的早期版本的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,这允许u更改分配给DTO的字段而不会破坏现有代码。 2.假设客户有足够的行为证明自己是一个实体,您将如何访问道具以对其进行修改,例如地址/电话变更等

–IntelliData
15年5月28日在17:02



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

– Ben Aaronson
15年5月28日在17:58

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

– Ben Aaronson
15年5月28日在18:03

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

– Ben Aaronson
15年5月28日在18:03



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

– radarbob
2015年6月3日15:38

#2 楼

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

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

进一步阅读什么时候使用Getter和Setters是合理的

评论


那么,为什么所有关于“ Getter / Setters”的宣传都是邪恶的?

–IntelliData
15年5月18日在15:03

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

–罗伯特·哈维(Robert Harvey)
15年5月18日在15:04



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

–空
15年5月18日在15:09

@MetaFight setEvil(null);

–user40980
15年5月18日在16:26

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

–主教
15年5月18日在16:47

#3 楼

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

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

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

阅读续您链接到的文章,看看他如何建议在不使用邪恶设置者的情况下,在“雇员”对象上实际设置“名称”和“工资”之类的属性。事实证明,他使用的是带有“导出器”的模式,该模式中填充了名为addName,addSalary的方法,这些方法又设置了相同名称的字段...因此,最后他还是完全使用了setter模式,只是命名不同惯例。

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

评论


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

–IntelliData
15年5月18日在15:26

再说一次,有人说过“从不使用OOP”:有害.cat-v.org / software / OO_programming

–雅克B
15年5月18日在18:57

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

–leftaround关于
15年5月19日在7:44



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

–雅克B
15年5月19日在9:29

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

–user949300
2015年5月19日在17:19

#4 楼

要从数据对象转换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和货币交易的情况下,客户可能仅用于: br />

帮助人们识别出他们的汇款是交给了正确的人;和
Account s。

该客户不需要DOBFavouriteColourTel之类的字段,甚至不需要Address


客户在用户登录银行网站的情况下。

相关字段为:




FavouriteColour,其形式可能是个性化主题;

LanguagePreferences
GreetingName

代替具有getter和setter的属性,可以通过单个方法捕获这些属性:


PersonaliseWebPage(模板页面);




在营销和个性化邮件环境中的客户。

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


IsCustomerInterestedInAction();和
GetPersonalDiscounts()。

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





1。当然,CustomerAccount类只是示例,对于简单的示例或家庭作业而言,拆分此客户可能是过大的事,但是通过拆分的示例,我希望证明将数据对象转换为有责任的对象的方法会起作用。


评论


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

–雅克B
15年5月18日在18:48

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

–IntelliData
2015年5月18日19:00



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

–user949300
15年5月18日在20:21

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

– CodesInChaos
2015年5月19日上午10:13

像Customer.FavoriteColor这样的东西呢?

–加布
15年5月20日在7:38

#5 楼

TL; DR


行为建模很好。
good(!)抽象建模更好。
有时需要数据对象。
<行为与抽象

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

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

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

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


客户类

客户当然是系统中的一个实体,应尝试提供有用的抽象。例如,它可能应该与购物车关联,并且购物车和客户的组合应允许进行购买,这可能会启动诸如向其发送所请求的产品,向他收取费用(考虑到他选择的付款)的动作方法。等等。要注意的是,您提到的所有数据不仅与客户相关联,而且所有这些数据都是可变的。客户可能会搬家。他们可能会更改其信用卡公司。他们可能会更改其电子邮件地址和电话号码。哎呀,他们甚至可能改变自己的名字和/或性别!因此,功能齐全的客户类别确实必须提供对所有这些数据项的完全修改访问。

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

评论


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

–IntelliData
2015年5月19日下午16:56

#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时。

但是,对于IMO,将Customer与抽象标准ResultSet耦合是完全可以接受的。一个单独的帮助器对象(我将其称为CustomerDBHelper,可能是AbstractMySQLHelper的子类)知道与数据库的所有复杂连接,知道棘手的优化细节,知道表,查询,联接等...(或使用像Hibernate这样的ORM来生成ResultSet。您的对象与ResultSet对话,q4312079q是一个抽象标准,不太可能更改。当您更改基础数据库或更改架构时,Customer不会更改,而CustomerDBHelper会更改。如果幸运的话,只有AbstractMySQLHelper会自动进行更改,以便为客户,商家,运输等进行更改。

这样,您可以(也许)避免或减少对getter和setter的需求。 br />
并且,Holub文章的重点是,将以上内容与使用getter和setter进行所有操作并更改数据库的情况进行比较和对比。

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

评论


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

–IntelliData
2015年5月19日下午16:55

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

–user949300
15年5月19日在17:10



#7 楼

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

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

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

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

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

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

#8 楼

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

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

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

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

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

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

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

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

实际上,几乎没有理由对getter / setter进行辩解,因为它们最常用于建模纯数据。使用对象的框架和开发人员确实希望getter / setter所做的仅是设置/获取字段。您实际上在使用getter / setter方面所做的工作,远远超过了在公共场所进行的工作。

那是旧习惯,很难去除旧习惯...您甚至可能受到同事或同事的威胁老师,如果您不擅长将吸气剂/吸水剂放到任何地方,如果他们没有足够的背景来更好地了解它们是什么和不是什么的话。

您可能需要更改语言才能驾驭所有人论文获取者/设定者样板代码。 (如C#或Lisp)。对我来说,获取者/设定者只是另一个十亿美元的错误...

评论


C#属性实际上并没有像getter和setter那样实现封装。

–IntelliData
15年5月19日在17:01

[gs] etters的优点是您可以执行通常不执行的操作(检查值,通知观察者),模拟不存在的字段等,并且主要是可以在以后进行更改。对于Lombok,样板消失了:@Getter @Setter类MutablePoint3D {private int x,y,z;}。

– maaartinus
2015年5月20日,3:47



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

–尼古拉斯·布斯凯(Nicolas Bousquet)
15年5月20日在7:06

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

–尼古拉斯·布斯凯(Nicolas Bousquet)
15年5月20日在7:17

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

– maaartinus
15年5月20日在7:51

#9 楼


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

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

不要将Customer作为对象类与“ Customer”(使用您的软件执行不同任务的用户/角色)混淆。

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

来自链接有关使吸气剂/设置者变得邪恶的文章:


OO设计过程以用例为中心:用户执行独立的任务,具有一些有用的结果。 (登录不是用例
,因为它在问题域中缺乏有用的结果。绘制
工资支票是用例。)然后,一个OO系统实现所需的活动
播放包含用例的各种场景。


在没有定义任何行为的情况下,将岩石称为Customer不会改变以下事实:它只是一个带有某些对象的对象您想跟踪的属性,而想要玩些什么技巧来摆脱getter和setter都没关系。岩石并不在乎它是否具有有效的名称,也不希望岩石知道地址是否有效。

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

在某些情况下,Rock只是一个数据对象,在我们定义具有有用结果而不是假设的特定行为之前,它将一直是一个数据对象。


请尝试以下操作:

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

Rock对象是否下订单,或者是人类通过单击UI元素来触发系统中的操作而执行的操作?

评论


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

–IntelliData
15年5月19日在16:59

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

–user40980
15年5月19日在19:49

#10 楼

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

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

关于聚合,不变性也不是问题。假设您有一系列可以聚合的状态:

将每个状态实现为独立对象是完全可以的。也许您可以走得更远:与您的域名专家聊天。他或她可能不会将此聚合视为某个统一实体。

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