如何重构这段C#代码?

4

我正在处理一些遗留代码,其中有很多像这样的代码:

public class Person
{
    public Person(PersonData data)
    {
        this.Name = data.Name;
        this.Gender = data.Gender ;
    }

    public String Name { get; private set;}
    public String Gender { get; private set;}
}

public class PersonData
{
    public String Name;
    public String Gender;
}

public static Person ReadPerson(Reader reader)
{
    PersonData data = new PersonData;
    data.Name = reader.ReadString();
    data.Gender = reader.ReadString();

    Person p = new Person(data);
    return p;
}

PersonData类存在的目的是在其构造函数中设置Person类中的私有字段。除此之外,PersonData类引入了冗余代码,因为你可以看到现在在Person和PersonData类中都有Name和Sex。在我看来,这种设计不具备可扩展性:现在我有一个新字段“Age”要读取,我必须在两个不同的地方添加“Age”属性。鉴于我已经在遗留代码中看到了很多这样的代码,这是一个有效的设计选择吗?我该如何重构它?
编辑:这两个类是真实代码的简化版本。因此,请原谅使用字符串而不是枚举来表示性别。在真实代码中,PersonData有超过10个字段,Person类也是如此。

在某些编程模型中,有时您需要为同一属性定义2个容器。例如,在MVVM(顺便说一下,这是我知道的唯一模型!)中,我经常不得不在我的“LOB”对象中创建属性,然后再在ViewModel中创建它。然后我对ViewModel进行操作,从而更新LOB。我不知道这是否涵盖了您的情况,但就是这样。 :) - Joe
注意:通常“性别”是一个布尔值,有时会跟着“请吗?”。尝试使用“Gender”(或其变体),它通常基于领域受到限制(性别和偏好/关联可能会进一步分离),以确定接受哪些值:一个简单的“枚举”可以包括“男性,女性,其他”(但可能更多/不同,并且在简化形式中仅考虑前两个选项)。 - user166390
有趣的阅读:维基百科:第三性别 - user166390
这是遗留代码的哪个版本?如果是3.0,您不需要任何东西,可以使用对象初始化程序。 - user1416420
很遗憾,它是用C++/CLI(一个程序集)和Managed C++(另一个程序集)编写的,我甚至不能使用C#来处理它... - AZ.
显示剩余2条评论
5个回答

3

当使用构造函数注入并且您的构造函数中有大量参数时,使用参数对象是一种有效的方法 - 但是如果参数比较少,则不必要。

以下是一个建议:

public class Person
{
    public Person(string name, string sex)
    {
        _name = name;
        _sex = sex;
    }

    public string Name { get {return _name; }}
    public string Sex { get {return _sex; }}

    private readonly string _name, _sex;
}

这使得该类是不可变的


我的例子只是一个简化版本,真正的代码在PersonData类中有很多字段,那么在这种情况下,您会建议使用参数对象甚至引入重复吗? - AZ.
+1. 可能会发生创建一致外观的代码:具有许多参数的构造函数的随机类 -> 将构造函数中的大量参数更改为使用“参数对象”。现在,由于它们没有采用单个参数的构造函数,因此10%的对象看起来不同-> 更改其余部分以实现一致性。 - Alexei Levenkov
@AZ,是的,参数对象比具有3个或更多参数的构造函数更好。另一个选项是将读取直接移动到每个对象中。如果您不需要保证外部调用者无法修改“Person”对象,则考虑放弃“PersonData”,并使字段可读/写。此外,不可变对象更容易理解,因此我会尽量避免这样做。 - Alexei Levenkov
@AZ。没有看到实际代码,很难回答这个问题,答案可能涉及纯度和实用性的混合。在这种情况下,您的参数对象与您正在初始化的类并没有非常不同。如果您有很多这样的情况,答案可能是将类合理化为更好的层次结构,或者重新评估哪些属性需要是只读的。 - slugster

1

如果它是某种外部对象(不像您的情况下似乎是数据传输对象),您可以考虑使用流畅接口构建它们,这不会减少类的数量,但会让您以更漂亮的方式构造对象,并更好地控制所需和可选内容。

如果感兴趣,可以查看标记为流畅接口 的帖子。例如条件构建器��法链接流畅接口:

var person = PersonBuilder
  .CreatePerson()
    .Named(reader.ReadString())
    .Sex(reader.ReadString())
    .Build()

+1 哈,我喜欢流畅的接口!我最近在另一个项目中构建了一个,但从未想过它可以用这种方式。 - AZ.

0
一种方法是,而不是使用


public String Name { get; private set;}
public String Sex { get; private set;}

暴露一个类型为PersonData的属性

public class Person
{
    public PersonData PersonData { get; }
}

你还可以看一下从 PersonData 派生出 Person 的方法。


0

摆脱PersonData,并将Reader提供给构造函数:

public sealed class Person
{
    public Person(Reader reader)
    {
        this.Name = reader.ReadString();
        this.Sex = reader.ReadString();
    }

    public string Name { get; private set; }

    public string Sex { get; private set; }
}

如果他想从任何其他输入创建实例怎么办?他必须存储在数据库中然后再读取吗? - Nikola Davidovic
4
我不太喜欢这个想法,因为它会让Person类与Reader耦合。Person类不应该关心读者如何阅读。 - AZ.
实际上是的。他在示例中使用Person类的方式看起来像是从某种数据存储库中检索出来的。要创建一个新的Person,我会添加一个存储库方法AddPerson(NewPerson newPerson),其中NewPerson是由前端创建的对象。Person类本身只能从数据源实例化。这样你就总是知道你的对象是否被持久化了。 - Mathias Becher
+1. 总体来说是一个不错的方法,但AZ呼吁特定项目。 - Alexei Levenkov
SRP违规...读取数据不是Person的责任。应该有其他抽象来负责实现。从阅读器或数据集中读取数据,然后将其传递给Person的构造函数。 - Dmitriy Startsev
显示剩余3条评论

0
一般来说,我会回到你正在建模的现实世界(或业务:)系统。如果类与该世界中的某些内容匹配,则可以使用它。如果该类纯粹是编程系统的产物,而且似乎是不必要的,那么我会将其丢弃。使用“数据”类也可能隐藏使用显式参数会捕获的各种问题。例如,当您添加“年龄”时,如何检测是否找到了所有情况?如果将其添加为构造函数参数,则每个缺失的情况都会出现错误。

问题在于,如果你的构造函数有20个参数,更不用提你还要小心它们的顺序了... - AZ.
事实上,您有20个参数。那是您正在建模的一个元素。将它们隐藏在结构中并不会改变这一点。我同意排序很微妙。无论如何,整个问题都有点主观 :) - DrC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接