Skip to main content

Design like a programmer, part 4: object oriented design

There are many courses and materials available about the object oriented programming (OOP). Although many of them are very solid and accurate, in my opinion most of them fail to catch the meritum of this programming paradigm. As opposed to other materials, which focus mainly on object relationships, like ‘has-a’ or ‘is-a’, or go into great lengths about code encapsulation, I would like to offer a learning perspective focusing on polymorphism. Once we establish a solid understanding of polymorphism, we’ll see how this paradigm can also be applied to code-free designs and what benefits it can have. This article is a little bit more code-heavy, but hopefully my descriptions and explanations will help you go through it even if code is not your thing.

The common knowledge

When we look at courses explaining what OOP is, they usually put the most emphasis on the structural framework that the paradigm is built upon. We learn about classes that can be instantiated to create objects. We’re taught how each of those classes can have data members inside of them and how those members’ types can be other classes. We are given an example of an Employee class that has an Address member of type Address. Eventually we are presented with a diagram of class inheritance, from which we learn that when we have an Animal class, we can inherit from it when we create a Dog or a Cat class. Essentially, most of this feels like a course on proper code structuring and many a pupil will be left asking themselves ‘what’s the point ?’. Speaking from my own experience, I’d certainly had a very shallow understanding of the paradigm after finishing my first OOP course, mostly seeing it as ‘a way the code is written and structured these days’. It was only after watching some great videos by Zoran Horvat when I started to actually understand what OOP is about.

Polymorphism

Polymorphism is what allows us to define types such that they form inheritance hierarchies. A Dog object that is derived (inherits) from an Animal object is polymorphic in that it can be interacted with both as a Dog and as an Animal. This means, that an entity interacting with a Dog would be able to use both the interactions Dog inherited from the Animal and the ones defined in the Dog itself. Let’s look at this example more closely. An Animal class could contain basic information about the animal in general. This information could be the animal’s Colour, its Age or any other generic property that’s universally shared by all animals. Animal could also expose a set of common functionalities, such as allowing us to Feed or Pet it. All of these informations combined is what we call an interface. In the meantime, Dog’s interface could contain a Bark action and a member variable storing its breed. If an entity was to interact with a Dog object stored as a Dog variable, both the Animal’s and the Dog’s interfaces would be accessible. However, if that same entity was given a Dog object as an instance of Animal, it could not access Dog’s interface at all. Where the fun really begins is when we factor in virtual parts of the interface. If the Animal’s Pet function would be declared as virtual, Dog could override its behavior with its implementation. If we were to Pet an Animal, the outcome of this action could be either:
  • The one defined in the Animal if Dog does not override Pet ,
  • The one defined in the Dog if it overrides Pet,
  • A combination of the two if the Dog overrides Pet and in its execution it contains a call to its parent’s implementation.
