#Best Practices: Added functionality without inheritance

1 messages · Page 1 of 1 (latest)

wicked wave
#

So here's the problem. I'm trying to make a survival party plugin where the idea is that 3rd party plugins can be easily added. Currently I'm doing this using the following idea: https://prnt.sc/6qutzTzoxjNV

Lightshot

Captured with Lightshot

#

Basically, the core plugin holds the real instance. Then, 3rd party plugins listen for the creation of new core plugin instances

tropic marten
#

is this something that you intend to sell on spigot?

wicked wave
#

It's going on spigot yh

#

I haven't really thought about selling it

#

Depends if I can actually make it decent or not

#

Why? Am I required to sell or not sell it to ask here or?

tropic marten
#

well there is a reason I asked that. So if this was going to be a premium plugin, the core plugin wouldn't be permitted to be sold because those addons rely on it

#

so you could make the core plugin free and the addons premium

#

since the core would always be available

wicked wave
#

Even if I made the API available but not the core plugin logic?

ancient trail
#

Premium core plugins are fine

tropic marten
#

you would still need the core to be available because its unreasonable to expect people to buy the core and then also need to buy addons to make it work

ancient trail
#

The only thing not allowed is premium plugins that depend on premium plugins

wicked wave
#

So essentially, you can't make an add-on to a premium plugin?

tropic marten
wicked wave
#

P sure I've seen this be done

ancient trail
#

The only thing in the guidelines is

  • (premium) Resources must not depend on other premium resources.
tropic marten
#

oh well if the rules don't forbid it, then nvm

#

anyways, your ideo of providing functionality that isn't required but someone wanted to extend it is fine. You would just make use of a custom class loader is all

wicked wave
#

EITHER WAY, my main question's not really about selling and that

tropic marten
#

Yes back to the main question and not them side ones

wicked wave
#

It's about, what's the best way to go about adding 3rd party functionality to an object

#

Because, like, I want to use inheritance, but then I'd have to make loads of instances of the same data, since I want my plugin to work regardless of which combination of 3rd party plugins are being used

#

Like I don't want to force a specific inheritance ladder

tropic marten
#

oh its like I said, you would essentially do what spigot does

wicked wave
#

So currently I've been using wrapper (I think that's the term? Just a class that holds an instance of the core instance but also holds the extra functionality)

tropic marten
#

use a custom class loader and essentially you treat the addons as plugins but plugins within your plugin

wicked wave
#

Oh, so make them not actual plugins?

#

But just jars that the core plugin reads?

#

But I don't get how that's a better way of achieving my goal? Like I'd still be using the exact same wrapper tactic

tropic marten
#

that is what a plugin is

#

spigot just reads the jars of plugins and loads the classes lol. Those jars don't do anything on their own

#

but you can make your plugin load additional plugins specific to it though just like spigot does it

#

however, we typically call these addons

#

just to distinguish that it is specific to that one plugin

#

so its not confusing

wicked wave
#

Okay fair

#

But I still don't get how that makes adding complexity to object instances any easier

tropic marten
#

because you don't worry about instances in your core, instead you let the addons worry about. Your core just makes available information that it believes is relevant

#

and only handles core logic not really specific to anything

#

Its not easy making an API

#

but with some practice you can do it though 🙂

wicked wave
#

Are you saying not to make an instance in the core code?

#

Or just don't write it with add ons in mind?

tropic marten
#

correct, you shouldn't be doing anything specific that would require addons being present

#

so the only code that should be there is anything it can do in the absence of such

#

if you need additional functionality, that is when you make a addon to provide it

#

addons can even be utility as well

wicked wave
#

Ngl, I've been writing the plugin having some of the default code act like an add-on. This is because I want these features to be replaceable with more complex systems by add ons

#

For example

#

The core permissions system is very basic. But I want it to be replaceable with a more customiseable one

#

