我是在使用 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
, draw
和 delete
方法。 delete
到 default
方法很简单,微不足道,不应该是错误的。 drawDepthPass
和 draw
我需要访问是否是 Drawable
被镶嵌和/或实例化,所以我添加了公共(public)( 非默认 )方法 isTessellated()
, isInstanced()
和 getInstancesCount()
. Drawable
中实现它们会有点麻烦,因为我们程序员很懒惰。 . default
方法 Drawable
,给出最基本的行为Drawable
. TessellatedDrawable
和 InstancedDrawable
提供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)仍然面向客户端,而不是支持子类。原因如下:
我注意到的另一个问题
Drawable
接口(interface)族的特点是它使用默认方法相互覆盖的能力,允许一些简单的mixin 到实现类中,比如Box
.您可以直接说 implements TessellatedDrawable
真是太好了并避免讨厌的覆盖 isTesselated
方法!问题是这现在成为实现类类型的一部分。客户知道 Box
有用吗?也是TessellatedDrawable
?或者这只是一个使内部实现更清洁的方案?如果是后者,最好使用这些 mixin 接口(interface),例如 TessellatedDrawable
和 InstancedDrawable
不是公共(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/