There is one position that appears to be missing in the list above. If the Dog can call its parent’s implementation of the Pet function, why couldn’t the Animal call those of its children?
It’s actually pretty difficult to give a compelling answer, so let’s just say it simply defeats the purpose of polymorphism and stands in the way of scoping the visibility and complexities of long inheritance trees. One of the most powerful features of polymorphism is that an entity triggering the Pet action doesn’t have to reference the implementations of all the classes that might be derived from Animal. In fact, it should not know that these classes exist at all, or of the fact that Animal is not a final implementation. The moment we start referencing a base class’ children in its implementation, we pull those references to the entities that interact with Animal. Not to mention that we create a circular dependency between the base class and the child class it references, but this is a bit of a more programming-related concept which I will not go into more details in this article. It’s simply not a good idea. If you ever find yourself in need of doing such a thing, you should treat it as a flaw in your design coming to the surface.
When we follow the best polymorphism practices, the number of our systems’ external dependencies goes down considerably, making them easier to expand and maintain. A huge win in my book. Of course life is not at all black and white, and there are cases, when we need to know more about the object we’re dealing with. Say we wanted to predict how an Animal would react to us Petting it. After all, it might be a deadly beast that could kill us on the spot. Even in situations like this, we should avoid trying to find out the specific type of the object we’re given. Instead, we could add a virtual IsDangerous query to the Animal and have concrete types override it. Thus, each subtype of Animal would return a value that would allow the consumer of the Animal object to make an appropriate decision.
In our dangerous case, we would ask the animal whether it’s dangerous, and if the answer is positive, we would brace ourselves before proceeding to Pet. All that without knowing which particular Animal we’re Petting. Another approach could be to define a virtual TryPet function in Animal. Then, the virtual IsDangerous query could be made protected (accessible only to the types derived from Animal) and called inside the Animal’s TryPet function to determine, whether it is safe to Pet. The nice thing about this approach is that entities interacting with Animal would have even less knowledge about the inner workings of the Petting process. That would come at a cost, though, because the consumer of Animal would lose access to the IsDangerous query, making it impossible for it to define some custom logic if need be.
In most situations it should be fine to go with the second option. If, for whatever reason, we still need to interact with Animal using a concrete interface of any of its derived classes, we should take the time to design this process so that it does not involve iterative casting. A short explanation of what casting is for those unfamiliar with the concept. A cast is an attempt to create an object of specific type using data of another object. Depending on the size of the objects we operate with, this can be not only quite expensive, but also very risky if we cast dynamically. Casting is used mostly to cast between objects that share the same type family. In our example, a Dog object could be safely cast to Animal, because Dog is itself also Animal thanks to polymorphism. However, if we tried casting an Animal to a Dog, this could open a pandora box of issues. We don’t want to go there, risking our application to crash immediately, or even worse, cause crashes later down the line, making them quite difficult to debug.
Now when we’re all on the same page with the concept of casting, let’s get back to the issue at hand. One solution I’ve seen used often is to define an enumeration listing all derived subtypes and have each Animal implementation override a query function that returns an item from the enumeration. With such a solution in place, iterating through a number of Animal objects in search of a specific one would see us compare its return enumeration value to the one we’re after. Only when the enumeration values would match would we cast the Animal to the type we need it to be. This cuts the computation time and reduces the risk inherent to casting. Of course, as with anything concerning design or programming, the solutions mentioned here are by no mean the definitive ones. There could very well be cases where we might need access to a very specific interface of an object. However, interacting with a generic interface should generally be preferred over interacting with a concrete implementation.

Deal with interfaces, not objects

This is one of the very first concepts I could not wrap my head around when I first encountered it. Initially, it looked to me like writing a lot of additional code for no benefit I could see. It was only later on, after having dealt with complex systems when I started to see the point. I hope you will have an easier time embracing the concept after the preceding section. We already established that a class defines its own interface, and that derived classes inherit their base class’ interfaces. We also know that thanks to this, an instance of a B class that is derived from class A can be stored and accessed as an instance of A.
Now, some environments, like C# language or Unreal Engine’s Blueprints, allow us to assign pure virtual interfaces to classes. When a class is assigned a pure virtual interface, it is forced to implement the functions or methods defined in this interfaces. Thanks to this property, such a class can be stored and accessed as an instance of that interface. We can also assign multiple such interfaces to one class. The best thing about all of this is that those pure virtual interfaces can be assigned to any class. Let’s look at an example. An Employee class can have both an ISerializable and an IPropertyContainer interfaces assigned to it. This would allow us to pass the Employee to a Serializer object that serializes ISerializable objects into XML, JSON or any other format, or to a PropertyFactory to have it presented as a set of editable fields in the UI of our game or application as an IPropertyContainer. Neither the Serializer nor the PropertyFactory would need to access the Employee’s source code - they would deal with the ISerializable and the IPropertyContainer interfaces respectively and would only reference these interfaces.
The power of pure virtual interfaces lies in the fact that they allow us to simplify our inheritance trees considerably. I’ve used this approach multiple times in my desktop applications, where I would assign an INamedProperty interface to properties I wanted to allow my users to rename, all the while keeping those properties inheritance tree unaffected by this functionality. After all, if I wanted to divide my properties hierarchy into Named and Anonymous, this would most likely see me duplicate a lot of the code, which is neither clean nor maintainable in the long run. Thanks to those two small interfaces, I was able to introduce this simple functionality with minimal effort and without modifying the structures I have already built. This also further helps to cut the number of dependencies in our systems. A sizeable class with many dependencies can be stored or interacted with by any entity that includes or uses only the interface source code, staying free from the complex net of code references a concrete class implementation would likely bring. The same concept can be used in other programming languages as long as they support the notion of pure virtual functions/methods and/or multiple inheritance. I’ve not yet come across a scripting environment that offered these functionalities, but it’s probably just a matter of time before we see them.

The designer’s perspective

