在Java 8中使用多重继承

ski*_*iwi 26 java oop inheritance multiple-inheritance java-8

使用 Java 8的功能还是滥用它?

请参阅下面的代码和说明,了解为什么选择它是这样的.

public interface Drawable {
    public void compileProgram();

    public Program getProgram();

    default public boolean isTessellated() {
        return false;
    }

    default public boolean isInstanced() {
        return false;
    }

    default public int getInstancesCount() {
        return 0;
    }

    public int getDataSize();

    public FloatBuffer putData(final FloatBuffer dataBuffer);

    public int getDataMode();

    public boolean isShadowReceiver();

    public boolean isShadowCaster();    //TODO use for AABB calculations

    default public void drawDepthPass(final int offset, final Program depthNormalProgram, final Program depthTessellationProgram) {
        Program depthProgram = (isTessellated()) ? depthTessellationProgram : depthNormalProgram;
        if (isInstanced()) {
            depthProgram.use().drawArraysInstanced(getDataMode(), offset, getDataSize(), getInstancesCount());
        }
        else {
            depthProgram.use().drawArrays(getDataMode(), offset, getDataSize());
        }
    }

    default public void draw(final int offset) {
        if (isInstanced()) {
            getProgram().use().drawArraysInstanced(getDataMode(), offset, getDataSize(), getInstancesCount());
        }
        else {
            getProgram().use().drawArrays(getDataMode(), offset, getDataSize());
        }
    }

    default public void delete() {
        getProgram().delete();
    }

    public static int countDataSize(final Collection<Drawable> drawables) {
        return drawables.stream()
                .mapToInt(Drawable::getDataSize)
                .sum();
    }

    public static FloatBuffer putAllData(final List<Drawable> drawables) {
        FloatBuffer dataBuffer = BufferUtils.createFloatBuffer(countDataSize(drawables) * 3);
        drawables.stream().forEachOrdered(drawable -> drawable.putData(dataBuffer));
        return (FloatBuffer)dataBuffer.clear();
    }

    public static void drawAllDepthPass(final List<Drawable> drawables, final Program depthNormalProgram, final Program depthTessellationProgram) {
        int offset = 0;
        for (Drawable drawable : drawables) {
            if (drawable.isShadowReceiver()) {
                drawable.drawDepthPass(offset, depthNormalProgram, depthTessellationProgram);
            }
            offset += drawable.getDataSize();   //TODO count offset only if not shadow receiver?
        }
    }

    public static void drawAll(final List<Drawable> drawables) {
        int offset = 0;
        for (Drawable drawable : drawables) {
            drawable.draw(offset);
            offset += drawable.getDataSize();
        }
    }

    public static void deleteAll(final List<Drawable> drawables) {
        drawables.stream().forEach(Drawable::delete);
    }
}
Run Code Online (Sandbox Code Playgroud)
public interface TessellatedDrawable extends Drawable {
    @Override
    default public boolean isTessellated() {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)
public interface InstancedDrawable extends Drawable {
    @Override
    default public boolean isInstanced() {
        return true;
    }

    @Override
    public int getInstancesCount();
}
Run Code Online (Sandbox Code Playgroud)
public class Box implements TessellatedDrawable, InstancedDrawable {
    //<editor-fold defaultstate="collapsed" desc="keep-imports">
    static {
        int KEEP_LWJGL_IMPORTS = GL_2_BYTES | GL_ALIASED_LINE_WIDTH_RANGE | GL_ACTIVE_TEXTURE | GL_BLEND_COLOR | GL_ARRAY_BUFFER | GL_ACTIVE_ATTRIBUTE_MAX_LENGTH | GL_COMPRESSED_SLUMINANCE | GL_ALPHA_INTEGER | GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH | GL_ALREADY_SIGNALED | GL_ANY_SAMPLES_PASSED | GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH | GL_ACTIVE_PROGRAM | GL_ACTIVE_ATOMIC_COUNTER_BUFFERS | GL_ACTIVE_RESOURCES | GL_BUFFER_IMMUTABLE_STORAGE;
        int KEEP_OWN_IMPORTS = UNIFORM_PROJECTION_MATRIX.getLocation() | VS_POSITION.getLocation();
    }
//</editor-fold>
    private FloatBuffer data;
    private Program program;

    private final float width, height, depth;

    public Box(final float width, final float height, final float depth) {
        this.width = width;
        this.height = height;
        this.depth = depth;
        data = generateBox();
        data.clear();
    }

