我是使用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
被tesselated和/或实例化,所以我增加了公众(非默认)的方法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可以完美地处理这些获取,因为功能应该来自哪个接口从来没有任何歧义。
现在进入我自己的小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
会在其上执行诸如调用draw
或drawDepthPass
方法之类的事情。大。但是,查看的默认实现drawDepthPass
,它会使用isTessellated
和isInstanced
方法获取一些信息,然后使用它们来选择Program并以特定方式在其上调用方法。可以将这些逻辑位封装在一个方法中,但是要使其以默认方法完成,必须将吸气剂强制进入公共接口。
我的模型当然不对,但是在我看来,这种逻辑更适合抽象的超类和子类关系。抽象超类实现了一些处理所有Drawable的逻辑,但是它使用isTesselated
或方法与特定的Drawable实现进行协商isInstanced
。在抽象超类中,这些将是需要实现子类的受保护方法。通过将此逻辑放入接口的默认方法中,所有这些方法都必须是公共的,这会使客户端接口变得混乱。这似乎类似于其他的方法是getDataMode
,isShadowReceiver
和isShadowCaster
。客户应该调用这些,还是逻辑上是实现的内部?
这突出说明的是,尽管添加了默认方法和静态方法,但接口仍然面向客户端,而较少面向支持子类。原因如下:
我注意到Drawable
接口家族的另一个问题是,它使用默认方法的功能互相覆盖,以允许一些简单的mixin到实现类Box
。您可以说这是一种很巧妙implements TessellatedDrawable
的isTesselated
方法,可以避免讨厌的方法!问题在于,这现在已成为实现类类型的一部分。客户知道aBox
也是a有用TessellatedDrawable
吗?还是这只是使内部实现更整洁的方案?如果是后者,则最好使用这些混合接口,TessellatedDrawable
而InstancedDrawable
不是公用接口(即,包私有)。
还要注意,这种方法会使类型层次结构变得混乱,这会使代码的导航更加混乱。通常,新类型是一个新概念,但是具有仅定义返回布尔常量的默认方法的接口似乎很重要。
这方面的另一个观点。同样,我不知道您的模型,但是这里混入的特征非常简单:它们只是布尔常量。如果有一种Drawable
实现,例如,开始时没有实例化,后来又可以实例化,则不能使用这些mixin接口。默认实现实际上在功能上受到很大限制。他们不能调用私有方法或检查实现类的字段,因此它们的使用受到很大限制。以这种方式使用接口几乎就像将它们用作标记接口一样,只是可以调用一种方法来获取特征,而不是使用instanceof
。除此之外,似乎没有太多用处。
Drawable
接口中的静态方法似乎最合理。它们是看似面向客户端的实用程序,它们提供了公共实例方法提供的合理的逻辑汇总。
最后,关于模型的几点似乎很奇怪,尽管它们与使用默认方法和静态方法没有直接关系。
这似乎是一个Drawable
有-A Program
,因为有实例方法compileProgram
,getProgram
和delete
。然而drawDepthPass
和的方法类似,要求客户端传入两个程序,根据布尔型getter的结果选择其中一个。我不清楚调用方应该在哪里选择正确的程序。
drawAll
方法和offset
值正在发生类似的变化。似乎在Drawable列表中,必须根据每个Drawable的数据大小使用特定的偏移来绘制它们。显然,最基本的方法draw
要求调用者传递一个偏移量。这似乎是推销呼叫者的重大责任。因此,也许偏移的东西确实也属于实现。
有迹象表明,采取可绘制和呼叫列表的几个方法stream()
,然后forEach()
或forEachOrdered()
。不需要,因为它List
有一个forEach
继承自的方法Iterable
。
我认为探索这种新材料的使用方式非常好。它足够新,以至于尚未出现普遍接受的样式。这样的实验以及本次讨论有助于发展这种风格。另一方面,我们还需要注意不要仅仅因为这些新颖的功能而使用这些闪亮的新功能。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句