与在创建过程中可变但之后不可变的成员一起上课


22

我有一个创建对象集合的算法。这些对象在创建期间是可变的,因为它们最初很少,但是随后在算法中的不同位置填充了数据。

算法完成后,就决不能更改对象,但是对象会被软件的其他部分消耗。

在这些情况下,如下所述,拥有该类的两个版本是否被视为一种好习惯?

  • 可变的是由算法创建的,然后
  • 算法完成后,将数据复制到不可变的对象中,然后将其返回。

3
您能否编辑您的问题以澄清您的问题/问题?
西蒙·贝格

您可能还会对此问题感兴趣:如何在.NET中冻结冰棍(使类不可变)
R0MANARMY

Answers:


46

您也许可以使用构建器模式。它使用一个单独的“构建器”对象来收集必要的数据,并在收集所有数据后创建实际的对象。创建的对象可以是不可变的。


您的解决方案是符合我要求的正确解决方案(并非全部说明)。经调查,返回的对象并非都具有相同的特征。builder类具有许多可为空的字段,并且在构建数据后,它选择要生成的3种不同类中的哪一种:这些类通过继承相互关联。
保罗·理查兹

2
@Paul在这种情况下,如果此答案解决了您的问题,则应将其标记为已接受。
2015年

24

实现此目的的一种简单方法是拥有一个允许读取属性和仅调用只读方法的接口,以及一个实现该接口的类,该类也使您可以编写该类。

您创建它的方法,处理前者,然后返回后者,仅提供一个与之交互的只读接口。这将不需要复制,它使您可以轻松地微调希望调用者(而不是创建者)可以使用的行为。

举个例子:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

这种方法的唯一缺点是可以将其简单地强制转换为实现类。如果是安全问题,那么仅使用此方法是不够的。一种解决方法是,您可以创建一个Facade类来包装可变类,该类仅提供调用者可以使用且不能访问内部对象的接口。

这样,连铸造都不会对您有帮助。两者都可以从相同的只读接口派生,但是强制转换返回的对象只会给您Facade类,这是不可变的,因为它不会更改包装的可变类的基础状态。

值得一提的是,这并不遵循通过不变构造函数一劳永逸地构造不可变对象的典型趋势。可以理解,您可能必须处理许多参数,但是您应该问自己是否需要预先定义所有这些参数,或者是否可以稍后引入。在这种情况下,应使用仅带有必需参数的简单构造函数。换句话说,如果它掩盖了程序中的另一个问题,请不要使用此模式。


1
返回“只读”对象并不能改善安全性,因为获取该对象的代码仍可以使用反射来修改该对象。甚至一个字符串也可以使用反射进行修改(未复制,就地修改)。
MTilsted 2015年

可以使用私人班级
很好

@Esben:您仍然必须应对MS07-052:代码执行导致代码执行。您的代码与他们的代码在相同的安全上下文中运行,因此他们只需附加调试器即可执行所需的任何操作。
凯文

您可以说Kevin1关于所有封装。我不是要防止反射。
Esben Skov Pedersen

1
使用“安全性”一词的问题是,立即有人认为我所说的更安全的选择等同于最大的安全性和最佳实践。我也没说过 如果您将库交给他人使用,除非被混淆(有时甚至被混淆),您可能会忘记保证安全性。但是,我认为我们都可以同意以下事实:如果您使用反射篡改了返回的立面对象,以便检索其中包含的内部对象,那么您并没有完全使用应该使用的库。
尼尔

8

您可以使用Builder模式为@JacquesB说,或者想为什么它实际上是你的这些对象必须在创建过程中是可变的?

换句话说,为什么创建它们的过程必须及时分散,而不是将所有必需的值一次性传递给构造函数并创建实例呢?

因为Builder可能是解决错误问题的好方法。

如果问题是您最终得到的构造函数的长度约为10个参数,并且希望通过一点一点地构建对象来减轻它的负担,则可能表明设计已被弄乱了,这10个值应为“袋装” /分为几个对象...或主要对象分为几个较小的对象...

在这种情况下-始终坚持不变,只要改进设计即可。


由于代码执行多个数据库查询以获取数据,因此很难一次创建所有内容。它比较不同表和不同数据库中的数据。
保罗·理查兹
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.