6

Recently I made a question about Java classes implementing methods from two sources (kinda like multiple inheritance). However, it was pointed out that this sort of need may be a sign of a design flaw. Hence, it is probably better to address my current design rather than trying to simulate multiple inheritance.


Before tackling the actual problem, some background info about a particular mechanic in this framework:

It is a simple game development framework. Several components allocate some memory (like pixel data), and it is necessary to get rid of it as soon as you don't need it. Sprites are an example of this.

Anyway, I decided to implement something ala Manual-Reference-Counting from Objective-C. Certain classes, like Sprites, contain an internal counter, which is increased when you call retain(), and decreased on release().

Thus the Resource abstract class was created. Any subclass of this will obtain the retain() and release() implementations for free. When its count hits 0 (nobody is using this class), it will call the destroy() method. The subclass needs only to implement destroy().

This is because I don't want to rely on the Garbage Collector to get rid of unused pixel data.


Game objects are all subclasses of the Node class - which is the main construction block, as it provides info such as position, size, rotation, etc.

enter image description here

See, two classes are used often in my game. Sprites and Labels.

Ah... but wait. Sprites contain pixel data, remember? And as such, they need to extend Resource.

enter image description here

But this, of course, can't be done. Sprites ARE nodes, hence they must subclass Node. But heck, they are resources too.


  • Why not making Resource an interface?
  • Because I'd have to re-implement retain() and release(). I am avoiding this in virtue of not writing the same code over and over (remember that there are multiple classes that need this memory-management system).

  • Why not composition?

  • Because I'd still have to implement methods in Sprite (and similar classes) that essentially call the methods of Resource. I'd still be writing the same code over and over!

What is your advice in this situation, then?

  • I guess they are Resources that are not Nodes? – Djizeus May 31 '14 at 4:53
  • @Djizeus: Yes, exactly. – Omega May 31 '14 at 5:00
  • 4
    Java 8 allows default methods on interfaces. – Silviu Burcea May 31 '14 at 5:08
  • @Silviu Burcea: Which basically means interfaces are not interfaces (contracts) any more but lie somewhere between interfaces and abstract classes. – Giorgio Jun 23 '14 at 4:31
  • @Giorgio: Abstract classes implemented as interfaces. Since you can implement more than one interface, we are facing the multiple inheritance in Java. – Silviu Burcea Jun 23 '14 at 10:25
11

You wrote

Sprites contain pixel data

which is a clear sign of a "has a" relationship, not an "is a". So bite the bullet and make Sprite -> Resource a composition.

Why not composition? Because I'd still have to implement methods in Sprite (and similar classes) that essentially call the methods of Resource

yes, you will - but only trivial delegation methods. You won't have to repeat any functional code. That's actually acceptable. and the standard way composition/delegation works. Here you find a detailed example how to replace inheritance by delegation. This approach is so common, there is even an Eclipse refactoring exactly for this purpose,

2

Your Sprite object is effectively trying to take 2 responsibilities: manage memory cleanup and do the job with mentioned pixel data. The S letter in SOLID principles advices to leave each class a single responsibility. Bread doesn't slice itself: there is some external force that takes the bread, the tool(s) and does its job. I would think of those, who are going to call these retain() and release() methods. Are the callers actually want to "slice the bread", i.e. "do the referencecounting of the arbitrary object"?

So I would recommend neither composite, nor inherit the Resource in Sprite, but rename it to some sort of ResourceReferenceCounter and use Sprite and ResourceReferenceCounter independently. Which is how effectively java garbage collector works with your objects from your application's point of view.

1

tl;dr: composition is good, implementation inheritance is... an unfortunate choice.

What you try to achieve is to make Sprite look simultaneously like a Resource and like a Node. You don't need this. You need a way to expose the Resource interface and the Node interface.

Imagine:

// traditional part

interface Node {
  Position getPosition();
  // anything else
}

interface Resource {
  void allocate(...);
  void destroy();
}

// composition interfaces

interface NodeHolder {
  Node asNode(); // the only method
}

interface ResourceHolder {
  Resource asResource(); // the only method
}


// reusable resource helper

class ResourceImpl implements Resource {
  public void allocate(...) { /* some implementation */ }
  public void destroy() { /* some implementation */ }
}

// you could have a default Node implementation the same way, 
// but you don't have to; you could directly extend a Node implementation:

class Sprite 
  extends NodeBaseImpl 
  implements NodeHolder, ResourceHolder 
{
  private Resource my_resource;
  public Sprite(...) {
    // whatever construction needed
    my_resource = new ResourceImpl();
  }

  public Resource asResource() { return my_resource; } // composition!
  public Node asNode() { return this; } // composition again! :)
  // ...
} 

Now all your resource-managing code operates on ResourceHolders, and all your node-handling code operates on NodeHolders. No code assumes that any object inherits a specific base class; only interfaces are used:

List<NodeHolder> nodes = new ArrayList<>(5);
nodes.add(new Sprite(...));
nodes.add(new Minimap(...));
nodes.add(new HitpointBar(...));
// can add more NodeHolders
...

// Rotate them all
for (NodeHolder nh : nodes) { nh.asNode().rotate(15); }

Implementations can be inherited or composed, to your taste. Only code to copy all over are methods asNode(), etc, which are usually trivial, and only one per interface.

You double the number of interfaces, though, because you use nnnHolder interface dispatch to explicitly do what e.g. Go does automatically.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.