I am writing a piece of software to display an image on the screen using a particular library. The library lets me create an Atlas out of texture file data, to fetch Textures out of the Atlas using the textures' names, and to draw the textures by calling Renderer.draw(texture). It doesn't make a great deal of sense to create more than one of an Atlas or Renderer.
I previously designed my code as follows:
Similarly, I made a TextureRenderer class whose sole purpose is to provide static access to a Renderer. But many people seem to say that opening up a class like this to global access is bad. I cannot think of any better alternative, however. My core update/draw logic currently works something like this:
Making a Renderer object belong to the Game or Screen class doesn't make much sense to me, as there doesn't seem to really be a 'has-a' relationship between them; it's more of a 'uses-a', so that seems to imply that it would be best to just call Renderer.draw(thing) from outside the class rather than making it a data member. This would make my main code essentially call the various methods in order to get or print some form of data, much like System.out.println(). And of course, making one of the classes a subclass of Renderer makes even less sense. But making data members static for the purpose of global access and using singletons are often cited as 'bad' programming practices. This problem also arises in a lot of other scenarios in my program, ie. when I want to serialize Thing data members like texture filenames; do I put the serializer as a static data member in a class for global access and call it like I would a function, or do I put it in a class? And it seems like the Renderer will have to be initialized at the start of the program, which would take place in the init() method in Game, then be used to draw Textures in the Screen, so it would have to be accessible from both locations. How should I approach these scenarios?
edit: In case it's suggested to put these in the main class, that's unfortunately going to be difficult without using static variables; the library recommends having the main class creating an Application object, which is essentially a black box that actually does all the work of calling Game's init() and draw() methods (via a Game object passed into its constructor) and looping until the program exits. AbstractGame and Screen are classes supplied by the library, so it doesn't seem like I can easily give Game any data that AbstractGame doesn't specifically ask for.
Using "has-a" to think about object relationships can be useful at times but I don't think your take on "has-a" vs "uses-a" is helping you in this case. It might help if you think about it in terms of "A collaborates with B" instead. An object will typically hold references to other objects that it will collaborate with to do some work. For example, a business service object may define a workflow that involves calling one or more domain objects and/or one or more infrastructure objects. That is, a Transfer service may call on two Account objects, an AccountDao (data access object), and a Notifier object to complete a balance transfer transaction. Here, a "has-a" view on object relationships might lead you to think that it's not appropriate for a Transfer object to hold a reference to a Notifier even though there's really nothing wrong with it. I think using dependency injection helped me make the leap in understanding the difference.
That's one pitfall of object-oriented programming that you'll need to learn to avoid early on: object-oriented thinking is not about modeling your software objects after real-world objects; it's about assigning responsibilities appropriately. Thinking in terms of real-world objects can help you organize your thoughts but in the end, proper OO design is about encapsulating what each object is responsible for "knowing" (its data members) and "doing" (its methods).
The best ideas are the crazy ones. If you have a crazy idea and it works, it's really valuable.—Kent Beck
That's definitely an approach I never considered. I'll try to focus more on knowing and doing when designing my code, as opposed to has-a/is-a relationships; the former seems more complicated and open-ended, but hopefully I can get acquainted with that way of thought more as I practice. Dependency injection also seems interesting, and I'll try to think about my services in that way more often.
Jack Franz wrote:edit: In case it's suggested to put these in the main class, that's unfortunately going to be difficult without using static variables
Unless you define them in the method itself. Most of the time, main() methods are simply used as "launchers", so they should contain only as much code as is needed to initialize required stuff and then pass it off to an instance (possibly of the class containingmain()), which then does everything else. One very basic technique is described in the MainIsAPain page; although it's mainly meant for greenhorns to get them out of the habit of ploughing all their code into main(). You still might find it worth a read.
Making a Renderer object belong to the Game or Screen class doesn't make much sense to me, as there doesn't seem to really be a 'has-a' relationship between them; it's more of a 'uses-a'...
But couldn't you say the same thing about a program and a database? And yet these days they're generally established via a DataSource (javax.sql.DataSource) object, rather than a static DriverManager definition.
This idea of a factory object that returns an interface is used quite a lot for these kinds of "heavyweight" objects that tend to only be initialized once and then used a lot, but forcing it via a static class, or a singleton, tends to cause more problems than it solves.
For one thing, if you come up with (or find) a better "Renderer" in the future, it allows you to plug it in with the minimum of effort.
"Leadership is nature's way of removing morons from the productive flow" - Dogbert
Articles by Winston can be found here