我是否滥用了静态变量?

6
为了将玩家伪装成另一个实体,我创建了一个伪装类,代码如下所示:

public class Disguise
{
    private static HashSet<Disguise> disguises = new HashSet<>();
    private net.minecraft.server.v1_8_R2.EntityLiving nmsEntity;
    private Player disguise;

    public Disguise(Player disguise, EntityLiving entity, boolean affectLogin)
    {
        if(affectLogin)
            disguises.add(this);

        this.disguise = disguise;
        this.nmsEntity = entity;
    }

    public Disguise(Player disguise, EntityLiving entity)
    {
        this(disguise, entity, true);
    }

    public void send(Player visible)
    {
        if(visible == disguise)
            return;

        EntityPlayer player = NMSUtils.getNMSPlayer(visible);

        nmsEntity.setPosition(player.locX, player.locY, player.locZ);
        nmsEntity.d(disguise.getEntityId());
        nmsEntity.setCustomName(disguise.getDisplayName());
        nmsEntity.setCustomNameVisible(true);

        PacketPlayOutSpawnEntityLiving spawn = new PacketPlayOutSpawnEntityLiving(nmsEntity);
        PacketPlayOutEntityDestroy destroy = new PacketPlayOutEntityDestroy(disguise.getEntityId());

        player.playerConnection.sendPacket(destroy);
        player.playerConnection.sendPacket(spawn);
    }

    public void send(List<Player> visible)
    {
        for(Player player : visible)
            send(player);
    }

    public void send(Player... visible)
    {
        send(Arrays.asList(visible));
    }

    public void send()
    {
        send(new ArrayList<>(Bukkit.getOnlinePlayers()));
    }

    public Player getDisguised()
    {
        return disguise;
    }

    public static HashSet<Disguise> getDisguises()
    {
        return disguises;
    }
}

我还拥有一个静态的HashSet,用来存储所有已创建的实例。我这样做是因为我希望登录的玩家也能看到伪装,并且当玩家退出登录时,我希望将伪装从玩家身上移除。使用静态HashSet是否是正确的方法(就像我现在这样做)?如果不是,应该怎么做呢?


首先,我会封装return disguises;,否则其他类可以开始改变HashSet<T>,永远不要返回原始集合本身... - Willem Van Onsem
7
如果您试图进行面向对象编程,但滥用静态方法,则会将其变成引用编程,因为您永远不需要创建新对象,而只需调用类而无需创建实例。请注意保持原文意思,并使译文通俗易懂。 - jgr208
你认为将HashSet设置为静态的问题是什么?我不确定我看到了什么问题。他有一个集中的存储库,并且不需要实例来使用它。是因为他将其隐藏在Disguise中吗?但我只会为它提供访问器/修改器...永远不会直接暴露一个集合。 - user4229245
你的程序需要或者你想要它是面向对象的吗?如果是这样,除非你不需要类的实例(例如一个工具类),否则不要使用静态关键字。如果你不关心面向对象,并且在需要使用一个类的函数时创建一个新的类实例,请随意在任何地方使用静态关键字。这完全取决于你想要做什么的设计。 - jgr208
那么如果设计上不会创建另一个哈希表实例,它就没有理由是静态的。你可以使用单例模式来确保这种情况不会发生。 - jgr208
显示剩余4条评论
3个回答

4

static 被要求使用。由于其特性,它容易遭受“滥用”,但这只是挑战的一部分。

总之,如果您的模块在不出现错误的情况下实现了所需功能,请不要过于担心这个级别(特定变量)的最佳做法。它不太可能扩展到设计不良会给您带来问题的程度。毕竟,它不是生命维持系统。

如果您想为了好玩而练习良好的格式,则我的第一感觉是将Disguise中的管理逻辑移至(例如)DisguiseManager类,并通过管理器类处理所有Disguise的创建/销毁。更简单的方法是在Disguise上使用私有构造函数和静态create / destroy方法。构造函数中的全局副作用通常是不好的形式。


那么我是否需要在Manager类中使用单例模式呢?因为我只想要一个伪装列表。 - XLordalX
我不明白为什么你“必须”做任何事情。 Singleton 本质上就是一个穿着更好衣服的静态全局变量。 如果你只想要一个列表,那就只实例化一个。 没有必要过度思考 :) - Cheezmeister

2
基本上每次调用构造函数时,您都希望将this添加到全局位置。
这没问题,但有两个问题:
1. 在构造函数中公开this是危险的,需要进行仔细分析。(在这个方面,您的代码存在错误)
2. 并发 - 如果应用程序是多线程的,则需要线程安全。(在并发环境中,在构造函数中公开this更加棘手)
3. 垃圾回收 - 当对象变成“垃圾”时,如何从全局位置中删除它。

当持有伪装物的玩家退出登录时,该对象将从HashSet中移除。 - XLordalX

2
使用静态对象在代码不断增长、许多访问该对象的访问器时会变得非常令人沮丧。如果您要调试代码,那么您将如何捕捉到精确的代码以操作HashSet?
为什么不重构使用HashSet的客户端,通过getter获取它?将HashSet实例封装为Singleton,只创建一个HashSet用于存储Players/Disguies。
通过任何一种getter方法,例如通过Singleton,您可以轻松地在访问HashSet之前或之后添加其他代码。例如,在使用返回HashSet的方法之后,您可以打印HashSet的内容。您也可以使用静态对象做到这一点,但查找所有静态对象的用法可能是个噩梦。

使用单例模式不会导致相同的结果吗?因为我确定不会有任何未来的实现。 - XLordalX
@XLordalX 更新了我的回答,突出了不同之处。 - Evdzhan Mustafa
请不要使用单例模式,因为这可能会引起问题,这已经有很多文档进行了记录 -> https://dev59.com/OWcs5IYBdhLWcg3wjUuR - tddmonkey
@MrWiggles,你链接中被接受的答案指出:“如果你发现你想要使用单例模式,你可能需要考虑你的设计,但是有时候它是有用的。” - Jonny Henly
@MrWiggles 我同意,但是被接受的答案进一步说明:“例如,我曾经需要编写一个应用程序,最多只能有一个数据库连接来处理数千个请求。因此,单例模式是有意义的,因为我受到资源限制,只能拥有一个实例。” 这个例子似乎与OP的情况几乎相同。 - Jonny Henly
显示剩余3条评论

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