我是否使用 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
界面开始,每个实现都有自己的drawDepthPass
,draw
和delete
方法。
将delete
重构为default
方法非常简单,不容错误。
但是,为了能够重构drawDepthPass
和draw
,我需要访问Drawable
是否已被细分和/或实例化,因此我添加了公众(非默认)方法isTessellated()
,isInstanced()
和getInstancesCount()
。
然后我发现由于我们的程序员很懒,在每个Drawable
中实现它们会稍微麻烦。
因此,我将default
方法添加到Drawable
,提供了最基本Drawable
的行为。
然后我认为我仍然很懒,并且不想手动为tessellated和instanced变体实现它。
因此,我创建了分别提供TessellatedDrawable
InstancedDrawable
和default
的{{1}}和isTessellated()
。在isInstanced()
我撤消了InstancedDrawable
的{{1}}实施。
因此,我可以拥有以下内容:
default
:getInstancesCount()
Drawable
:public class A implements Drawable
Drawable
:public class A implements TessellatedDrawable
Drawable
:public class A implements InstancedDrawable
。为了确保您,这一切都编译好并且运行正常,{8}由Java 8完美处理,因为功能应该来自哪个接口。[/ p>
现在进行我自己的小OOP设计评估:
Drawable
实际上都是public class A implements InstancedDrawable, TessellatedDrawable
,因此implements InstancedDrawable, TessellatedDrawable
不会中断。Drawable
和/或Drawable
进行分组,与其实施方式无关。我的其他想法:
使用更传统的分层方法,但是我忽略了它最终的结果:
Collection<Drawable>
TessellatedDrawable
InstancedDrawable
abstract class AbstractDrawable
class Drawable extends AbstractDrawable
我也考虑过一个Builder模式,但是当你创建一个特定对象的很多独特实例时,这是一个模式,这不是我们在这里做的,也不是关于构造函数的对象。
所以第一个也是最后一个问题是:我是否使用 Java 8的功能或滥用它?
答案 0 :(得分:11)
首先,如果它有效,并且你做了你想做的事情,并且将来没有任何破坏的危险,那么说你滥用它是没有意义的。毕竟,它完成了工作,对吧?默认方法和静态方法等功能被添加到具有特定目标的接口中,但是如果它们帮助您实现其他目标,则可以是创造性地使用新功能,也可以是粗暴和肮脏的黑客攻击。 :-)在某种程度上,这是一个品味问题。
考虑到这一点,我在API中寻找的内容,以及我在设计API时尝试做的是将API的客户端与实现者区分开来。一个API。 API的典型客户端或用户从某个地方获取某种接口类型的引用,并在其上调用方法以使事情发生。实现者提供接口中定义的方法的实现,覆盖方法和(如果子类化)调用超类方法。通常,客户端调用的方法与从子类调用的方法不同。
在我看来,这些概念在Drawable
界面中混合在一起。当然,Drawable
的客户会执行调用draw
或drawDepthPass
方法的操作。大。但是查看drawDepthPass
的默认实现,它会使用isTessellated
和isInstanced
方法获取一些信息,然后使用这些方法选择一个程序并以特定方式调用它。将这些逻辑位封装在方法中是很好的,但为了在默认方法中完成,必须将getter强制进入公共接口。 / p>
当然,我对你的模型可能是错的,但在我看来,这种逻辑更适合抽象的超类和子类关系。抽象超类实现了一些处理所有Drawable的逻辑,但它使用isTesselated
或isInstanced
等方法与特定的Drawable实现进行协商。在抽象超类中,这些是受保护的方法,需要子类来实现。通过将此逻辑放入接口的默认方法中,所有这些都必须是公共的,这会使客户端接口变得混乱。其他似乎相似的方法是getDataMode
,isShadowReceiver
和isShadowCaster
。是否期望客户打电话给这些客户,或者他们是否在逻辑上内部实施?
这突出表明,尽管添加了默认方法和静态方法,但接口仍面向客户端,而不是支持子类。原因如下:
我在Drawable
接口系列中注意到的另一个问题是,它使用默认方法的能力相互覆盖,以允许一些简单的混合到实现类,如Box
。您可以简单地说implements TessellatedDrawable
并避免令人讨厌的覆盖isTesselated
方法,这很简洁!问题是,这现在成为实现类的一部分。客户知道Box
也是TessellatedDrawable
是否有用?或者这只是一个使内部实施更清洁的方案?如果它是后者,那么像TessellatedDrawable
和InstancedDrawable
这样的mixin接口可能不是公共接口(即包私有)。
另请注意,此方法会混淆类型层次结构,这会使代码更难以导航。通常一个新类型是一个新概念,但它似乎是重量级的接口只定义返回布尔常量的默认方法。
这方面的另一点。同样,我不知道你的模型,但这里混合的特征非常简单:它们只是布尔常量。如果有Drawable
实现,例如,开始没有实例化,以后可以实例化,它就不能使用这些mixin接口。默认实现在他们可以做的事情上非常受限制。他们无法调用私有方法或检查实现类的字段,因此它们的使用非常有限。以这种方式使用接口几乎就像使用它们作为标记接口一样,只需要调用方法来获取特性,而不是使用instanceof
。除此之外似乎没什么用处。
Drawable
界面中的静态方法似乎最合理。它们的实用程序看似面向客户端,它们提供了公共实例方法提供的合理的逻辑聚合。
最后,有一些关于模型的观点似乎很奇怪,尽管它们与默认和静态方法的使用没有直接关系。
似乎Drawable
有一个Program
,因为有实例方法compileProgram
,getProgram
和delete
。然而drawDepthPass
和类似的方法要求客户端传入两个程序,其中一个程序是根据布尔getter的结果选择的。我不清楚调用者应该选择正确的程序。
使用drawAll
方法和offset
值进行类似的操作。看起来像是在Drawables列表中,它们必须使用基于每个Drawable数据大小的特定偏移量来绘制。然而,显然最基本的方法draw
,要求调用者传入偏移量。这似乎是推动呼叫者的一大责任。所以也许偏移的东西也真的属于实现。
有几种方法可以获取可绘制列表,然后调用stream()
,然后调用forEach()
或forEachOrdered()
。这不是必要的,因为List
上有一个forEach
方法,继承自Iterable
。
我认为探索如何使用这些新东西真是太棒了。它足够新,以至于尚未出现一种普遍接受的风格。像这样的实验和这个讨论有助于发展这种风格。另一方面,我们还需要注意不要使用这些闪亮的新功能,因为它们是新的和有光泽的。