    @Override
    public void compileProgram() {
        program = new Program(
                new VertexShader("data/shaders/box.vs.glsl").compile(),
                new FragmentShader("data/shaders/box.fs.glsl").compile()
        ).compile().usingUniforms(
                        UNIFORM_MODEL_MATRIX,
                        UNIFORM_VIEW_MATRIX,
                        UNIFORM_PROJECTION_MATRIX,
                        UNIFORM_SHADOW_MATRIX
                        );
    }

    @Override
    public int getInstancesCount() {
        return 100;
    }

    @Override
    public Program getProgram() {
        return program;
    }

    @Override
    public int getDataSize() {
        return 6 * 6;
    }

    @Override
    public FloatBuffer putData(final FloatBuffer dataBuffer) {
        FloatBuffer returnData = dataBuffer.put(data);
        data.clear();   //clear to reset data state
        return returnData;
    }

    @Override
    public int getDataMode() {
        return GL_TRIANGLES;
    }

    @Override
    public boolean isShadowReceiver() {
        return true;
    }

    @Override
    public boolean isShadowCaster() {
        return true;
    }

    private FloatBuffer generateBox() {
        FloatBuffer boxData = BufferUtils.createFloatBuffer(6 * 6 * 3);

        //put data into boxData

        return (FloatBuffer)boxData.clear();
    }
}
Run Code Online (Sandbox Code Playgroud)

首先介绍我如何使用此代码:

  1. 我开始与Drawable接口,并具有自己的每一个实现drawDepthPass,drawdelete方法.

  2. 重构delete一个default方法很容易,琐碎,不应该是错误的.

  3. 但是为了能够重构drawDepthPass并且draw我需要访问是否有一个Drawable被测试和/或实例化,所以我添加了公共(非默认)方法isTessellated(),isInstanced()getInstancesCount().

  4. 然后我发现它会稍微麻烦,因为我们的程序员很懒,要在每个程序中实现它们Drawable.

  5. 因此,我添加了default方法Drawable,给出了最基本的行为Drawable.

  6. 然后我想我仍然很懒,并且不想为tessellated和instanced变体手动实现它.

  7. 所以我创建TessellatedDrawableInstancedDrawable提供default isTessellated()isInstanced()分别.并在InstancedDrawable我撤销了default执行getInstancesCount().

因此,我可以拥有以下内容:

  • 正常Drawable:public class A implements Drawable
  • 细分Drawable:public class A implements TessellatedDrawable
  • 实例Drawable:public class A implements InstancedDrawable
  • 细分和实例Drawable:public class A implements InstancedDrawable, TessellatedDrawable.

为了确保您,这一切都可以编译并且运行良好,implements InstancedDrawable, TessellatedDrawableJava 8可以完美地处理这些问题,因为功能应该来自哪个接口.

现在进行我自己的小OOP设计评估:

  • Drawable事实上每一个都是一个Drawable,所以Collection<Drawable>不会破坏.
  • 可以对所有TessellatedDrawable和/或InstancedDrawable与其实现方式无关进行分组.

我有其他想法:

  • 使用更传统的分层方法,但我忽略了它最终会:

  • abstract class AbstractDrawable

  • class Drawable extends AbstractDrawable
  • class TessellatedDrawable extends AbstractDrawable
  • class InstancedDrawable extends AbstractDrawable
  • class InstancedTessellatedDrawable extends AbstractDrawable

我也考虑过一个Builder模式,但是当你创建一个特定对象的很多独特实例时,这是一个模式,这不是我们在这里做的,也不是关于对象的构造函数.

所以第一个也是最后一个问题是:我使用 Java 8的功能还是滥用它?

Stu*_*rks 11

首先,如果它有效,并且它做你想做的事情,并且将来不会有任何破坏的危险,那么说你滥用它是没有意义的.毕竟,它完成了工作,对吧?默认方法和静态方法等功能被添加到具有特定目标的接口中,但是如果它们帮助您实现其他目标,则可以是创造性地使用新功能,也可以是粗暴和肮脏的黑客攻击.:-)在某种程度上,这是一个品味问题.

考虑到这一点,我在API中寻找的内容,以及我在设计API时尝试做的是区分API的客户端和API的实现者.API的典型客户端或用户从某个地方获取某种接口类型的引用,并在其上调用方法以使事情发生.实现者提供接口中定义的方法的实现,覆盖方法和(如果子类化)调用超类方法.通常,客户端调用的方法与从子类调用的方法不同.

