Reputation: 69349
I am working on the following structure:
Buffer
XBuffer
extends Buffer
XYBuffer
extends XBuffer
All objects should be instantiable, so no abstract
, in order to support forward compatability.
I have came up with the following, with pitfalls/issues I will describe below:
public class Buffer<S extends Buffer<S>> {
protected final int bufferType;
protected final int bufferDataType;
protected int bufferId;
protected boolean created;
public Buffer(final int bufferType, final int bufferDataType) {
this.bufferType = bufferType;
this.bufferDataType = bufferDataType;
}
@SuppressWarnings("unchecked")
public S create() {
assertNotCreated();
bufferId = GL15.glGenBuffers();
created = true;
return (S)this;
}
@SuppressWarnings("unchecked")
public S bind() {
assertCreated();
GL15.glBindBuffer(bufferType, bufferId);
return (S)this;
}
@SuppressWarnings("unchecked")
public S fillData(final float[] data) {
assertCreated();
FloatBuffer dataBuffer = BufferUtils.createFloatBuffer(data.length).put(data);
dataBuffer.flip();
GL15.glBufferData(bufferType, dataBuffer, bufferDataType);
return (S)this;
}
public void delete() {
assertCreated();
GL15.glDeleteBuffers(bufferId);
}
public int getBufferId() {
return bufferId;
}
public boolean hasBeenCreated() {
return created;
}
private void assertCreated() {
if (!hasBeenCreated()) {
throw new RuntimeException("Buffer has not been created.");
}
}
private void assertNotCreated() {
if (hasBeenCreated()) {
throw new RuntimeException("Buffer has been created already.");
}
}
@Override
public String toString() {
return "Buffer(" + bufferType + ", " + bufferDataType + ", " + bufferId + ", " + created + ")";
}
}
public class ArrayBuffer<S extends ArrayBuffer<S>> extends Buffer<S> {
public ArrayBuffer(final int bufferDataType) {
super(GL15.GL_ARRAY_BUFFER, bufferDataType);
}
}
public class StaticDrawArrayBuffer extends ArrayBuffer<StaticDrawArrayBuffer> {
public StaticDrawArrayBuffer() {
super(GL15.GL_STATIC_DRAW);
}
}
Now the following happens:
StaticDrawArrayBuffer
works as expected.ArrayBuffer
does not work as expected, as I cannot instantiate it anymore without using generics. (Keep in mind: The generics used here are only there to help me, not to actually provide generic functionality)Buffer
does not work as expected either with the same issue as ArrayBuffer
.What do I want?
Buffer
and ArrayBuffer
without using generics.How would I do this?
To clarify more, these statements should all compile:
Buffer buffer = new Buffer().create().bind();
ArrayBuffer arrayBuffer = new ArrayBuffer().create.bind();
StaticDrawArrayBuffer staticDrawArrayBuffer = new StaticDrawArrayBuffer().create.bind()
If I would not use any generics at all, then new ArayBuffer().create()
will return a Buffer
, resulting in that it cannot be assigned to ArrayBuffer
anymore. It does leave chaining of Buffer
methods intact, but it would furthermore also break if the chain would contain methods only available to ArrayBuffer
.
If it helps, I have the ability to use Java 8 if it is a almost bugfree (it should be now, I think?), considering this project won't see real daylight for a long while anyway.
Upvotes: 1
Views: 505
Reputation: 21089
One possibility would be to separate the part of your code that does not use generics from the generic inheritance tree (which is kind of clunky to begin with, in my opinion, but that's mainly an issue with the way Java was designed and the design choices made when generics were added to the language).
What I mean by that is to do the following:
public class Buffer extends BaseBuffer<Buffer> {
public Buffer(final int bufferType, final int bufferDataType) {
super(bufferType, bufferDataType);
}
}
public class ArrayBuffer extends BaseArrayBuffer<ArrayBuffer> {
public ArrayBuffer(final int bufferDataType) {
super(bufferDataType);
}
}
Where BaseBuffer
and BaseArrayBuffer
are the classes you call Buffer
and ArrayBuffer
.
(Though it seems you may already be doing this, based on one of your comments?)
The issue with this method is that you could not pass a StaticDrawArrayBuffer
as an ArrayBuffer
or an ArrayBuffer
as a Buffer
(or store them in variables of those types) as they are no longer subclasses of those specific types. You'd have to use something like BaseArrayBuffer<StaticDrawArrayBuffer>
and BaseBuffer<ArrayBuffer>
for that instead.
Upvotes: 0
Reputation: 201
I would strongly suggest that you rethink your architecture. Instantiating both children and parent classes is a bad, bad idea. Suppose, one day you wish to change parent behaviour (because you use it and requirements have changed), while leaving child intact. What then?
Use Strategies instead. Specify Buffer interface (why do you need it? What it's supposed to do?) and give each instance a Strategy to fulfill their purpose.
Then you'll have only Buffer that contains Buffers and Strategies to implement your logic:
public interface BufferingStrategy
{
public Buffer create() ;
public Buffer bind();
public Buffer fillData(final float[] data);
}
Upvotes: 0
Reputation: 17864
What about Covariant return types instead of generics?
public class ArrayBuffer extends Buffer {
public ArrayBuffer(final int bufferDataType) {
super(GL15.GL_ARRAY_BUFFER, bufferDataType);
}
public ArrayBuffer create() {
super.create();
return this;
}
public ArrayBuffer bind() {
super.bind();
return this;
}
}
public class StaticDrawArrayBuffer extends ArrayBuffer {
public StaticDrawArrayBuffer() {
super(GL15.GL_STATIC_DRAW);
}
public StaticDrawArrayBuffer bind() {
super.bind();
return this;
}
}
AFAIK this should compile now:
Buffer buffer = new Buffer().create().bind();
ArrayBuffer arrayBuffer = new ArrayBuffer().create().bind();
StaticDrawArrayBuffer staticDrawArrayBuffer = new StaticDrawArrayBuffer().create().bind()
Upvotes: 1