如何反对业务对象类设计的这种“完全公开”的思维方式


9

我们做了很多的单元测试,我们的业务对象的重构,而我似乎有对类设计比其他同龄人不同的意见。

我不喜欢的示例类:

public class Foo
{
    private string field1;
    private string field2;
    private string field3;
    private string field4;
    private string field5;

    public Foo() { }

    public Foo(string in1, string in2)
    {
        field1 = in1;
        field2 = in2;
    }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
    }

    public Prop1
    { get { return field1; } }
    { set { field1 = value; } }

    public Prop2
    { get { return field2; } }
    { set { field2 = value; } }

    public Prop3
    { get { return field3; } }
    { set { field3 = value; } }

    public Prop4
    { get { return field4; } }
    { set { field4 = value; } }

    public Prop5
    { get { return field5; } }
    { set { field5 = value; } }

}

在“ real”类中,它们不是全部都是字符串,但是在某些情况下,我们有30个备用字段用于完全公共属性。

讨厌这堂课,而且我不知道我是否只是在挑剔。注意事项:

  • 属性中没有逻辑的私有后备字段,似乎是不必要的并使类膨胀
  • 多个构造函数(还可以)但与
  • 所有拥有公开授权者的物业,我不是粉丝。
  • 由于构造函数为空,可能不会为属性分配任何值,如果调用者不知道,则可能会得到一些非常不需要且难以测试的行为。
  • 属性太多!(在30例中)

作为执行者,我很难真正知道对象Foo在任何给定时间处于什么状态。有人提出了这样的论点:“ Prop5在构造对象时,我们可能没有必要的信息。好吧,我想我可以理解,但是如果是这样,则只Prop5公开setter,而不是在一个类中最多公开30个属性。

我是否只是因为想要一门“易于使用”而不是“易于写作(一切都公开)”的课程而变得挑剔和/或疯狂?诸如此类的类对我尖叫,我不知道将如何使用它,所以我只是将所有内容公开以防万一

如果我不是很挑剔,那么有什么好的论点可以对抗这种思维呢?我不太善于表达观点,因为我很沮丧地试图表达自己的观点(当然不是故意的)。



3
对象应始终处于有效状态。也就是说,您不需要具有部分实例化的对象。一些信息不是对象核心状态的一部分,而其他信息则是(例如,桌子必须有腿,但衣服是可选的);实例化后可以提供可选信息,实例化时必须提供核心信息。如果某些信息是核心信息,但在实例化时未知,那么我说这些类需要重构。在不了解课程背景的情况下很难说更多。
Marvin

1
说“我们可能在对象构建时没有必要的信息来设置Prop5”使我问“您是说有些时候在构建时会拥有该信息,而在其他时候则不会。有这些信息吗?” 这让我想知道,这个类实际上是否要代表两种不同的事物,或者是否应该将该类分解为较小的类。但是,如果“以防万一”的话,这也很糟糕,IMO。对我来说,对于如何创建/填充对象还没有一个明确的设计。
Wily博士的学徒,

3
对于属性中没有逻辑的私有后备字段,有没有理由不使用自动属性
Carson63000 '16

2
@Kritner,自动属性的全部要点是,如果将来确实需要实际的逻辑,则可以无缝地添加它来代替auto getset:-)
Carson63000,2016年

Answers:


10

完全公开的类可以为某些情况辩护,而在另一种极端情况下,仅使用一个公开方法(可能还有很多私有方法)的理由。并且带有一些公共类和一些私有方法的类。

所有这些都取决于您要使用它们建模的抽象类型,系统中的哪些层,不同层中所需的封装程度以及(当然)该类作者的思想流派。从。您可以在SOLID代码中找到所有这些类型。

整本书中都有关于何时选择哪种设计的书,因此在这里我不会列出任何规则,这部分的空间不足。但是,如果您有一个现实世界的抽象示例想要通过类进行建模,那么我相信这里的社区将很乐意帮助您改进设计。

解决您的其他问题:

  • “属性中没有逻辑的私有后备字段”:是的,您是对的,对于琐碎的getter和setter来说,这只是不必要的“噪音”。为了避免这种“膨胀”,C#为属性的get / set方法提供了一种捷径语法:

所以代替

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

   public string Prop1 { get;set;}

要么

   public string Prop1 { get;private set;}
  • “多个构造函数”:这本身不是问题。当其中存在不必要的代码重复时(如您的示例所示),或者调用层次结构很复杂,就会出现问题。通过将公共部分重构为单独的函数,并以单向方式组织构造函数链,可以轻松解决此问题。

  • “由于构造函数为空,可能不会为属性分配任何值”:在C#中,每个数据类型都有一个明确定义的默认值。如果未在构造函数中显式初始化属性,则将为其分配此默认值。如果有意使用它,则完全可以-因此,如果作者知道自己在做什么,则可以使用空的构造函数。

  • “属性太多!(在30种情况下)”:是的,如果您可以自由地以未开发的方式设计此类,那么30种太多了,我同意。但是,并不是我们每个人都有这种奢侈的感觉(您不是在下面的注释中写的,它是旧系统吗?)。有时,您必须将现有数据库或文件中的记录或第三方API中的数据映射到您的系统。因此,在这些情况下,可能不得不使用30个属性。


谢谢,是的,我知道POCO / POJO占有一席之地,我的例子非常抽象。但是,我认为如果不去读小说,我不会变得更加具体。
克里特纳