So I've been writing it as an addon. Like, I haven't included code for this permission system in the core code

#

Because if a server owner doesn't want to use the default system, what's the point of it being recorded and using it space when it's unused?

tropic marten
#
Core Plugin -- core api --addon
                       \
                         addon
#

this is how it needs to be structured

#

your core plugin is to implement your api otherwise it doesn't really provide much on its own lol

#

and then the addons depend on the core api, and then your core plugin should have a directory where addons get placed and it looks for jars to load

#

core plugin should provide things like events management. Registering and notifying etc

#

and then anything else that is specific to the core that the addons may need

#

then, the addons extend off that to provide further functionality

tropic marten
#

but the downside of this though, is that an addon can't make use of disabled code either

wicked wave
#

Okay, this is how I've been doing it up until now:

API class:

  String uuid;
  String getUuid()```

Add-on class:
```class MemberWrapper
  Member instance;
  String getUuid() { return instance.getUuid(); }
  ## extra functionality specific to add-on```
#

Is that the right kinda thing?

tropic marten
#

your core plugin should create instances of the addons to hold in memory since the addons don't run themselves

#

this is why spigot requires specifying a main class so it knows which class it needs to create an instance of and retain

#

and thus for plugins everything starts from there and you can do the same for your addons as well

#

you can require that plugins create/provide some kind of uuid

#

since that is the purpose of API's

wicked wave
#

Right, but I only want one member instance per real player. Surely if the add-ons create their own instances, that's not ideal?

tropic marten
#

probably not, in which case you have something like a plugin manager that would handle that

#

this is why spigot requires you to extend JavaPlugin in your main class 🙂

wicked wave
#

Yeah, but surely the alternative is to have the core plugin create an instance that the add ons can access

tropic marten
#

so that some things can be provided to the plugin via the server and api

#

also going to need to learn how to make singletons as well

#

singletons are classes where its impossible or nearly impossible to have more then one instance of

wicked wave
#

Yh, I use mostly static classes for that

tropic marten
#

You don't want to make static classes for that, because statics guarantee an object exists regardless if values are present or not. Singletons on the other hand can exist or not exist and if it doesn't exist can be created at that time

