java - Java 8 中多重继承的使用

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

首先是关于我如何获得此代码的步骤:
  • 我从 Drawable 开始接口(interface)和每个实现都有自己的 drawDepthPass , drawdelete方法。
  • 重构 deletedefault方法很简单,微不足道,不应该是错误的。
  • 然而为了能够重构drawDepthPassdraw我需要访问是否是 Drawable被镶嵌和/或实例化,所以我添加了公共(public)( 非默认 )方法 isTessellated() , isInstanced()getInstancesCount() .
  • 然后我发现在每个 Drawable 中实现它们会有点麻烦,因为我们程序员很懒惰。 .
  • 因此,我添加了 default方法 Drawable ,给出最基本的行为Drawable .
  • 然后我想我仍然很懒惰,不想为镶嵌和实例化的变体手动实现它。
  • 所以我创建了 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, TessellatedDrawable由 Java 8 完美处理,因为功能应该来自哪个接口(interface)没有任何歧义。

    现在进入我自己的小 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 Pattern,但是当你创建某个对象的许多独特实例时,这是一种使用的模式,这不是我们在这里要做的,这也不是关于对象的构造函数。

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

    最佳答案

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

    考虑到这个观点,我在 API 中寻找什么,以及我在设计 API 时尝试做的是将 API 的客户端与 API 的实现者区分开来。 API 的典型客户端或用户从某处获取某种接口(interface)类型的引用,并调用其上的方法以使事情发生。实现者为接口(interface)中定义的方法提供实现、覆盖方法和(如果是子类化)调用父类(super class)方法。通常,打算由客户端调用的方法与打算从子类调用的方法不同。

    在我看来,这些概念混杂在 Drawable 中。界面。当然,Drawable 的客户会做诸如调用 draw 之类的事情或 drawDepthPass他们的方法。伟大的。但是看看 drawDepthPass 的默认实现,它使用 isTessellated 获取一些信息和 isInstanced方法,然后使用这些来选择一个程序并以特定方式调用它的方法。将这些逻辑部分封装在一个方法中是很好的,但是为了在默认方法中完成它,必须将 getter 强制使用公共(public)接口(interface)。

    当然,我可能对您的模型有误,但在我看来,这种逻辑更适合抽象的父类(super class)和子类关系。抽象父类(super class)实现了一些处理所有 Drawable 的逻辑,但它使用类似 isTesselated 的方法与特定的 Drawable 实现协商。或 isInstanced .在抽象父类(super class)中,这些将是子类需要实现的 protected 方法。通过将此逻辑放入接口(interface)的默认方法中,所有这些都必须是公共(public)的,这会使客户端接口(interface)变得困惑。其他看起来相似的方法是 getDataMode , isShadowReceiver , 和 isShadowCaster .客户是否期望调用这些,或者它们在逻辑上是实现的内部?

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

  • 接口(interface)只有公共(public)成员。
  • 抽象类可以有 protected 方法供子类覆盖或调用。
  • 抽象类可以有私有(private)方法来实现实现共享。
  • 抽象类可以有字段(状态),这些字段可以被保护以与子类共享状态,否则通常是私有(private)的。
  • 抽象类可以具有对子类强制执行某些行为策略的 final方法。

  • 我注意到的另一个问题 Drawable接口(interface)族的特点是它使用默认方法相互覆盖的能力,允许一些简单的mixin 到实现类中,比如Box .您可以直接说 implements TessellatedDrawable 真是太好了并避免讨厌的覆盖 isTesselated方法!问题是这现在成为实现类类型的一部分。客户知道 Box 有用吗?也是TessellatedDrawable ?或者这只是一个使内部实现更清洁的方案?如果是后者,最好使用这些 mixin 接口(interface),例如 TessellatedDrawableInstancedDrawable不是公共(public)接口(interface)(即包私有(private))。

    另请注意,这种方法会使类型层次结构变得困惑,这会使代码在导航时更加困惑。通常,新类型是一个新概念,但拥有仅定义返回 boolean 常量的默认方法的接口(interface)似乎是重量级的。

    这方面的另一点。同样,我不知道你的模型,但这里混合的特征非常简单:它们只是 boolean 常量。如果有 Drawable比如说,开始时没有被实例化,后来可以被实例化,它不能使用这些混合接口(interface)。默认实现在它们可以做什么方面确实受到很大限制。它们不能调用私有(private)方法或检查实现类的字段,因此它们的使用非常有限。以这种方式使用接口(interface)几乎就像将它们用作标记接口(interface)一样,只是稍微增加了能够调用方法来获取特征的功能,而不是使用 instanceof .除此之外似乎没有太大用处。
    Drawable 中的静态方法界面似乎大多是合理的。它们是看起来面向客户端的实用程序,它们提供由公共(public)实例方法提供的合理逻辑聚合。

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

    好像是 Drawable has-a Program ,因为有实例方法 compileProgram , getProgram , 和 delete .然而drawDepthPass和类似的方法需要客户端传入两个程序,根据 boolean getter 的结果选择其中一个。我不清楚调用者应该在哪里选择正确的程序。
    drawAll 也发生了类似的事情。方法和 offset值(value)。似乎在 Drawable 列表中,必须根据每个 Drawable 的数据大小使用特定的偏移量绘制它们。然而,显然是最基本的方法,draw , 要求调用者传入一个偏移量。这似乎是插入调用者的重大责任。所以也许偏移量也确实属于实现。

    有几种方法可以获取可绘制列表并调用 stream()然后 forEach()forEachOrdered() .这不是必需的,因为 List有一个 forEach方法,继承自 Iterable .

    我认为探索如何使用这些新东西很棒。它足够新,以至于尚未出现普遍接受的风格。像这样的实验和这个讨论,有助于发展这种风格。另一方面,我们还需要注意不要仅仅因为它们是新的和 Shiny 的而使用这些 Shiny 的新功能。

    关于java - Java 8 中多重继承的使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22052330/

    相关文章:

    c++ - 类设计: Class member inherits from the same base class

    c++ - 如何删除抽象类中类似的 const 和非 const 成员函数之间的代码重复?

    java - 匿名类方法中的变量可见性

    c++ - 我的第一个 CPP 程序的问题 - 标题和源代码

    PHP OOP : Handling multiple row outputs from a database, 最好的方法是什么?

    perl - 我可以重载 Perl 的 = 吗? (还有使用 Tie 时的问题)

    c++ - 使用引用实现状态模式

    Java HW,字符串初始化错误

    java - Android,启用虚拟键盘时条码扫描仪输入不完整

    java - 以非零退出值 255 完成