Revxrsal / Lamp

A modern annotations-driven commands framework for Java and Kotlin

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Question] Wiki Command Singleton

TheoBong opened this issue · comments

commented

Hi, am curious as to why on wiki pages command conditions and permission readers, you made enums with a static reference to access the class? Is there an advantage to this over just instantiating the class once in the already singleton main class (when registering)?

Looks like static abuse at first glance but could be completely justifiable and I just don't see it. Thanks.

commented

I think it's a common misconception to classify any use of static as static abuse.

For example, the below are all perfectly valid use cases of static:

  1. Utility functions and utility classes
  2. Classes that prohibit outside instantiation, i.e. singletons or sealed-like interfaces
  3. Constants

On the other hand, these are examples of bad uses of static:

  1. Non-atomic, non-volatile, or non-synchronized static variables that get mutated
  2. Non-synchronized lazy singletons (look these up)
  3. Data structures that are not thread-safe, such as a HashMap, that get continuously read and written from without appropriate synchronization or locking
  4. Instances of objects that hold references to possibly invalid objects, or instances that may be invalid/old/out of sync by the time they are accessed.

To delve a bit into the magic of the JVM, static variables do not get garbage collected (freed from memory) except when the entire class loader is garbage collected (think: your plugin is unloaded). This means that, if you're not careful, you may accidentally store references to objects that are no longer valid.

As you can see, bad uses of static revolve around two things:

  1. Uncoordinated access to static variables: This may not be an issue in small, single-threaded applications, but it becomes visible and problematic in large, complicated multi-threaded apps, which are known to cause data races. Data races are known to cause unpredicted and non-deterministic behavior, and are a breeding ground for hard-to-find bugs.
  2. Storing objects that may no longer be valid by the time they are accessed. Consider the following example:
public class BadStatic implements Listener {
	
	private static Player player;

	@EventHandler
	public void onPlayerJoin(PlayerJoinEvent event) {
		player = event.getPlayer();
	}
}

This seemingly innocent code is a good example of static abuse. We capture the Player when they join. Now, when they leave, all the instances get removed from the server. However, because we are still holding a strong reference to the player, they will stay in memory during the lifetime of our plugin and are never going to get freed.

This, however, is not limited to using it with static. Even using it as an instance field, or having it stored in a data structure like a List or a Map, would prevent the object from being freed as long as our listener is alive, thus unnecessarily consuming memory resources. (If you're interested, see WeakReference)

Static simply makes it worse: it'll stay during the lifetime of our app, rather than the lifetime of our object.

Now, getting back to your original question, using enums with INSTANCE fields is a pattern in Java called the 'Enum Singleton Pattern'. Give this article a read, as it goes over the advantages of using it over the more known public static final MyClass INSTANCE = ....

Finally, as to whether it's better than direct instantiation or not, I'd say the difference is marginal, and it's mostly a design choice. As a general rule of thumb: if your class only contains methods, does not contain any state, and direct instantiation brings no advantage, make it a utility class with a private constructor. If you can't because it's an implementation of an interface, use singletons.
It makes your code more robust, easier to debug, easier to maintain, and harder to misuse. It also simplifies things like equality checks, toString operations, etc., and you'll make sure users have no way to misuse your API.

I recommend you go read things about static abuse, what is considered static abuse, and what is not, and what are legitimate uses of statics. Definitely something many people don't know about :)

commented

Well said! The procedural(/“utility”) nature of the class, without state, seems to be the factor I forgot to consider. Learned something new today with the whole enum singleton pattern. Really appreciate you taking the time to explain this- your response brightened my day.

commented

On second thought, in the code segment

public enum LifetimeExecutionCondition implements CommandCondition {  
    INSTANCE;  
  
    private final Set<UUID> executed = new HashSet<>();  
  
    @Override public void test(CommandActor actor, ExecutableCommand command, List<String> arguments) {  
        if (command.hasAnnotation(ExecutableOnceInLifetime.class)) {  
            if (!executed.add(actor.getUniqueId())) {  
                throw new CommandErrorException("You may not run this command again!");  
            }  
        }  
    }  
}

The static state created by private final Set<UUID> executed = new HashSet<>();, while not being mutated in any non-synchronized way, still feels like it could be considered static abuse by your standards. Having non-constant, mutated state in a singleton class that is meant to operate procedurally ("utility class") is abuse since it hinders the testability/encapsulation of any part of the program that uses it. Any part of the program that depends on LifetimeExecutionCondition now has to also depend on the entire rest of the program for data contained within the set executed. This feels like it'd be considered "global state" which go against encapsulation principles outlined by OOP and TDD.

Your response justifies the use of singletons in the usage of a utility/procedural way. To make the class truly "utility" or procedural (just methods, no state), wouldn't you have to pass in the UUID through the function parameters?

Once again, I could completely be missing something here so please let me know! Writing this kind of late so could be completely wrong. Once again, thanks for being so helpful.

commented

What you said is entirely correct, and perhaps having a singleton here is a bad design decision indeed. A more robust way would have it as a class where each would contain its own set of executed and you'd obtain it through traditional instantiation. Though I'd say this may not be a major issue in a CLI-like app (where the example was given) where your code is single-threaded and you only maintain a single instance of a CommandHandler. Either way, I'll update the example in the wiki.

Furthermore, I've done a bit more reading and realized there's one important pitfall of singletons, which I may haven't taken into account. When a class is loaded, its singleton may get initialized immediately, and any classes that this singleton depends on will get initialized as well. Therefore, it's important to ensure that you don't end up with circular dependencies where class A depends on B and class B depends on A. This post goes a bit more into this topic, and it's an excellent read as well.

I appreciate you bringing this issue into my attention!