question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Make static methods that create objects distinct from those that create their builders.

See original GitHub issue

_This issue is taken from https://github.com/SpongePowered/SpongeAPI/pull/2275#issuecomment-739912355_

One large pain point that I’ve encountered is that there are factory methods that are named the same as their builder methods. Coupled with the convenience methods on the immutable variants, the method that should be used to create an object is extremely unclear (another issue will be opened on the convenience methods).

Take the following two methods from your library in isolation - what someone booting an IDE and typing Component.text( might see.

Component.text();
Component.text(String);

They both look like factory methods to me: my assumption is that they would both create Components, one empty, one containing the string. However, it turns out that the first is a TextComponent.Builder, and one is a TextComponent. What’s bad, however, is that my assumption can actually hold - if I use these as ComponentLikes in something that accepts ComponentLikes, then I will indeed end up with Component.empty() (a method that’s actually okay but totally eclipsed by this) and my text component. So now we have two methods that don’t do the same thing, but can be treated as such, but are actually totally different ways to do the same thing.

While both can actually be used in the same way, with the same methods names, they really shouldn’t be. In theory, I can do the following:

Component.text().content("Text").color(NamedTextColor.GREEN).style(Style.style().decorate(TextDecoration.BOLD).decorate(TextDecoration.ITALIC).build());
Component.text("").content("Text").color(NamedTextColor.GREEN).decorate(TextDecoration.BOLD).style(Style.style(TextDecoration.BOLD).decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE)); // slighly facetious with the starting method, but it proves a point

and you get the same result - one uses the builders, one does not - and because both the builder and the standard component implement ComponentLike, I can use these within other calls and would be no wiser to the fact they are two totally different objects.

I propose the following:

  • Deprecate any static methods that return builders and are named the same as factory static methods that do not return builders.
  • Create replacement methods suffixed with Builder to clearly mark that those methods return a different type.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:5
  • Comments:14 (9 by maintainers)

github_iconTop GitHub Comments

14reactions
asteicommented, Dec 13, 2020

I feel like this issue is an unhelpful reproduction of #159, but in the opposite direction. But it more generally raises issues for a lot of our projects.

Before I continue, I’d like to make a digression. In the issue @dualspiral linked, he talks about the disparity between Sponge’s API design choices and Adventure’s. This would be a valid issue if it weren’t for the fact that Adventure is not Sponge. It is used by several major consumers in the Minecraft ecosystem: Geyser, Velocity, Sponge, and eventually Paper. Having Adventure cater more to the needs of the Sponge project hurts the Adventure project and directly affects everyone who is not Sponge who depends on Adventure. (If anything, at the time of writing this, Geyser may be the single biggest user of Adventure in the community). We can bikeshed about how the Adventure API should cater to its individual users all day long and we would not get anywhere.

I ultimately believe that the builder methods aren’t even the main complaint - it’s the documentation. It’s hard to feel motivated to write good documentation. It’s not as fun as working on something new and shiny. It is grunt work, and it’s something we (especially the improvisational, thinking types like me) try to avoid.

But anyway… back to the issue. I can understand why Component.text() having overloads that return both components and component builders can be confusing. This is worsened by the fact that component builders and components can be used in almost identical ways, but components being immutable and component builders being mutable. Perhaps it’s a case of being lucky and getting the result you expected anyway, and if that’s the case I could see a potential case for changing the API.

Ultimately, what this issue boils down to is a fight between peoples’ personal tastes. APIs are always going to reflect the personal opinions of their authors. It so happens that Adventure values closely mapping how the game treats components, which is not bad but certainly convoluted (I come from the BungeeCord component API which takes a much more intuitive approach than Adventure’s 1:1 mapping of vanilla logic). That being said, I do support moving Adventure towards being more discoverable, approachable, and consistent. But I think it’s time to determine what standards should be followed before we make any changes.

10reactions
luckocommented, Dec 10, 2020

I’ve talked about this a bit internally but thought it would be best to reply here too:

I’m against this change.

Firstly, having nice/short names for these methods makes code look significantly nicer. I’ve written thousands of lines of components using the current API - trust me the extra characters add up…

I don’t agree that the current layout/names is confusing or unclear.

If we really do think it’s necessary to create more of a distinction, then I would prefer to see a ComponentBuilders class re-introduced for use in static imports, or go the other way - move the constructor methods somewhere else and leave the methods that return Builders in Component.

Using the builders is the preferred way to construct large components with children or complex properties. I am worried that by changing this, people will fallback to using Component.text(...) etc (which returns a normal, fully built component) and use the mutate methods on those instead.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Java Constructors vs Static Factory Methods - Baeldung
Learn about static factory methods in Java and why they're sometimes preferred over constructors for instantiating and initializing objects.
Read more >
How can an object be created from a static block?
Static methods belong to the class. There is just one of them and they don't need to be constructed. Instance (non-static) methods belong...
Read more >
Using static factory methods with Laravel models
The factory then deals with these different use-cases for creating an object. One lesser known type of factory pattern is the static factory...
Read more >
Exploring Joshua Bloch's Builder design pattern in Java
It allows you to produce different types and representations of a ... Builder pattern addresses these issues by creating a static inner ...
Read more >
What is a Static Method? - Definition from Techopedia
Methods are created as static when object state has no effect on their behavior because they depend only on their own parameters.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found