Pregunta

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:

  • Instantiating any of the lowest level subclasses works fine, 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?

  • Everything I have now, except that I can instantiate 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.

¿Fue útil?

Solución

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()

Otros consejos

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);
}

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top