wicked wave
#

    private static final Set<Party> parties = new HashSet<>();

    /**
     * Register new party
     * @param party input party
     * @return TRUE if successful, FALSE if input invalid
     */
    public static boolean registerParty(@NotNull final Party party) {
        // Validation
        if (getPartyByName(party.getName()) != null || getPartyById(party.getId()) != null) return false;

        party.add(party);
        return true;
    }```
#

That's an example of part of the API I've got so far

tropic marten
#

this is an example of a class being a singleton

#

so, how you would stop there being more then one instance is you would make the constructor for the class private

#

and then that line I linked would invoke that private method but also checking to see if an instance doesn't already exist

#

if it does it provides that instance instead

#

if it doesn't exist, it automatically creates one

#

but yeah, basically your biggest thing with your project

#

is designing an API

#

which as I said from the beginning is not easy and takes time to learn to do properly

wicked wave
#

Yep, well that's part of the reason I'm doing it

tropic marten
#

I mean its easy in that you can create one, just not easy to have it well structured and all that fun stuff lol

#

even I don't create very good api's all the time 😛

wicked wave
#

So with your example, I don't get why you made an instance if there's no constructor

#

Like, I'd understand if it triggered some procedures upon creation (like, checking a config or something)

tropic marten
#

its an old plugin and its for internal purposes and not meant to be used directly

#

I have an api provided that doesn't involve the caching etc

#

and its main reason is to handle reloads

#

on a reload, objects get cleared so instead of ensuring all objects are all created at a given time, that method will create it automatically if its missing for whatever reason

wicked wave
#

Right

wicked wave
#

Like, it'll hold the add ons?

tropic marten
#

well, as I said there is a reason spigot requires the extending of JavaPlugin

#

JavaPlugin if you ever looked extends Plugin

#

and then in the implementation, the server provides Plugin with some things, like Config

#

which plugins don't have to worry about initializing that object or needing to do much to access it

#

getConfig specifically refers to individual plugins config.yml

#

yet the server is providing it 🙂

wicked wave
#

So are you saying that, for add on specific functions for a core object, I should just handle it in a manager or something? Rather than a wrapper I mean

#

Like getting the "Member" class instance isn't the issue

#

It's how best to add attributes and methods onto it

tropic marten
#

so adding some information to a plugins instance in the core for example would be a prime example a manager handling that

#

because you don't want the plugin/addon providing that since it can't be reliable lol

wicked wave
#

Yh but using a manager class for a thing like perms would mean mapping attributes to the main instance. Surely using OOP would be better than one fat hash map

tropic marten
#

ah, but I can show you something better

wicked wave
#

Please do :)

tropic marten
#

a perms class that doesn't use a hashmap at all

#

no hashmaps at all 🙂

#

even handles dynamic perms too

#

that is, perms it doesn't know about ahead of time

#

not overly difficult

wicked wave
#

Yeah, I know about enums. But your example is reading stuff that's already stored

#

When I said "permissions", I mean like party-specific perms. Should've clarified

tropic marten
#

as I said, that enum class even handles permissions it doesn't know about

wicked wave
#

So like, when you join a party, it starts you off with default perms

#

And then the owner can allocate them etc

tropic marten
#

yeah, you can easily have an interfaces for this, and then just extend the base one to further add onto it

#

this allows you providing some default perms in your plugin while also allowing others to extend further

#

but hashmap isn't necessary per-say, you can even create custom player objects

#

that have these perms already attached

wicked wave
#

But I'm asking about how best to achieve it. Like, using a wrapper class or whether there's some method of using inheritance for it

tropic marten
#

you could do both

#

you would need a wrapper anyways since you can't just make a player object that the server doesn't know about

#

so you would need a wrapper to take that player object and stuff it in another one

#

but at the same time you can provide inheritance too from your base wrapper implementation

wicked wave
#

Would I need to implement a wrapper in the main plugin though?

tropic marten
#

well yes, if you want a custom player object

#

the inheritance if you want to provide a structure to the addons to access said thing

#

or you can just make it globally available to do whatever with lol

wicked wave
#

Does it need to wrap the player object though?

#

Like, I've just been having the plugin hold a player object that just holds a uuid

tropic marten
#

It may be wise as that is the easiest way to provide the same information from the server into your custom player object without duplicating it yourself

wicked wave
#

So it can be mapped to player objects, but doesn't require it. That way, it can easily deal with offline players

#

I suppose

#

IDK, guess I don't like the idea of getting player objects just from their uuid

tropic marten
#

well UUID's are unique o.O

wicked wave
#

Yeah but like, offline players and online players are treated differently

tropic marten
#

not really

#

Online Player extends offlineplayer

#

which is why you are supposed to use offline player whenever possible

wicked wave
#

I see

tropic marten
#

and only use online player if you really need it

#

and its not hard to obtain a online player object from offline player

#

it has a method to check if the player is online

#

and you can cast

#

OfflinePlayer player = (OfflinePlayer) Bukkit.getPlayer(UUID);
#

this is perfectly valid and you can go the other way too

#

now obviously there is some differences between the two as an online player has more information attached to it

#

but they are not handled differnently though, even if you are using UUID's lol

wicked wave
#

Right

#

So Player represents and online player?

tropic marten
#

yes, it can't exist otherwise unless you fake the data in it, which the server doesn't and the API doesn't allow you to just arbitrarily create a Player object either

#

not without using NMS anyways

wicked wave
#

I see

tropic marten
#

but even with NMS it isn't a Player object, rather a PlayerNPC instead

#

well NPC disguised as a player lol

wicked wave
#

Okay, so is this an alright structure?

#

Wait hang on

#

I still don't get where the inheritance comes in

#

Sorry if I'm being dumb

#

I thought you meant like, the add on extends the API's object, but then you'd have to create a new instance of it for each add on

tropic marten
#

well you don't need a new instance for each addon

#

you provide the instance in such a way that they can't do whatever they want with it, but the inheritance would allow them to extend it

#

just like how spigot provides interfaces for various entities, for you to use

#

so spigot provides a player object, that only the server can create, but it provides inheritance to allow you to change that player object to other things though

#

or just provide more information

wicked wave
#

What, for like NPCs?

#

I don't get how inheritance can help you when the API has already created the object instance

#

Like, I get that you can use the interfaces to run its methods

#

But like, I don't get how you can add functionality it, only make use of the API's pre-written functionality

#

Wait, do you mean I should implement the API's interfaces to the wrapper?

tropic marten
#

API's don't work unless something implements it lol

#

and inheritance in the API doesn't work unless something implements at minimum the base interface

wicked wave
#

Well yeah, but the add on's wrapper doesn't need to go into the API

tropic marten
#

the addon shouldn't need its own wrapper

#

this is why API inheritance is a thing

#

just because you didn't provide additional interfaces

#

doesn't mean an addon can't just extend it

#

but at minimum, a base interface needs to be provided to allow that

wicked wave
#

I'm really sorry, I still don't get what can be extended

#

Do you mean the Member object?

#

I mean, this is what my code looks like currently:

#
public class Member {

    private OfflinePlayer player;

    public Member(String uuid) {
        player = Bukkit.getOfflinePlayer(UUID.fromString(uuid));
    }

    public OfflinePlayer getPlayer() { return player; }
}

public final class MemberManager() {
    private static Set<Member> members = new HashSet<>();

    public static void registerMember(Member member) { members.add(member); }
}

// ADD-ON CODE
public final class MemberWrapper {

    private Member member;
    
    private Role role = Role.DEFAULT;
    
    public MemberWrapper(Member member) { this.member = member; }
    
    public Member getMember() { return member; }
    
    public Role getRole() { return role; }
}```
#

