Hi! I'm new to this forum, I hope my stay will be pleasant for everyone involved. =) I place my question here since it's more a question on how to design an interface in a "good practice" way, than solving a game making problem.
To clearify: the Item class is an inanimate thing that can exist in the world and in an inventory of any kind. The Action class is a class that uses the methods of the Item class and references to other objects like the position in the world, the player, the target, etc, to produce effects and actions. The seperation is motivated by me wanting to reuse code for actions that happen across a wide range of objects, and also keep the effects modular.
I am designing an Item interface in a game I'm making. My main issue is that I have tried to make the interface clean with only a few methods because I want it to be easy for the person implementing subclasses of the Action class, as it will need to contain implementations that use this interface. I have seen other games having massive item/object interfaces with lots of isX() methods and methods that doesn't really apply to the object behind it all. I find such solutions marginally better than instanceof and casting, since they both use if clauses to a large extent.
But I am stumped with the question on how to solve Item "inventory". Memory overhead is a real issue since these items will number in millions, and even if many will just be references to static Items, inventory Items will need to have a list/array of some kind that must be in an specific instance. So I want to avoid having an object handle these inventory methods because it carries additional memory overhead beyond the actual array or list. This List/Array will contain Items, but not all items will have such inventories. Therefore I find it ugly to have about 5 methods about just how to manipulate this list/array in the actual Item interface, as only a few will use it. Even if the potential to cause bugs on the action implementing side is not that big, it's still ugly and I feel an Item should be more abstract than this. I could have just made an abstract Inventory class and have some Items extend this, as the inventory related methods would mostly use the same implementation, and then connect to the subclasses by overridding a method so the actual variables for inventory size and such would be reachable by this superclass, but this would only make implementation of inventory Items easier, it will still leave a bloated and messy interface, and I anticipate more methods for other different aspects could also start appearing if I don't restrict this watering down of the abstraction and it could turn out a mashup of different possible subclasses! I think any class should avoid having methods that is not relevant for the contained object.
My Proposed solution is just having a way to check if the item has an inventory, and then if it has it then the implementer in a action class either checks if it is an inventory, or knows this because of testing earlier than the scope of this Action. Then the implementer would feed the Item in question to the desired methods that would now instead reside in an abstract helper class that now gets the list and important variables from the Item. The solution makes the list public and I don't really like that in a way, but now all methods in Item have a purpose and the number of methods has decreased.
Is there another way of doing this or is there some flaw in my reasoning that any of you experienced programmers notice? I'm selflearned and have no idea of what is the best practise but I know I prefer interfaces to not contain hundreds of methods. =) Are classes like my outlined helperclass to be avoided? I feel there is something fishy about spreading out the implementation of the Item behaviour outside the class scope... Perhaps making it an internal class or being careful in designing the package so this behaviour is emphasised?
Any ideas, tips that come to mind?
Helper classes and interfaces are very different things.
Helper classes (which I think are also called utility classes) are uninstantiable, and contain static members only.
Interfaces are intended to be instantiated (as implementing classes) and interface methods are never static.
Agree with SE that your description is difficult to understand. It would appear you have several problems, some about size of a class and others about memory. You should separate the problems, and solve them individually. Leave the memory problem till last.
Have you considered putting your many Items in a database? Databases are good at storing millions of objects, and retrieving them rapidly.
Thank you for trying to help me, I apreciate it. =) And thanks for the welcome.
I'm aware my describing turned out abstract and convoluted. You are right that the problems pertain to memory and wanting a small and clean interface. And memory is more important in this specific case. My solution on the memoryproblem is not wanting to instantiate any more objects to solve the problem. Thus I have only the original item instance to store the variables necessary for the Inventory management.
The solution of having several interfaces might be better, but that would mean I would have to cast the Item object to an inventory object, to access the inventory methods? This is probably not much different from my solution with the static helper/utility class, except for the casting performance overhead. I think I won't be needing an inventory interface because it currently looks like I only need one implementation of the inventory methods.
I have been avoiding this kind of casting based solutions in my code since I see a lot of warnings and flak responses to such solutions in assorted forums. Perhaps I'm being overly cautious because I don't understand the real risks behind it and just avoid it because of general warnings?
What should I watch out for, and be mindful of when casting like this? In the bigger picture, and in good practise terms? Thanks in advance. =)
Joined: Oct 13, 2005
Agree you should be wary about casting; it is error‑prone and can lead to exceptions. There is often no performance overhead after the compiler stage, however.
For anyone with the same issue: I used multiple interfaces that were seperate. The main interface "Item" has methods to return the instance as the other applicable interfaces, such as "Inventory". As the method is written in the scope of the interface implementing class its a simple upcast and typesafe.