我正在制作游戏。我有一个实体类,它是一对x,y坐标,宽度和高度。
我想将此Entity类子类化为相当多的新类,以添加功能组的组合。我想添加几个“组”功能:
Moveable {
public float getVelocityX();
public void setVelocityX(float velocityX);
public float getVelocityY();
public void setVelocityY(float velocityY);
public void moveRelative(float dx, float dy);
public void setPosX(float posX);
}
和
Rotatable {
public void setAngularVelocity(float degreesPerSecond);
public void setAbsoluteAngle(float angle);
}
和
Resizable {
void setAbsoluteSize(int width, int height);
void setRelativeSize(int widthGrowFactor, int heightGrowFactor);
public void setWidth(float width);
public void setHeight(float height);
}
......等等(可更新,可绘制,可碰撞)。
某些实体可以旋转/绘制但不能移动/碰撞/更新/调整大小:“静态”不可触摸的东西,例如背景动画(云,太阳,星星)。
某些实体可以移动/旋转/被绘制/更新/碰撞/调整大小:玩家角色和怪物。
只能绘制一些实体:游戏的HUD。
某些实体可以移动/被绘制/更新/碰撞但不能旋转/调整大小:例如枪支射击。
正如您所看到的,我需要上述功能的组合,并且可以通过一些多重继承轻松解决。
为什么不用你问的接口呢?因为这会让我复制/粘贴所有方法的大量代码,并且创建新类型的Entity类将永远引入copy / pasta。
您可能会问为什么我不会只创建一个可以完成所有操作的子类,然后覆盖那些不应该使用空方法的方法,或者在那些无效的方法中抛出UnsupportedOperationException?这仍然是复制/粘贴,即使它在每个方法中都是一行。只有move()
的实体最终可能会有50个方法“throw new UnsupportedOperationException();”这是不可接受的。
如果您建议使用适配器模式,我希望您发布一个示例,说明适配器模式如何做这样的事情(我已经讨论过它了。)
如果您建议撰写,请说明我将如何访问受保护的字段。
谢谢!
答案 0 :(得分:1)
Java确实允许interfaces
的多重继承。如果您只需要继承is-a关系和一组有保证的方法来与对象进行通信,那么这种方法非常有效。
只能继承一个class
(抽象与否)(无论需要多少接口)。这是为了避免“钻石继承”的复杂性 - 网络搜索这句话要多学习。如果确实需要重用多个源中的逻辑,通常的解决方法是在实用程序类中定义其他共享行为,并让您的类委托给它的实例。是的,这是一个has-a而不是is-a关系,但是因为is-a可以被抽象到一个界面中,你可以获得所需的最终组合行为。 (与钻石继承不同,这会迫使您明确决定要调用哪个实现。)
答案 1 :(得分:1)
通过合同,您将被迫实现接口中定义的方法。但是,你可以在这些方法中做任何你喜欢的事情。在这个简单的示例中,为每个实例创建了Moveable
和Rotatable
的进一步实现类。对相应动作的请求被转发到实现。
这样您就可以与多个对象共享相同的实现。行动本身甚至能够管理其状态而不是委托类。
public SomeClass implements Moveable, Rotatable
{
// the implementation provide a concrete implementation
// you can even exchange the implementation with new ones
private Moveable movaAction = new MoveImpl();
private Rotatable rotateAction = new RotateImpl();
public float getVelocityX()
{
return moveAction.getVelocityX();
}
public void setVelocityX(float velocityX)
{
moveAction.getVelocityX(velocityX);
}
...
public void setAngularVelocity(float degreesPerSecond)
{
rotateAction.setAngularVelocity(degreesPerSecond);
}
...
}
如果你进一步定义f.e. a registerNewMove(Moveable action)
如果需要,你甚至可以在运行时交换具体的实现(策略模式)。
答案 2 :(得分:0)
如果所有子类的代码相同,那么它可以在超类中。你可以这样做:
public final move() {
if (!isMovable()) {
throw new UnsupportedOperationException("This entity can't be moved");
}
// TODO implement the actual move
}
public abstract boolean isMovable();
如果你想让接口告诉实体是否可以移动,你甚至可以在超类中实现isMovable()方法:
public final boolean isMovable() {
return this instanceof Movable;
}
答案 3 :(得分:0)
或许从完全不同的角度看待你的问题会很有用。您是否考虑过,不是将功能构建到对象的层次结构中,而是将功能构建到对象的初始化中。
首先尝试这样的事情:
static class Thing {
int x;
int y;
float w;
float h;
float angle;
}
enum Op {
Translate{
@Override
void op (Thing it, Thing op) {
it.x += op.x;
it.y += op.y;
}
},
Rotate{
@Override
void op (Thing it, Thing op) {
it.angle += op.angle;
}
},
Scale{
@Override
void op (Thing it, Thing op) {
it.w *= op.w;
it.h *= op.h;
}
};
abstract void op(Thing it, Thing op);
}
现在您有一个可以操作的基类Thing
。构建 legal 操作现在变成了数据处理问题而不是层次结构问题。