But what do any of these nerdy details mean to designer? Well, there are circumstances and environments where polymorphism and/or communicating with an entity through an interface can be leveraged in designs. For example, in Unreal Engine Blueprints it’s possible to access a Blueprint through any of the interfaces it implements. A great example of this directly from the Unreal Engine user guide:
The use of Blueprint Interfaces allows for a common method of interacting with multiple disparate types of Objects that all share some specific functionality. This means you can have completely different types of Objects, such as a car and a tree, that share one specific thing like they can both be shot by weapon fire and take damage. By creating a Blueprint Interface that contains an OnTakeWeaponFire function, and having both the car and the tree implement that Blueprint Interface, you can treat the car and the tree as the same type and simply call the OnTakeWeaponFire function when either of them is shot.
This is essentially the same as in code, except a designer can define interfaces by themselves and then add them to any blueprints they might want to interact with in a universal manner. Blueprints also fully support object polymorphism, so that’s definitely something worth having an in-depth knowledge of if you’re designing in Unreal Engine. More often than not, we can also build our designs leveraging polymorphism. Instead of designing our logic around specific types of objects, we can design their elements around certain interfaces we’d expect our objects to share. That could mean designing a PowerGrid system that deals PoweredDevice objects instead of Computers or AccessPanels. Or writing a state machine system that deals with virtual State class objects, so that the designers can create each state implementation overriding the functions the State class exposes as virtual (such as CanEnter, OnEnter, ExitTo etc.).

Summary

The sky's the limit. When designing complex entity logic, especially when there are multiple types of objects that we might want to interact with through a common interface, it’s definitely worth it to invest the time to build a solid, polymorphic solution. Such an investment will always pay off, sooner or later, depending on the size of the project. When polymorphism is leveraged to its full potential, the designs become cleaner, each individual component’s concerns are more separated, and the number of dependencies goes down significantly. Personally, I’m happy to see these concepts gaining more and more traction, especially in design teams. Heck, some of gameplay designers I know can leverage polymorphism more effectively than many programmers. And that’s great! I can’t wait to see what advancements they can make in the field of design in the near future.

Comments

Popular posts from this blog

Float precision and Time in Unity

Float precision and Time in Unity Recently, I was tasked with addressing an interesting issue that occured in a Unity game. After the game run for about 2 days, it virtually stopped streaming in map assets during world locomotion. Assets were streamed in with a huge delay (up to x-teen seconds) and it simply wouldn’t do. After finding out what caused this strange bug, I thought it would make an interesting article to share with you. Both the problem, and the process of finding its origin were quite an interesting experience. Debugging Since the problem only occurred after at least 2 days of ‘soaking’, I knew time is going to play a role here. I started investigating the issue by looking at the custom streaming code used in the game. It consisted of a bunch of asset loading and unloading functions, called every tick. At the start of each tick, before calling the functions, the code would cache the current Time.realtimeSinceStartup, which is a timer managed by the Unity engine tha

Array property customization in Unreal Engine 4

With the drive towards as much data-driven gameplay as possible, there comes a need for easy to edit and nice to interact with data assets that can accommodate all that data. While Unreal Engine 4’s UI framework allows us to display a wide range of data structures, its default handling of nested properties can quickly result in deeply nested structures that need to be expanded in order to edit them. This can really hurt productivity in some scenarios. Fortunately, we have the ability to fully customize how our data is laid out. While there are nice tutorials all around the web that explain how to customize our custom classes and structs, I’ve not been able to find an article that would explain how one would go about customizing how collection types display their data. In this article I will describe how to customize the display of an array property in UE4. I will follow it with the one for customizing maps display in the near future. Defining the issue I intend to explain the pr

My CD Projekt RED story, part 3

My CD Projekt RED story, part 3 We are now 2 years into my CD Projekt RED adventure and so far, it’s been mostly smooth sailing. I got promoted to a QA Analyst position, an equivalent of a specialist position for developers. I was earning around 2600zł or $650 per month, which would sometimes go as high as 3500zł with additional crunch pay. At this point I felt fairly rewarded for my efforts and could even start planning my wedding. I also received the first bonus for my participation in the creation of both Witcher 3 expansions. This amounted to roughly 13.000zł or $4250, which was an amount of money I had a hard time wrapping my head around. I still lived in a single room in an apartment I shared with other people, but at this point it was mostly my own choice. I’ve had my own wedding on the horizon so I needed every coin I could save for the occasion. Getting out of QA It was during that time that I decided I want to transition away from doing QA work. I approached the audio te