Java adding functionality to an existing class with a new class while still exposing all methods of previous class

StackOverflow https://stackoverflow.com/questions/21948738

Question

I want to do something like a super() but without passing anything to the constructor.
Something like copying all the variables in their current states, which were set to methods inside that class.
I want to avoid doing stuff like getPlayer() in a wrapper class when the wrapper class is the Player just with extra methods.
I don't want to move these extra methods into the Player class because it's not part of the Player class it's something a Player class may become into but not at all times.

I still want to be able to run constructor of the SpecialPlayer with it's own variables it has to set while still maintaining it as a Player from whatever instance I pass into it.

The Player class variables may change at any time while it's casted into a SpecialPlayer class and the SpecialPlayer class should have up to date variables same as Player at all times.. Yes it has to the same reference exactly at all times by the looks of it.



Here is some pseudo code I wrote to try to illustrate the problem

public class Player {
    private int money;
    private boolean bar;

    public Player(int money) {
        this.money = money;
        bar = true;
    }

    public void toggleBar() {
        bar != bar;
    }
}

public class SpecialPlayer extends Player {
    private Player player;
    private long foo;

    public SpecialPlayer(Player player) {
    this.player = player; //wrapper way not good...
    //this = player; ??? doesn't compile...
    foo = System.currentTimeMillis();
    }

    public long getFoo() {
        return foo;
    }

    public Player getPlayer() { //<-- this is stupid.
        return player;
    }
}


Player player = new Player(12345);
player.toggleBar(); //it's now false, also it's not a constructor variable.

SpecialPlayer specialPlayer = new SpecialPlayer(player);
specialPlayer.getFoo(); //some long number value.
specialPlayer.toggleBar(); //now it should be true!.
player.toggleBar(); //should be false.
specialPlayer.toggleBar(); //should be true.
Was it helpful?

Solution 3

I think this might be what you want to do.

public SpecialPlayer(Player player) {
    super(player.money);
    bar=player.bar;
    foo = System.currentTimeMillis();
}

A new specialPlayer will have the money and bar of the player passed to it.

Alternatively, if you want to set every field to the same as the Player passed you could try this:

public SpecialPlayer(Player player) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
    super(player.money); //Still need to do this
    for(Field sfield:player.getClass().getDeclaredFields();){
        sfield.setAccessible(true);
        sfield.set(this, sfield.get(player));
    }
}

This will set all of the fields of the new SpecialPlayer which are declared in Player to the value of that field in the player passed.

To be able to change the state of the original Player and have it reflected in the new SpecialPlayer as your update requires you could change all of your references to the original Player to point to the new SpecialPlayer.

Player player= new Player(123);
SpecialPlayer special = new SpecialPlayer(player);
player=special;

Another thing to think about, do you really want to make a new SpecialPlayer with all the same fields as an old Player, or do you actually just want to make the old Player special? It might be easier to make the SpecialPlayer constructor this:

public SpecialPlayer(int money){
    super(money);
}

Then for all your players who might later become special you would create like this:

Player player1 = new SpecialPlayer(123);
Player player2 = new SpecialPlayer(345);

And then just cast them to SpecialPlayer when you want them to be special.

SpecialPlayer special = (SpecialPlayer)player1;

This way you don't need to change your references or create new objects when you make your player special.

You could also add a method in SpecialPlayer which does what the constructor currently does and call it whenever you want to make that player special:

public void setSpecial(){
    foo = System.currentTimeMillis();
}  

.

SpecialPlayer special = (SpecialPlayer)player1;
special.setSpecial();

You may also need a flag which could be toggled when a player is setSpecial() to distinguish if they should use an overridden method or revert to the super method.

OTHER TIPS

I think you're a little confused about how inheritance works

for example, this slightly changed version of your code below

class Player {
    private int     money;
    private boolean bar;

    public Player() {} // I've added this guy, so your subclass is not forced to implement a similar constructor as Player(int money)

    public Player(int money) {
        this.money = money;
        bar = true;
    }

    public void setBar() {
    }
}

class SpecialPlayer extends Player {
    private long    foo;

    public long getFoo() {
        return foo;
    }
}

with this structure, it's legal for example to do things like

SpecialPlayer sp = new SpecialPlayer();
sp.getFoo(); //method specific to special player
sp.setBar(); //method inherited from Player

