Java冰棒不可变对象

5
我正在解决一个问题,需要加载大量输入数据,并对这些输入数据进行处理,以创建“问题空间”(即构建数据结构,从而可以高效地访问输入等)。一旦初始化完成,将启动一个多线程进程,以并发方式广泛使用有组织/处理过的输入。
出于性能原因,我不想在并发阶段锁定和同步所有读取操作。我真正想要的是一个不可变对象,可以同时安全地访问多个读取器。
出于实用原因(可读性和可维护性),我不想使InputManager成为真正的不可变对象(即所有字段都是'final'并在构造中初始化)。InputManager将具有众多数据结构(列表和映射),其中每个对象之间具有许多循环引用。这些对象被构造为“真正”的不可变对象。我不想为InputManager提供14个参数的构造函数,但我确实需要InputManager类在构建后提供一致的只读问题空间视图。
我所追求的是Eric Lippert在这里讨论的“冰棒不变性”。
我采用的方法依赖于使用所有变异方法的“包可见性”,并在单个包中执行所有可变操作(即构造InputManager)。获取器均具有公共可见性。
类似于:
public final class InputManager {  // final to prevent making mutable subclasses 
    InputManager() { ... } //package visibility limits who can create one
        HashMap<String,InputA> lookupTable1;
        ...

    mutatingMethodA(InputA[] inputA) { //default (package visibility)
        //setting up data structures...
    }

    mutatingMethodB(InputB[] inputB) { //default (package visibility)
        //setting up data structures...
    }

    public InputA getSpecificInput(String param1) {
        ... //access data structures
        return objA; //return immutable object
    }
}

如果我没有表述清楚的话,总体思路是我将在单个线程中构建InputManager,然后将其传递给多个线程,这些线程将使用该对象进行并发工作。我希望尽可能地强制实施这种“两阶段”可变/不可变对象生命周期,而不要做过于“聪明”的事情。如果有更好的方法来实现这个目标,请提供评论或反馈,因为我相信这不是一个罕见的用例,但我也找不到支持它的设计模式。
谢谢。
3个回答

1
我认为你可以为两个阶段分别设计独立的接口。一个用于构建,另一个用于阅读。这样,你就能干净利落地分离你的访问模式。你可以把这看作接口隔离原则 (PDF)的一个实例:

客户端不应该被迫依赖他们不使用的接口。


1
我喜欢分离接口的想法,并感谢指向pdf的指针。谢谢。 - BrianV

1
只要对象被安全地发布,且读者无法改变它。
这里的“发布”指的是创建者如何使对象对读者可用。例如,创建者将其放入阻塞队列中,读者正在轮询队列。
这取决于您的发布方法。我敢打赌它是安全的。

感谢您的评论。我会确保发布方法是适当的。 - BrianV

1

个人而言,我会坚持您简单而充足的方法,但如果您有兴趣,确实存在一种可变伴侣习语。您可以编写一个内部类,该类具有mutators,同时重用封闭实例的所有字段和getter。

一旦失去了可变的伴侣,留下的封闭实例就真正是不可变的。


感谢大家提供的出色答案。根据这些答案,我查看了guava ImmutableCollections实现,并且那里的“builder”范例似乎非常像您提到的可变伴侣惯用语。我正在研究这个问题,试图决定在项目的这一阶段是否值得付出努力。谢谢! - BrianV
这里有一个相当不错的讨论: (http://codereview.stackexchange.com/questions/11538/comments-on-my-java-pattern-for-mutable-turned-immutable-objects) - BrianV

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