Java: Enforcing doubly linked objects
I am designing a game engine in Java.
At the core of this engine exist the two classes Asset and Attribute, where an Asset has a list of Attributes. Most Attributes need no link back up to their Attribute, meaning that Attributes can and often do appear in the lists of more than one Asset. However, there is an extention of Attribute called UniqueAttribute, which is an implementation for those that are specific to their Asset, and utilise a link back.
Ideally, my Asset's addAttribute method would look something like this if I cut out the other code:
public void addAttribute(Attribute attribute){ if(attribute instanceof UniqueAttribute) ((UniqueAttribute)attribute).setAsset(this); attributeList.add(attribute); }
Unfortunately, since they live in different packages, UniqueAttribute.setAsset() must be public. This leaves the method open to outside users of the engine to mess with, and while I could just handwave it off by saying using this method directly is a bug - it seems rather sloppy.
The second option is to provide the UniqueAttribute with the Asset on construction, meaning that the code at the point of creation would look something like this:
asset.addAttribute(new UniqueAttribute(asset));
While I can add a check-and-throwable or assert to confirm the correct asset is passed in, I am basically relying on the user to connect the two, which I also would prefer not to do.
The third option is to bite the bullet and put 50 java files all into the same package so that I can just use the standard visiblity.
Is there some kind of pattern or something that will help link these two together without exposing the wires, or forcing me to pu开发者_运维百科t everything into one massive package?
Irrelevant rant: I have always disliked that the concept of subpackages in java has not really been expanded in any meaningful way. A subpackage, as far as java is concerned is simply a different package, and there have been many occasions I could do with more visibility modifiers directly related to this.
My suggestion would be that Asset, Attribute and UniqueAttribute should all be in the same package (possibly along with a few other core "engine" classes). Then you can use standard package visibility for UniqueAttribute.setAsset.
You don't need to put all other classes in the same package - your Asset.addAttribute method should be public and accessible from other packages so the rest of your application can just use that directly.
So the solution could be called "3-" in your categorisation.
As some more general points, also consider:
- Whether you really need the complexity of both Attributes and UniqueAttributes - I'm not sure you really do, having previously implemented a reasonably complex game object model without needing anything that looked like a UniqueAttribute. If the UniqueAttribute "needs a link back" then perhaps it is trying to be too clever / do too much?
- Even if you do need both, do you really want to write code that treats them the same way / as part of the same object heirarchy? they seem quite conceptually different, and you will end up writing a lot of conditional code if you conflate the two.....
- There are various other advantages of attributes being consistently shared and immutable - it's better for memory usage, concurrency and testability among other things. And as they are presumably quite small, the cost of copy-on-write semantics is trivial in the cases where you need it.
I would add a callback method in Attribute which is called when an instance of Attribute is added to an Asset:
class Attribute {
protected void addedToAsset(Asset asset) {
// do nothing
}
}
This method would be called in the addAttribute method
class Asset {
public void addAttribute(Attribute attribute) {
attributeList.add(attribute);
attribute.addedToAsset(this);
}
}
And the method would be overridden in UniqueAttribute in order to control the link with Asset:
class UniqueAttribute extends Attribute {
Asset asset;
protected void addedToAsset(Asset asset) {
// manage the previous link if needed
if (this.asset != null) { ... }
this.asset = asset;
}
}
Wit this solution, Asset and Attribute should be placed in the same package. But UniqueAttribute could be in whatever package you want.
Modify your scond option
asset.addAttribute(new UniqueAttribute(asset));
like this:
class UniqueAttribute {
Asset asset;
public UniqueAttribute(Asset asset) { this.asset = asset; asset.addAttribute(this); }
}
Do a similar approach for the non unique Attribute. That means instead of using addAttribute() from the outside, only use it inside of the constructors.
An other option is to add two factory methods to Asset: createAttribute() and createUniqueAttribute();
Well, basically you want to do 3 things:
- make setAsset method visible inside the package containing Asset class
- hide setAsset method from all other packages
- don't use subpackages to achieve that
That's a bit problematic: if you declare public that method in Attribute class all other classes including that package (let's call it AttributePackage), you can't prevent the user to include somewhere that package.
On the other hand you could do the following:
- create an interface containing only the Attribute method that user should use, let's call it AttributeInterface
- make Attribute implement that interface
- add AttributeInterface to a new package
An user that want use Attribute class should use it through AttributeInterface meanwhile Asset will use Attribute class directly in order to have access to all methods.
I'll make an example:
//attribute interface package
public interface AttributeInterface{
public void publicAttributeMethodClientShouldUse();
}
//attribute package
public class Attribute{
public void setAsset(Asset a);
public void publicAttributeMethodClientShouldUse();
}
Asset will reference directly Attribute meanwhile user should reference AttributeInterface. Hope to be clear.
First, these entities do appear to be so closely related as to be placed in the same package. I'd put them in the same package and make the addAttribute method package-private.
Bear in mind though that since the introduction of AccessibleObject in Java, visibility and access control in the language has become merely cosmetic... Anyone using your library can grab your classes and make private methods and fields accessible and modifiable! Hell, they can even modify final
members! So, don't put much emphasis in the visibility aspect and just make sure your model and method flow make sense for your users and work correctly.
精彩评论