there's no sense on wrapping a Player inside SpecialPlayer, if you want SpecialPlayer to be a Player.

UPDATE: how to assign the data from an existent Player into a SpecialPlayer

Think on a class like a memory space. For example

Object SpecialPlayer

[X] money
[X] bar
[X] foo

Object Player

[X] money
[X] bar
[ ] foo

So it's easy to see that you can do something like

Player p = new SpecialPlayer()

because p must know only the data from "money" and "bar" and, despite "foo" is still there, from p point of view, it's irrelevant, because all p knows is "money" and "bar"

the opposite, sure, is not true, because

SpecialPlayer sp = p

will force the interpreter to resolve the problem of assigning the field "foo" for p. There are two situations here

  1. p is actually a SpecialPlayer, so you can just typecast it like sp = (SpecialPlayer)p. The interpreter will just realize that p was initially created as a SpecialPlayer and then, there's a "foo" somewhere.
  2. p is not a SpecialPlayer, but a pure Player. In this case, there's no way for the interpreter to guess what value "foo" must have. In java, there's no way to just let SpecialPlayer be initialized with the same fields it has from Player and assume for example that "foo" is null. In this case, you have to manually do this assignment, creating a method such as

    public void valueOf(Player p){
        this.money = p.money;
        this.bar = p.bar;           
    }
    

Of course, this could be a static method that returns a SpecialPlayer, or the constructor as you've planned. But the problem is the same. How to assign "foo" to SpecialPlayer. The java interpreter simply "prefer" not to assume it as null (or any other default value) and let the developer implement it.

UPDATE #2 - there's a good question here

why can't (or shouldn't) I assign a superclass into a subclass, so I can just copy the attributes that I need and assuming the remaining ones are null?

for example

 Player p = new Player()
 p.setMoney(1)
 p.setBar(bar)

 SpecialPlayer sp = p

meaning

 sp.setMoney(1)
 sp.setBar(bar)
 sb.setFoo(null)

?

well, in java, when you assign an object into another, you're not actually copying the values from one into another. You're just creating a link. That's why you can assign

 SpecialPlayer sp = new SpecialPlayer()
 //set SpecialPlayer attributes
 Player p = sp
 SpecialPlayer sp2 = (SpecialPlayer)p;

without losing any information.

If we were copying the values instead, the code above would just erase the value of "foo" in the last line, because Player had no place to put "foo"

Is that good? Maybe. Imagine what would happen if, assigning one object into another, we were performing something like a "deep copy" of all the value attributes.

Imagine typical persistent entities, that are many times complex graphs of objects, sometimes very deep, sometimes with cycles. You would have a lot of memory problems with that.

If you want to be able to, at runtime, add functionality to an existing class, then you should be looking into dynamic proxies and/or aspect oriented programming (AOP).

See http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html for information about creating Proxy objects that implement a given interface but can pass-through calls as desired.

Alternatively, you need to re-evaluate your class model. Specifically, if a Player can become a SpecialPlayer sometimes, then SpecialPlayer doesn't really have an 'is-a' relationship with Player. More likely Player has a 'has-a' relationship with some functionality that might or might not be available. For instance, if you become a SpecialPlayer by picking up some sort of powerup, then that should be expressed by some kind of attribute or inventory functionality on Player that might return null. Unfortunately your example is too abstract to give you any concrete advice on the best course of action in refactoring.

Also, setBar() should be called toggleBar().

Okay I know the extends keyword should make the SpecialPlayer a Player but how do I pass my instance of a Player into the SpecialPlayer I can't use the Player's constructor because it's already constructed before hand using new Player(1234);

You can't extend an existing instance of a Player to make it a SpecialPlayer, it just doesn't work like that.

You can however make a clone of a Player that's a SpecialPlayer

class Player
{
    public Player(Player other)
    {
        //Copy all instance variables from other into this here
    }
    //Other constructors/instance variables/etc here
}

class SpecialPlayer extends Player
{
    public SpecialPlayer(Player other)
    {
        super(other);
        //Set SpecialPlayer-specific instance variables here
    }
}

//Example of how to create a SpecialPlayer clone of a Player:
Player myPlayer = new Player();
SpecialPlayer specialPlayer = new SpecialPlayer(myPlayer);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top