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

public interface TessellatedDrawable extends Drawable {
    @Override
    default public boolean isTessellated() {
        return true;
    }
}

public interface InstancedDrawable extends Drawable {
    @Override
    default public boolean isInstanced() {
        return true;
    }

    @Override
    public int getInstancesCount();
}

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

首先,介绍如何编写此代码的步骤:

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

  2. 重构deletedefault方法很简单,琐碎,不应该是错误的。

  3. 然而,为了能够重构drawDepthPassdraw我需要访问是否Drawable被tesselated和/或实例化,所以我增加了公众(非默认)的方法isTessellated()isInstanced()getInstancesCount()

  4. 然后我发现,由于我们的程序员很懒,在每一个中都实现它们比较麻烦Drawable

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

  6. 然后我发现我仍然很懒,不想为镶嵌和实例化的变量手动实现它。

  7. 因此,我分别创建TessellatedDrawableInstancedDrawable提供default isTessellated()isInstanced()并且InstancedDrawable我撤销了的default实现getInstancesCount()

结果,我可以得到以下内容:

  • 正常Drawablepublic class A implements Drawable
  • 镶嵌Drawablepublic class A implements TessellatedDrawable
  • 实例化Drawablepublic class A implements InstancedDrawable
  • 镶嵌和实例化Drawablepublic 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

我也考虑过构建器模式,但是,这是在创建某个对象的许多唯一实例时要使用的模式,这不是我们在这里所做的,也不是关于对象构造函数的。

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

斯图尔特·马克斯

首先,如果它可以工作,并且可以完成您想做的事,并且将来不会发生任何破坏的危险,那么说您滥用它就没有任何意义。毕竟,它完成了工作,对吗?诸如默认方法和静态方法之类的功能已添加到考虑到特定目标的界面中,但是如果它们可以帮助您实现其他目标,则可能是创造性地使用了新功能,也可能是粗暴的黑手。:-)在某种程度上,这取决于口味。

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

在我看来,这些概念在Drawable界面中混杂在一起当然,a的客户Drawable会在其上执行诸如调用drawdrawDepthPass方法之类的事情大。但是,查看的默认实现drawDepthPass,它会使用isTessellatedisInstanced方法获取一些信息,然后使用它们来选择Program并以特定方式在其上调用方法。可以将这些逻辑位封装在一个方法中,但是要使其以默认方法完成,必须将吸气剂强制进入公共接口。

我的模型当然不对,但是在我看来,这种逻辑更适合抽象的超类和子类关系。抽象超类实现了一些处理所有Drawable的逻辑,但是它使用isTesselated方法与特定的Drawable实现进行协商isInstanced在抽象超类中,这些将是需要实现子类的受保护方法。通过将此逻辑放入接口的默认方法中,所有这些方法都必须是公共的,这会使客户端接口变得混乱。这似乎类似于其他的方法是getDataModeisShadowReceiverisShadowCaster客户应该调用这些,还是逻辑上是实现的内部?

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

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

我注意到Drawable接口家族的另一个问题是,它使用默认方法的功能互相覆盖,以允许一些简单的mixin到实现类Box您可以说这是一种很巧妙implements TessellatedDrawableisTesselated方法,可以避免讨厌的方法!问题在于,这现在已成为实现类类型的一部分。客户知道aBox也是a有用TessellatedDrawable吗?还是这只是使内部实现更整洁的方案?如果是后者,则最好使用这些混合接口,TessellatedDrawableInstancedDrawable不是公用接口(即,包私有)。

还要注意,这种方法会使类型层次结构变得混乱,这会使代码的导航更加混乱。通常,新类型是一个新概念,但是具有仅定义返回布尔常量的默认方法的接口似乎很重要。

这方面的另一个观点。同样,我不知道您的模型,但是这里混入的特征非常简单:它们只是布尔常量。如果有一种Drawable实现,例如,开始时没有实例化,后来又可以实例化,则不能使用这些mixin接口。默认实现实际上在功能上受到很大限制。他们不能调用私有方法或检查实现类的字段,因此它们的使用受到很大限制。以这种方式使用接口几乎就像将它们用作标记接口一样,只是可以调用一种方法来获取特征,而不是使用instanceof除此之外,似乎没有太多用处。

Drawable接口中的静态方法似乎最合理。它们是看似面向客户端的实用程序,它们提供了公共实例方法提供的合理的逻辑汇总。

最后,关于模型的几点似乎很奇怪,尽管它们与使用默认方法和静态方法没有直接关系。

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

drawAll方法和offset正在发生类似的变化似乎在Drawable列表中,必须根据每个Drawable的数据大小使用特定的偏移来绘制它们。显然,最基本的方法draw要求调用者传递一个偏移量。这似乎是推销呼叫者的重大责任。因此,也许偏移的东西确实也属于实现。

有迹象表明,采取可绘制和呼叫列表的几个方法stream(),然后forEach()forEachOrdered()不需要,因为它List有一个forEach继承自的方法Iterable

我认为探索这种新材料的使用方式非常好。它足够新,以至于尚未出现普遍接受的样式。这样的实验以及本次讨论有助于发展这种风格。另一方面,我们还需要注意不要仅仅因为这些新颖的功能而使用这些闪亮的新功能。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章