Java 使用新类向现有类添加功能,同时仍公开前一个类的所有方法



我想做一些类似super()的事情,但不向构造函数传递任何东西。
类似于复制当前状态的所有变量,这些变量设置为该类中的方法。
当包装类是播放器时,我想避免在包装类中执行诸如getPlayer()之类的操作,只是具有额外的方法。
我不想将这些额外的方法移动到 Player 类中,因为它不是 Player 类的一部分,它是 Player 类可能会进入的东西,但并非始终如此。

我仍然希望能够使用它必须设置的自己的变量运行 SpecialPlayer 的构造函数,同时仍然从我传入的任何实例中将其维护为播放器。

Player类变量在强制转换为SpecialPlayer类时可能随时更改,并且SpecialPlayer类应始终具有与Player相同的最新变量。是的,它的外观必须始终完全相同的引用。



这是我写的一些伪代码来试图说明这个问题

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.

我想你对继承的工作原理有点困惑

例如,下面这个略有更改的代码版本

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;
}
}

使用这种结构,例如做以下事情是合法

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

将玩家包装在SpecialPlayer中是没有意义的,如果你想让SpecialPlayer成为一个玩家。

更新:如何将现有播放器中的数据分配到特殊播放器中

把一个类想象成一个记忆空间。例如

Object SpecialPlayer
[X] money
[X] bar
[X] foo
Object Player
[X] money
[X] bar
[ ] foo

所以很容易看出你可以做一些类似的事情

Player p = new SpecialPlayer()
因为 P 必须只知道来自"money"和"bar">

的数据,尽管 "foo" 仍然存在,但从 p 的角度来看,这是无关紧要的,因为 P 只知道"money"和"bar">

当然,相反的情况并非如此,因为

SpecialPlayer sp = p

将强制解释器解决为 p 分配字段"foo"的问题。这里有两种情况

  1. p实际上是一个SpecialPlayer,所以你可以像sp = (SpecialPlayer)p一样键入它。解释器只会意识到 p 最初是作为 SpecialPlayer 创建的,然后,在某处有一个"foo"。
  2. p不是特殊玩家,而是纯粹的玩家。在这种情况下,解释器无法猜测"foo"必须具有什么值。在java中,没有办法让SpecialPlayer使用与Player相同的字段进行初始化,并假设例如"foo"为空。在这种情况下,您必须手动执行此分配,创建一个方法,例如

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

当然,这可能是一个静态方法,它返回一个 SpecialPlayer,或者按照你的计划返回构造函数。但问题是一样的。如何将"foo"分配给SpecialPlayer。java 解释器只是"宁愿"不将其假定为 null(或任何其他默认值),而是让开发人员实现它。

更新#2 - 这里有一个好问题

为什么不能(或不应该)将超类分配给子类,这样我就可以复制我需要的属性并假设其余的属性为 null?

例如

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

意义

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

好吧,在Java中,当你将一个对象分配给另一个对象时,你实际上并没有将值从一个复制到另一个。您只是在创建一个链接。这就是您可以分配的原因

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

而不会丢失任何信息。

如果我们复制这些值,上面的代码只会删除最后一行中"foo"的值,因为Player没有地方放置"foo">

这样好吗?或。想象一下,如果将一个对象分配给另一个对象,我们正在执行所有值属性的"深层复制"之类的操作,会发生什么。

想象一下典型的持久实体,它们是许多倍复杂的对象图,有时非常深,有时带有循环。你会有很多内存问题。

如果您希望能够在运行时向现有类添加功能,那么您应该研究动态代理和/或面向方面的编程 (AOP)。

有关创建实现给定接口但可以根据需要传递调用的 Proxy 对象的信息,请参阅 http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html。

或者,您需要重新评估您的班级模型。 具体来说,如果玩家有时可以成为特殊玩家,那么SpecialPlayer与玩家并没有真正的"是"关系。 玩家更有可能与某些可能可用或可能不可用的功能存在"有"关系。 例如,如果您通过获得某种道具而成为特殊玩家,那么这应该由播放器上的某种属性或库存功能来表示,这些功能可能会返回 null。 不幸的是,你的例子太抽象了,无法给你任何关于重构最佳行动方案的具体建议。

另外,setBar()应该被称为toggleBar().

我认为这可能是你想做的。

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

specialPlayer将把玩家的钱和酒吧传递给它。

或者,如果您想将每个字段设置为与传递Player相同,您可以尝试以下操作:

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));
}
}

这会将新SpecialPlayer的所有字段(在Player中声明)设置为传递player中该字段的值。

为了能够更改原始Player的状态,并在更新需要时将其反映在新SpecialPlayer中,您可以将对原始Player的所有引用更改为指向新SpecialPlayer

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

另一件需要考虑的事情是,您真的想制作一个与旧Player相同的新SpecialPlayer,还是您实际上只是想让旧Player变得特别?将SpecialPlayer构造函数设为这样可能更容易:

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

然后,对于所有可能后来变得特别的玩家,您将创建如下:

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

然后当你希望他们特别时,把它们投SpecialPlayer

SpecialPlayer special = (SpecialPlayer)player1;

这样,当您使播放器变得特别时,您无需更改引用或创建新对象。

您还可以在SpecialPlayer中添加一个方法,该方法执行构造函数当前执行的操作,并在您想要使该播放器特别时调用它:

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

.

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

您可能还需要一个标志,当玩家被setSpecial()时可以切换该标志,以区分他们是否应该使用覆盖的方法或恢复到超级方法。

好的,

我知道扩展关键字应该使 SpecialPlayer 成为播放器,但是如何将我的播放器实例传递到 SpecialPlayer 中,我无法使用播放器的构造函数,因为它已经在使用新播放器之前构建了(1234);

你不能扩展一个Player的现有实例来使其成为SpecialPlayer,它只是不能那样工作。

但是,您可以克隆Player这是一个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);

相关内容

最新更新