如何在Java中扩展不可变类型

时间:2012-08-04 16:57:15

标签: java oop immutability

我在开始游戏项目时开始使用Java中的不可变值对象,遵循“公共最终字段”方法:

public class Team {
    public final String name, flag;

    public Team(String name, String flag) {
        this.name = name;
        this.flag = flag;
    }
}

到目前为止,这对我来说非常有效,但我需要在不同情况下提供有关团队的各种额外信息。例如,团队在比赛期间具有设定的颜色。问题是,处理这些扩展信息集的最佳方法是什么?我知道这是一个相当普遍的问题,但我想继续使用不可变对象,这可能会影响解决方案。

以下是我提出的选项。他们中的大多数可能“足够好”,但我想学习一些支持和反对它们的论据以供将来参考。

选项1:一个类中的所有内容

public class Team {
    public final String name, flag, colorName;
    public final int colorRgb;

    public Team(String name, String flag, String colorName, int colorRgb) {
        this.name = name;
        this.flag = flag;
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

对于所有用途,这只需要一个类,但没有基于类型的指示,表明预期/提供了多少额外数据。

选项2:子类化

public class TeamWithColor extends Team {
    public final String colorName;
    public final int colorRgb;

    public Team(String name, String flag, String colorName, int colorRgb) {
        super(name, flag);
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

这可能会使基于内容的equals()无法实现。

选项3:合成

public class TeamWithColor {
    public final Team team;
    public final String colorName;
    public final int colorRgb;

    public Team(Team team, String colorName, int colorRgb) {
        this.team = team;
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

如果团队数据和额外数据经常独立变化,那么复制/样板代码就会减少。

选项4:配对/元组(使用不可变的Pair类)

public class TeamColor {
    public final String colorName;
    public final int colorRgb;

    public Team(String colorName, int colorRgb) {
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

Pair<Team, TeamColor> teamWithColor = Pair.create(team, teamColor);

...或者将Team和TeamColor联系在一起的自定义类。

我倾向于选择3或4,但我对你的意见,论点和直觉感兴趣:)

6 个答案:

答案 0 :(得分:6)

正如你所说。团队可以出现在不同的情况下。 这些情况是给团队带来额外属性的背景。

因此,我建议在添加数据的每个不同上下文中使用合成。

public class TeamWithColor {
    public final Team team;
    public final TeamColor teamColor;

    public Team(Team team, TeamColor teamColor) {
        this.team = team;
        this.teamColor = teamColor;
    }
}

也许你会:

public class TeamDuringOlimpics{
    public final Team team;
    public final TeamColor teamColor;
    public final TeamFlag teamFlag;

    public Team(Team team, TeamColor teamColor, TeamFlag teamFlagTeamFlag teamFlag) {
        this.team = team;
        this.teamColor = teamColor;
        this.teamFlag = teamFlag;
    }    
}

答案 1 :(得分:2)

组合听起来像是添加需要变化的上下文数据的好选择。

在Java中,不可变类通常标记为final,不能扩展。请参阅String作为示例。这排除了第2号选项。

厌倦了使用Pairs。没有将Pair类型添加到Java中有很多好的理由。在这种情况下,您可以通过创建新数据类型(即通过合成)更好地建模数据。

创建不可变类的推荐最佳做法:http://www.javapractices.com/topic/TopicAction.do?Id=29

答案 2 :(得分:1)

如果一个不可变类是可继承的,或者如果包含其类型可能是可变的方面,那么它将没有防止恶作剧的安全性。 “不可变”接口同样没有安全性。同样,如果一个对象具有任何可扩展类型的成员,并且该对象被视为包含这些成员引用的对象中的任何信息。您可以自行决定是否存在问题。不可变接口和可扩展可变类可能比深度密封类更通用;如果一个班级不会被深深地封闭,那么部分地制作它就没有什么安全优势。

请注意,如果字段的语义指定标识,而不是包含<,则深度不可变对象可能具有可变类型的字段。 / i>,有问题的对象。例如,拥有一个“快照”对象可能是有用的,该对象包含对某些可变对象的引用,并且对于每个对象,包含对其当前状态的不可变副本;然后,“snapshot”对象将公开一种方法,将每个对象恢复到生成快照时的状态。尽管快照对象引用了一个可变对象,但它仍然是“深度不可变的”,因为快照对可变对象的引用纯粹是为了识别它们,并且即使这些对象的标识也不会改变他们的国家呢。

我喜欢.net中的一个模式,也应该在Java中工作,是具有IReadableFooIImmutableFoo等接口;后者将继承前者但不添加任何新成员。除了读取其属性的成员之外,接口IReadableFoo还应包含将返回AsImmutable()的成员IImmutableFoo。这种方法允许代码在方便的时候使用可变类型,同时最大限度地减少冗余防御性复制(想要持久化的代码必须在其上调用AsImmutable();如果有问题的对象是可变的,那么该操作将创建一个不可变的副本,但是如果对象已经是不可变的,则不需要新的副本。)

答案 3 :(得分:0)

您可以考虑将此信息保留在Team对象之外。例如,您可以拥有地图或地图

这样,您可以使用许多其他值来丰富对象,而无需引入新类或修改现有类。而且,它将保留其他对象的不可变属性。

答案 4 :(得分:0)

我推荐Decorator模式。

public class TeamWithColor extends Team {
    public final Team team;
    public final String colorName;
    public final int colorRgb;

    public Team(Team team, String colorName, int colorRgb) {
        this.team = team;
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

http://en.wikipedia.org/wiki/Decorator_pattern

答案 5 :(得分:0)

我通常会尽量避免将所有内容放在一个类中(选项1),这样我们就有4个选项

  • 如果我需要类是线程安全的,我更喜欢使用不可变元组的volatile / AtomicReference(选项4),而不是使用锁和其他阻止同步(让我们称之为:选项5)
  • 如果不关心线程安全性,那么除非自然存在IS-A关系,否则我会选择“组成”(选项3)而不是“继承”(选项2)

一个有色团队毕竟是一个团队,所以我很乐于利用这种关系。如果我是你,我会这样编码:

public final class ColoredTeam extends AbstractTeam implements Team {
    // the rest comes along intuitively
}

因为“ AbstractTeam”是抽象的,所以只要“ ColoredTeam”本身没有子类,基于内容的equals()实现就可以了,所以我将ColoredTeam最终实现了这一点,但是我可以将承包商私有化,引发异常,等