I'm trying to write a 2d game engine and I'm trying to implement a viewport system so that when I'm drawing in a certain viewport, the game coordinates will be transformed to the screen coordinates without having to manually do the transformation.
What I want to do is create a Graphics2D
wrapper that adds a setViewport
method.
The way I see it there are 2 options:
Create a class that has an instance of a Graphics2D
and has all the same methods as Graphics2D
plus setViewport
and just calls the respective method on the Graphics2D
instance.
Subclass Graphics2D
and just add a setViewport
method and then just cast from Graphics2D
to this new class
I tried #2 because #1 seemed very impractical but ran into a ClassCastException
. I'm unable to cast Graphics
or Graphics2D
to this new class. When I print the graphics object before the cast (either Graphics
or Graphics2D
), both come out as sun.java2d.SunGraphics2D
.
Am I doing something fundamentally wrong trying to subclass and cast? If not, how can I fix this problem?
One OO design priciple is "favor composition over inheritance", this is acheived using the wrapper class or Decorator design pattern (which is in my opinion what is meant by wrapper in the course slide). So what you have done is in fact the elegant solution for a number of reasons:
It protects against future implementation changes in Graphics2D
, if you use inheritance and by misfortune a new method is added to Graphics2D
with the same signature as your new method and a different return type, your class will no longer compile. If you use the same signature and return type you would override the new method in Graphics2D
which could (and has) result in several days of frustrating debugging.
Inheritance in this way violates encapsulation making software fragile and error-prone in the long run.
Using composition you are protecting your class from future changes in the class your composing with, your class would forward all method calls to it's private
Graphics2D
instance and handle transforming coordinates separately.
It also allows for easy future extension, using inheritance will tie you to the current implementation of Graphics2D
potentially limiting the performance of your class.
An example of this exists in the Java API: The Properties
class extends HashTable
, this illustrates improper use of inheritance because a Properties
is not a HashTable
, it's not meant to be used in the same way. In this case a call to Properties p.getProperty(key)
might give different results than p.get(key)
, because the latter-case does not take defaults into account.
The Decorator design pattern:
public class Wrapper {
private WrappedClass w;
public Wrapper(WrappedClass w) {
this.w = w;
}
// Forward calls to WrappedClass methods to the private instance.
public ReturnType example(Argument a) { return w.example(a); }
// Add your methods here:
}
Although this might seem like a tedious approach it is worth it in the long run for the reasons above. Also the existance of interfaces in the Java API such as the Set
interface for the HashSet
above makes writing such classes easier although I don't know if such an interface exists for Graphics2D
.
sources: Effective Java 2nd Edition - Joshua Bloch