@Kritner:请参阅我的编辑
布朗

是的,通常当我创建一个崭新的类时,我会选择自动物业路线。在我看来,拥有私人支持字段“仅因为”是不必要的,甚至会引起问题。当您拥有一个类的30个属性和100+个类的属性时,要说会有一些Properties设置或从不正确的后备字段中获取信息是很容易的事情……事实上我在重构中已经遇到过几次:)
Kritner

谢谢,字符串的默认值null是不是?因此,如果实际上在中使用了其中一个属性.ToUpper(),但从未为其设置值,则它将引发运行时异常。这又是一个很好的例子,为什么需要为类的数据应该具有对象的构造过程中进行设置。不仅将它留给用户。感谢
Kritner

除了太多的未设置属性外,此类的主要问题是它具有零逻辑(ZRP)且是贫血模型的原型。不需要诸如DTO之类的可以用语言表达的内容,这是一个糟糕的设计。
user949300 '16

2
  1. 通过说“属性中没有逻辑的私有后备字段,似乎是不必要的并使类膨胀”,您已经有了一个不错的论点。

  2. 这里似乎没有多个构造函数是问题。

  3. 至于将所有属性公开...也许是这样想的,如果您想在线程之间同步对所有这些属性的访问,您将很难过,因为您可能必须在它们所处的任何地方编写同步代码用过的。但是,如果所有属性都由getter / setter括起来,则可以轻松地将同步构建到类中。

  4. 我认为,当您说“难以测试的行为”时,这种说法就说明了一切。您的测试可能必须检查它是否不为null或类似的值(使用属性的每个位置)。我真的不知道,因为我不知道您的测试/应用程序是什么样的。

  5. 正确的方法是过多的属性,是否可以尝试使用继承(如果属性足够相似),然后使用泛型创建列表/数组?然后,您可以编写getter / setter来访问信息,并简化所有属性的交互方式。


1
谢谢-#1和#4我肯定会感到最舒服。不太清楚您在#3中的意思。#5类中的大多数属性构成了数据库上的主键。我们使用竞争性密钥,有时最多8列-但这是另一个问题。我在想试图把组成钥匙插入自己的类/结构,这将消除(至少在很多性能的部分这一类) -但这是数以千计的多行代码的遗留应用程序来电者。因此,我想我还有很多要考虑的问题:)
Kritner

嗯 如果该类是不可变的,则没有理由在该类内编写任何同步代码。
RubberDuck

@RubberDuck此类是不可变的吗?你什么意思?
史努比

3
@StevieV 绝对不是一成不变的。具有属性设置器的任何类都是可变的。
RubberDuck

1
@Kritner属性如何用作主键?这大概需要一些逻辑(空检查)或规则(例如,NAME优先于AGE)。根据OOP,此代码属于此类。你能以此为论据吗?
user949300

2

正如您告诉我的,这Foo是一个业务对象。它应根据您的域将某些行为封装在方法中,并且其数据必须保持尽可能的封装。

在您显示的示例中,它Foo看起来更像DTO,并且违反了所有OOP原则(请参阅Anemic Domain Model)!

作为改进,我建议:

  • 使此类尽可能不变。如您所说,您要进行一些单元测试。确定性是单元测试的一种强制能力,确定性可以通过不变性来解决,因为它解决了副作用问题。
  • 将这个神类分成多个类,每个类仅做一件事(请参阅SRP)。在一个真正的PITA中对30个属性类进行单元测试。
  • 通过仅具有一个主要构造函数,而其他构造函数调用主要构造函数来消除构造函数冗余。
  • 删除不必要的吸气剂,我严重怀疑所有吸气剂是否真的有用。
  • 在业务类别中恢复业务逻辑,这就是它的所属!

这些备注可能是潜在的重构类:

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}

2

如何反对业务对象类设计的这种“完全公开”的思维方式

  • “在客户类中,有几种分散的方法不仅仅是“ DTO职责”。它们属于同一类。”
  • “这可能是一个'DTO',但它具有商业形象-我们需要超越Equals。”
  • “我们需要对它们进行排序-我们需要实现IComparable”
  • “我们将其中几个属性串在一起。让我们重写ToString,这样每个客户端都不必编写此代码。”
  • “我们有多个客户端对此类进行相同的操作。我们需要干燥代码。”
  • “客户正在操纵这些东西的集合。很明显,它应该是一个自定义集合类;并且许多较早的观点将使该自定义集合比目前的功能更强大。”
  • “查看所有乏味的,公开的字符串操作!将其封装在与业务相关的方法中将提高我们的编码效率。”
  • “将这些方法放到类中将使其可测试,因为我们不必处理更复杂的客户端类。”
  • “我们现在可以对这些重构方法进行单元测试。实际上,由于x,y,z的原因,客户端无法测试。

  • 就以上任何一个论点而言,可以合计:

    • 功能变为可重用。
    • 干了
    • 它与客户端分离。
    • 对类进行编码更快,并且出错更少。
  • “一个做得好的类会隐藏状态并公开功能。这是设计OO类的起点。”
  • “最终结果在表达业务领域,不同实体及其显式交互方面做得更好。”
  • “这种“开销”功能(即没有明确的功能要求,但必须完成)明显地脱颖而出,并遵循单一责任原则。
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.