Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
OOPsie: Abstract Classes & Methods
After learning the basics of classes, objects, function overriding, and polymorphism, we only have a few topics left: interfaces and abstraction.
Welcome back, game developers, to another episode of OOPsie. When we started this series we talked about the basics of object-oriented programming (OOP) which included classes and objects. Afterwards, we headed into function overriding and polymorphism, or the process of relating objects through the use of an inherited structure. Next, we'll talk about ways we can modify what we learned last article to be more appropriate in the context of our class structure.
Abstract Class & Methods
One tweak to the inheritance structure we learned earlier is the concept of abstract classes and abstract methods. Let's start with abstract classes. Abstract classes are class definitions that cannot be instantiated. That is, no objects can be created of that class type. These are useful in cases where we need to promote inheritance but it might not make sense to create an object of that type. Usually this applies exclusively to classes that don't mimic something concretely defineable in real-life. Let's look at an example:
Unreal Tournament 3
Above are four weapons from Unreal Tournament 3. Clockwise from the upper left we have the rocket launcher, longbow AVRIL, sniper rifle, and bio rifle. For the purposes of our example, we'll focus on the RocketLauncher and the SniperRifle since those are the most common and easy to remember. Ok, now we'll build a simple inherited structure using a base class of Weapon and two subclasses, RocketLauncher and SniperRifle.
public class Weapon
{
public Weapon()
{
}
public virtual String Fire()
{
return "Bam!";
}
}
public class RocketLauncher : Weapon
{
public RocketLauncher()
{
}
public override String Fire()
{
return "Boom!";
}
}
public class SniperRifle : Weapon
{
public SniperRifle()
{
}
public override String Fire()
{
return "Ping!";
}
}
public class UnrealGame
{
static void Main(string[] args)
{
//Create a list of weapons
List weapons = new List();
//Add some weapons
weapons.Add(new RocketLauncher());
weapons.Add(new SniperRifle());
//Wait, this doesn't make sense!
weapons.Add(new Weapon());
foreach(Weapon weap in weapons)
{
Console.WriteLine(weap.Fire());
}
}
}
OUTPUT:
Boom!
Ping!
Bam!
Uh-oh, while the majority of our code worked fine, something very strange is showing up. Our weapons list now has a Rocket Launcher, a Sniper Rifle, and a Weapon... weapon. In the real-world, 'weapon' is a non-concrete idea that can define a whole group of items.
We can fix this with the abstract keyword by making the Weapon class abstract. But before we see the updated code, we should introduce abstract methods. Abstract methods are method signatures defined in a base class that force inheriting classes to provide their own implementations. This is really easy to understand when using our weapon example. Since every weapon has a different style of firing, there's no reason for us to provide a base implementation (in our case, the string 'Bam!'). What we want to do is force other classes that inherit from Weapon to create their own version of Fire().
One other neat thing to notice, a class with an abstract method forces all subclasses to have an implementation but the base class doesn't. For this reason, we cannot create an instance of the base class and it must be abstract. Thus, every class with an abstract method must be abstract itself.
Let's update our weapons example by making Weapon an abstract class and making Fire() an abstract method...
public abstract class Weapon
{
public abstract String Fire();
}
public class RocketLauncher : Weapon
{
public RocketLauncher()
{
}
public override String Fire()
{
return "Boom!";
}
}
public class SniperRifle : Weapon
{
public SniperRifle()
{
}
public override String Fire()
{
return "Ping!";
}
}
public class UnrealGame
{
static void Main(string[] args)
{
//Create a list of weapons
List weapons = new List();
//Add some weapons
weapons.Add(new RocketLauncher());
weapons.Add(new SniperRifle());
//Now this line will generate a compile error!!
//weapons.Add(new Weapon());
foreach(Weapon weap in weapons)
{
Console.WriteLine(weap.Fire());
}
}
}
OUTPUT:
Boom!
Ping!
There are 3 major additions to the above code. First, the Weapon class definition has been modified to include the abstract keyword which is required as it has abstract members. Second, the Fire() function in the Weapon class has been modified to also include the abstract keyword, but more importantly the implementation we had in that class (which printed 'Bam!') has been removed. Remember, abstract methods force inherited classes to write their own implementations so it makes no sense that an abstract method would specify its own implementation. Essentially, an abstract method says, "My class cannot be instantiated (created) and my children need to define their own implementations of me!"
The final major update is in the Main() method where we have been forced to remove the line that created a new gun of type Weapon. Remember, the abstract class keyword mentioned above tells the compiler that no one is allowed to create an object of type Weapon without going through a subclass. In fact, if you were to uncomment that line you should receive a compile/build time error for this reason.
Interfaces
An interface is a classification to something we've already seen in the previous sample. An interface is class that only contains abstract members. Sometimes I tend to lean towards calling a class fully abstract when talking about an interface which re-states the definition of interface.
Now that you know the definition, hopefully you can see the interface we unintentionally created previously. The Weapon class only contains a single method signature and since it's abstract, the class is an interface (i.e. it is fully abstract). We can modify the code one last time and update Weapon to use the interface keyword:
public interface Weapon
{
public abstract String Fire();
}
public class RocketLauncher : Weapon
{
public RocketLauncher()
{
}
public override String Fire()
{
return "Boom!";
}
}
public class SniperRifle : Weapon
{
public SniperRifle()
{
}
public override String Fire()
{
return "Ping!";
}
}
public class UnrealGame
{
static void Main(string[] args)
{
//Create a list of weapons
List weapons = new List();
//Add some weapons
weapons.Add(new RocketLauncher());
weapons.Add(new SniperRifle());
foreach(Weapon weap in weapons)