在我看来,这些概念在Drawable界面中混合在一起.当然,一个Drawable意志的客户会做一些事情,比如在他们身上调用draw或者drawDepthPass方法.大.但是查看默认实现drawDepthPass,它会使用isTessellated和获取一些信息isInstanced,然后使用这些信息以特定的方式选择一个程序并在其上调用方法.将这些逻辑位封装在方法中是很好的,但为了在默认方法中完成,必须将getter强制进入公共接口.

当然,我对你的模型可能是错的,但在我看来,这种逻辑更适合抽象的超类和子类关系.抽象超类实现了一些处理所有Drawable的逻辑,但它使用类似isTesselated或的方法与特定的Drawable实现进行协商isInstanced.在抽象超类中,这些是受保护的方法,需要子类来实现.通过将此逻辑放入接口的默认方法中,所有这些都必须是公共的,这会使客户端接口变得混乱.这似乎类似于其他的方法是getDataMode,isShadowReceiverisShadowCaster.是否期望客户打电话给这些客户,或者他们是否在逻辑上内部实施?

这突出表明,尽管添加了默认方法和静态方法,但接口仍然面向客户端,而不是支持子类.原因如下:

  • 接口只有公共成员.
  • 抽象类可以有子类的覆盖或调用的受保护方法.
  • 抽象类可以使用私有方法来实现实现共享.
  • 抽象类可以具有可以被保护以与子类共享状态的字段(状态),或者通常是私有的.
  • 抽象类可以具有在子类上强制执行某些行为策略的最终方法.

我在Drawable接口系列中注意到的另一个问题是它使用默认方法的能力来相互覆盖,以允许一些简单的混合到实现类中Box.你可以说implements TessellatedDrawable并且避免令人讨厌的isTesselated方法覆盖它是一种整洁!问题是,这现在成为实现类类型的一部分.客户知道a Box也是一个有用的TessellatedDrawable吗?或者这只是一个使内部实施更清洁的方案?如果是后者,那么这些mixin接口可能更喜欢TessellatedDrawable并且InstancedDrawable不是公共接口(即,包私有).

另请注意,此方法会混淆类型层次结构,这会使代码更难以导航.通常一个新类型是一个新概念,但它似乎是重量级的接口只定义返回布尔常量的默认方法.

这一点的另一点.同样,我不知道你的模型,但这里混合的特征非常简单:它们只是布尔常量.如果有一个Drawable实现,例如,开始没有实例化,以后可以成为实例,它就不能使用这些mixin接口.默认实现在他们可以做的事情上非常受限制.它们不能调用私有方法或检查实现类的字段,因此它们的使用非常有限.以这种方式使用接口几乎就像使用它们作为标记接口一样,只需要调用一个方法来获取特性,而不是使用它instanceof.除此之外似乎没什么用处.

Drawable界面中的静态方法似乎最合理.它们是看似面向客户端的实用程序,它们提供公共实例方法提供的合理逻辑集合.

最后,关于模型有一些看似奇怪的观点,尽管它们与默认和静态方法的使用没有直接关系.

这似乎是一个Drawable有-A Program,因为有实例方法compileProgram,getProgramdelete.然而drawDepthPass,类似的方法要求客户端传入两个程序,其中一个程序是根据布尔getter的结果选择的.我不清楚调用者应该选择正确的程序.

drawAll方法和offset价值正在发生类似的事情.看起来像在Drawables列表中,它们必须使用基于每个Drawable的数据大小的特定偏移来绘制.然而,显然最基本的方法是draw,要求调用者传入偏移量.这似乎是推动呼叫者的一大责任.所以也许偏移的东西也真的属于实现.

有几种方法可以获取一个drawables和调用List stream()然后forEach()或者forEachOrdered().这是没有必要的,因为它上面List有一个forEach继承自的方法Iterable.

我认为探索如何使用这些新东西真是太棒了.这是一个新的,以至于尚未出现一种普遍接受的风格.像这样的实验和这个讨论有助于发展这种风格.另一方面,我们还需要注意不要使用这些闪亮的新功能,因为它们是新的和有光泽的.

  • 很好的答案!这将是我们在[Code Review](http://www.codereview.stackexchange.com/help/on-topic)上给出的答案,如果您喜欢写这个答案,请随时加入我们:) (5认同)