Well, roughly. This is just a demo

tropic marten
#

public interface CorePlayer {
OfflinePlayer getOfflinePlayer();
//whatever is specific to a core player
}

public CorePlayerImpl implements CorePlayer {

private UUID playerUUID;
public CorePlayerImpl(UUID playerUUID) {
this.playerUUID = playerUUID;
}

@Override
public OfflinePlayer getOfflinePlayer() {
return Bukkit.getOfflinePlayer(playerUUID);
}
#

now, you have an interface to extend from

#

but so do the addons

#

anyways that is the premise

wicked wave
#

Okay, does the add on object implement the interface too?

#

I'd guess so, but then what's the point? Since the API never touches the add on version of the object

tropic marten
#

it wouldn't be necessary for the addon to implement the interface unless they wanted to provide their own custom object

#

they would merely need to just extend the interface

wicked wave
tropic marten
#

because you need something ensure structure

#

you don't want all your addons to be dealing with their own player objects

#

instead you would provide that from the core

#

if they do need to add more info to the object

#

they can use an interface while still being compatible with the core

#

for example LivingEntity in Spigot being able to be casted to Player

#

or vice versa

#

LivingEntity isn't necessarily implemented specifically, rather it derives from the base Entity

#

Entity -> LivingEntity -> Player

wicked wave
#

Yeah, so for an add on that does need to add more info

#

It should implement CorePlayer interface

#

?

#

The add on shouldn't be creating any instance that the core sees

#

There's not really any point in it IMO

#

So I don't get why it needs to be compatible

#

In your spigot example, the Player class is used because it's specifically a player that's being created

#

But in mine, the core creates a Member object. There is no other type of Member

#

Instead, the add ons just want to add attributes and methods to all Member instances that the core creates