Search code examples
javainheritancesubclassprivatesuperclass

Accessing instance of a package-private subclass as its public superclass


I'm writing a multiplayer game engine where I've mapped the traditional MVC pattern into Game/Client/Server (Game is the "model" that the other parts act on and communicate through. Client has its own MVC structure internally.)

I'm doing my best to make sure that the Client and Server packages never cross paths except when communicating through common abstractions. Similarly, the Game model never depends on classes from Client or Server.

However I'm running into an issue:

  • The Server is obviously privy to a ton of information the clients don't need. For example, whereas clients need to know about other Player objects that are also connected (with info like "nickname" and "ping"), they certainly don't need to know the other Players' IP addresses, connection time, account info, etc.
  • This has lead to me writing package-specific classes that represent the same info. For example I have a Player object for all connected users in the Game model, but there's a corresponding User object that stores additional info in the Server package. There's a HashMap in both packages that store the corresponding objects, bound with the same IDs.
  • (There are a bunch of instances of the same pattern in my project. Some info sits in the Game model, and in the Server model there's a corresponding object with additional info that the server needs.)
  • This is less than ideal, because I now need to make sure that changes (such as a Player disconnecting) are reflected in more than one area.

So the ideal solution (for the Player/User issue in particular) would be like this:

  • There's a Player class in the Game package that has only the fields and methods Game and Client needs.
  • There's a subclass of this called User declared privately in the Server package. This class has methods and fields for the connection and authentication.
  • There's a HashMap in the Game model that stores Players, mapped by (int) ID.
  • If the application is run as a listen or dedicated server, this HashMap of would actually get filled with User objects.

My question, then, is can the Game and Client packages access these objects as a public superclass (Player) even though in reality it's an instance of a subclass belonging to another package? From the Game and Client's perspective, they only need to access the superclass methods/fields.

(Also, obviously, if the application is run as a client the server will only be sending it info to make the Player object. Users will only get stored on dedicated/listen instances.)

I'd test this but my game is far from compiling (going through a refactoring nightmare at the moment, newbie mistakes need to be cleaned up) and I'm not sure if the IDE would detect any runtime conflict.


Solution

  • I agree that you can just flesh this out really fast in a new project, but I was curious how exactly it would work out, so I did it anyway:

    A Player class:

    package sandbox.pakcage1;
    
    public class Player {
        public Player(String name) {
            this.name = name;
        }
    
        private String name;
    
        public String getName() {
            return name;
        }
    }
    

    A User class:

    package sandbox.package2;
    
    import sandbox.pakcage1.Player;
    
    class User extends Player {
        private String userID;
    
        public User(String name, String id) {
            super(name);
            this.userID = id;
        }
    
        public String getUserID() {
            return userID;
        }
    }
    

    A class to generate some users:

    package sandbox.package2;
    
    import java.util.ArrayList;
    import java.util.List;
    import sandbox.pakcage1.Player;
    
    public class UserBuilder {
        public static List<Player> getUsers() {
            List<Player> users = new ArrayList<>();
    
            users.add(new User("Joe", "1"));
            users.add(new User("Bob", "2"));
    
            return users;
        }
    }
    

    And a main to tie it all together:

    package sandbox;
    
    import java.util.List;
    import sandbox.package2.UserBuilder;
    import sandbox.pakcage1.Player;
    
    public class Sandbox {
        public static void main(String[] args) {
            List<Player> players = UserBuilder.getUsers();
    
            for(Player player : players) {
                System.out.println(player.getName());
            }
        }
    }
    

    You'll find that the main functions perfectly, accessing the Player data members from the User objects just fine. You can't cast them to User objects though, or in any way access the User data - since that class is package protected in a different package.