如何在Java中避免缺乏多重继承

时间:2014-01-11 22:50:20

标签: java oop inheritance multiple-inheritance

我正在制作游戏。我有一个实体类,它是一对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();”这是不可接受的。

如果您建议使用适配器模式,我希望您发布一个示例,说明适配器模式如何做这样的事情(我已经讨论过它了。)

如果您建议撰写,请说明我将如何访问受保护的字段。

谢谢!

4 个答案:

答案 0 :(得分:1)

罗马在评论中说:

Java确实允许interfaces的多重继承。如果您只需要继承is-a关系和一组有保证的方法来与对象进行通信,那么这种方法非常有效。

只能继承一个class(抽象与否)(无论需要多少接口)。这是为了避免“钻石继承”的复杂性 - 网络搜索这句话要多学习。如果确实需要重用多个源中的逻辑,通常的解决方法是在实用程序类中定义其他共享行为,并让您的类委托给它的实例。是的,这是一个has-a而不是is-a关系,但是因为is-a可以被抽象到一个界面中,你可以获得所需的最终组合行为。 (与钻石继承不同,这会迫使您明确决定要调用哪个实现。)

答案 1 :(得分:1)

通过合同,您将被迫实现接口中定义的方法。但是,你可以在这些方法中做任何你喜欢的事情。在这个简单的示例中,为每个实例创建了MoveableRotatable的进一步实现类。对相应动作的请求被转发到实现。

这样您就可以与多个对象共享相同的实现。行动本身甚至能够管理其状态而不是委托类。

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 操作现在变成了数据处理问题而不是层次结构问题。