Why is Object-Oriented Programming Useful? (With a Role Playing Game Example)

This blog post is those still new to programming and have probably heard about "object-oriented programming", "OOP", "classes", "inheritance/encapsulation/polymorphism", and other computer science terms but still don't get what exactly OOP is used for. In this post I'll explain why OOP is used and how it makes coding easier. This post uses Python 3 code, but the concepts apply to any programming language.

There are two key non-OOP concepts to understand right off the bat:

  1. Duplicate code is a Bad Thing.
  2. Code will always be changed.

Aside from small "throw-away" programs that are written for some single task and only run once, you'll almost always need to update your code to either fix bugs or add new features. A large part of writing good software is writing software that is readable and easy to change.

If you have copy/pasted code in your program, changing it requires making the same change to multiple places in your program. This is problematic. If you miss applying the change to some places, you'll fail to fix the bug everywhere or implement the new feature inconsistently. Duplicate code is a Bad Thing. Having duplicate code in your program is setting yourself up for bugs and headache.

Functions let you get rid of duplicate code. You write the code in a function once, and just call the function everywhere your program needs that code run. Updating the function's code updates it everywhere the function is called automatically. Just as functions makes updating code easier, using object-oriented programming techniques also organizes your code to make changes easier. And code will always be changed.

The Role Playing Game Example

Most OOP tutorials stink. They have "Car" classes with "Honk" methods and other examples that are unrelatable to the actual programs that new coders write or have used before. So this blog will use an RPG-style video game (think World of Warcraft, Pokemon, or Dungeons & Dragons). We're used to thinking of things in these games as a collection of integers and strings. Check out all the numbers on this Diablo status screen or D & D character sheet:

Taking out the graphics from these RPG video games, the characters, armor, and other objects are just a bunch of integer or string values in variables. Without using object-oriented concepts, you could implement these things in Python code like this:

The above variable names are pretty generic. To add monsters in this program, you'll need to rename the player character's variables and make new ones for the monsters:

But of course you'll want to have more than one monster, but then you'll have variables like monster1Name, monster2Name, etc. That's a poor way to code, so maybe you'll make the monster variables into lists:

Then you could just have the first goblin's stats be the values at index 0 in the lists, the dragon's stats are at index 1, and the other goblin's stats are at index 2. This way you could have multiple monsters contained in these variables.

But this kind of code easily leads to bugs. If your lists get out of sync, the program will no longer work correctly. Say the player vanquishes the goblin at index 0 and the program calls a vanquishMonster() function. But the function has a bug and deletes the values from all the lists except (accidentally) monsterInventory:

The monster lists end up looking like:

Now it looks like the dragon's inventory is the same as the old goblin's inventory. And the second goblin now has the inventory the dragon had. This is quickly getting out of hand. The problem is that you have one monster's data spread out over multiple variables. You could fix this by putting a single monster's data into a dictionary, and then have a list of dictionaries:

G'ah. This code is getting complex. For example, the inventory of a monster is a dictionary-in-a-dictionary-in-a-list. What if things like spells or inventory items also have their own attributes and need to be dictionaries? What if an inventory item could be a sack, which itself contains other inventory items? This monsters dictionary would get intense.

This is something that object-oriented programming can solve by creating a new data type.

Creating Classes

Classes are the blueprints for a new data type in your program. Object-oriented programming provides a new approach for modeling the armor, monsters, etc. much better than a hodge-podge of lists and dictionaries, even though OOP concepts it take some getting used to.

In fact, since the hero will have all the same features of monsters (health, inventory, etc.) we can just have a generic LivingThing class that the hero and monsters share. Your code can be changed to look like this:

Hey look, using classes has already cut down our code in half since we can use the same code for both player characters and monsters.

In the above code you are defining a new data type/class (pedantics aside, but the two terms are basically the same. See also: Stack Overflow - What's the difference between a type and a class?) called LivingThing. You can have LivingThing values/objects (again, two terms for basically the same thing) just like you can have integer values, string values, or boolean values.

Some Python-specific details about the above code:

The above is a class statement that defines a new class, just like def statements define a new function. The class's name is LivingThing.

The above defines a method for the LivingThing class. "Method" is just the name used for functions that belong to a class. (See also: Stack Overflow - What is the difference between a method and a function?)

This particular method is special. The name __init__() is used for the "constructor function" (also called a "constructor method", or "constructor" or "ctor" for short) for the class. While a class is a blue print for a new data type, you still need to create values of this data type in order to have something that you can store in variables or pass to functions.

When called, the constructor creates the new object, runs the code in the constructor, and returns the new object. This is what the hero = LivingThing('Elsa', 50, 80, {}) line is. No matter what the class name is, the constructor is always named __init__.

If a class has no __init__() method, Python supplies a generic constructor method for your class that doesn't do anything. But the __init__() method is a good place for code that does the initial setup of a new object.

In Python, the first parameter for methods self. The self parameter is used to create member variables, explained next.

The body of the constructor function is this:

This seems a bit repetitive, but what this code does is create member variables for the object being created by the constructor. Member variables will begin with self. to show that they are member variables belonging to the object, and not just regular local variables in the method.

Calling the Constructor

The constructor call in Python will just look like a function call using the the class's name. So LivingThing() is the call to the LivingThing class's __init__() constructor. The above LivingThing('Elsa', 50, 80, {}) call creates a new LivingThing object is created and stored in the hero variable. The above code also creates LivingThing objects for the three monsters and stores them in the monsters list.

And here's where we begin to see the benefits of object-oriented programming. If another programmer is reading your code, when they see the LivingThing() call they know they can just look for "class LivingThing" and find out the details of everything they need to know about what a LivingThing is.

But an even bigger benefit comes when you try to update your LivingThing class.

Updating Classes

Say you wanted to add "hunger" levels to your RPG. If a hero or monster has a hunger level of 0, they are not at all hungry. But if their hunger level is above 100, then they take damage and their health value decreases each day. You could change your __init__() method to this:

Without changing any other line of code, every LivingThing object in your game now has a hunger level! You don't have to worry about some LivingThing objects having the hunger member variable and some not: the very definition of what a LivingThing is has been updated.

You also don't have to change any of the constructor calls since you didn't add a new hunger level parameter to the __init__() method. That's because 0 is a good common-sense default value for a new LivingThing object's hunger level. If you do add a new parameter to __init__() for the hunger levels, you'll have to update all the code that calls the constructor. But this is true for any function anyway.

If your RPG has a lot of default values for things, you can avoid a lot of "boilerplate" code by using classes with a constructor that sets the default for you.

Methods

Methods are useful for running code that affect the object itself. For example, you could just have code that changes the health of a LivingThing object directly:

But this isn't a very robust way to handle taking damage. Lots of other game logic needs to be checked whenever something takes damage. For example, say you want to check if a character dies each time they take damage. You would need code like this:

The problem with the above approach is that you would need that check everywhere your code decreases the health of a LivingThing object. But duplicating code is a Bad Thing. The non-OOP way to prevent duplicate code would be to put it in a function:

This is a better solution because any updates to takeDamage() (such as factoring in armor, protective spells, bonuses, etc.) just have to be added to the takeDamage() function.

However, the downside is that when your program grows in size, it's easy for takeDamage() to get lost in among them. It isn't so clear that takeDamage() is related to the LivingThing class. If you have hundreds of functions in your program, it will be hard to figure out which ones are related to the LivingThing class.

The solution is to turn this function into a LivingThing method:

Once your program has many classes, each with many methods and member variables, you will begin to see how OOP can help organize your programs to be more manageable.

Public and Private Methods

Methods and member variables can be marked as public or private. Public methods can be called and public member variables can be set by any code, inside or outside of the class. Private methods can be called and private member variables can be set only by code inside the object's own class.

In some languages such as Java, this "can be called/set" is strictly enforced by the compiler. In Python, there's no such concept as "private" and "public". All methods and member variables are "public". However, the convention is that if a method name begins with an underscore, it is marked as private. This is why you'll see methods such as _takeDamage(). Nothing prevents you from writing code that calls private methods or set private member variables from outside the object's class, but you've been fairly warned not to do that.

The reason for having this public/private distinction is to set expectations for how the class interacts with outside code. (See also: Stack Overflow - Why "private" methods in the object oriented?) The programmer of the class expects that other people won't write code that calls the private methods or sets the private member variables.

For example, the takeDamage() method factors in checking for death if health falls below 0. You may want to add all sorts of other checks in the code. Things like armor, agility, and protective spells could factor in to reduce the damage taken. The LivingThing object might be wearing an enchanted cape that heals them by adding the damage amount to their health instead of subtracting. All of this game logic can go in the takeDamage() method.

But all of OOP organizing is for nothing if you accidentally put this code in there:

That hero.health -= 50 will subtract 50 points of health, without taking into any consideration what armor Elsa is wearing, if she has protective spells, or is wearing that enchanted healing cape. This code bluntly decrements health by 50.

It's easy to forget about the takeDamage() method and accidentally write code like this. This doesn't check if the hero object's health member variable has dropped below 0. The program continues as though Elsa is alive even if she has negative health! This is a bug we can avoid with public/private members and methods.

If you rename the health member variable to _health and mark it private, then it's easy to catch this bug when you write it:

In a language like Java where the compiler enforces private/public access, it would be impossible to write a program that illegally accesses a private member or method. Object-oriented programming helps prevent these kinds of bugs.

Inheritance

Using the LivingThing class for dragons is nice, but dragons have a lot of other qualities in addition to the ones provided by LivingThing. So you want to create a new Dragon class that will have member variables like airSpeed and breathType (which can be string such as 'fire', 'blizzard', 'lightning', 'poison gas', etc).

Since Dragon objects will also have health, magicPoints, inventory, and all the other things that LivingThing objects have, you could just create a new Dragon class and copy/paste all the code from your LivingThing class. But this would result in duplicate code, which is a Bad Thing.

Instead, make a Dragon class that is a subclass of the LivingThing class:

This is effectively saying, "A Dragon is the same as a LivingThing, with some additional methods and member variables". When you create a Dragon object, it will automatically have all the same methods and member variables as LivingThing (saving us from duplicating the code). But it could also have dragon-specific methods and member variables to it. Further, any code that deals with a LivingThing object can automatically handle a Dragon object because Dragon objects already have the LivingThing members and methods. This principle is called subtype polymorphism.

In practice, inheritance is easy to abuse though. You must be certain that any conceivable change or update you make to the LivingThing class would also be something you would want the Dragon class and every other subclass of LivingThing to also have. This might not always be so straightforward.

For example, what if you created Monster and Hero subclasses of the LivingThing class, and then created FlyingMonster and MagicalMonster subclasses from Monster. Would the new Dragon class be a subclass of FlyingMonster or MagicalMonster? Or maybe just its own subclass of Monster?

This is where inheritance and OOP start to get tricky and religious arguments over the "correct" way to design classes come about. I want to keep this blog post short and simple, so I'll leave these topics as exercises for the reader to look into. (See also Stack Overflow - Prefer composition over inheritance? and Wikipedia - Composition over inheritance)

Summary

I hate programming tutorials for beginners that start with object-oriented programming. OOP is a very abstract concept. Until have some experience and are writing large programs, you won't understand why using classes and objects makes programming easier. Instead, the neophyte is left with a steep learning curve to climb and no idea why they're climbing it. I hope the RPG example has at least given you a glimpse as to why OOP can be helpful. There is MUCH more to OOP. If you want to learn more, try googling for "python object oriented design" or "python design patterns".

If you are still confused by OOP concepts, feel free to write programs without classes. You don't need them and they can result in over-engineered code. But once you have some coding experience under your belt, the benefits of object-oriented programming will become apparent. Good luck!

26 thoughts on “Why is Object-Oriented Programming Useful? (With a Role Playing Game Example)

  1. I would not recommend this as a tutorial for beginners, as it appears that you (the author) does not fully understand Python as a language. In particular, there are a few gross mistakes.

    1) There is no distinction between public/private in Python. Python is a language of consenting adults, nothing is private. Underscore names are meant for magic methods (__init__, __lt__, __add__, etc...) and for typeclass-specific functions (e.g. Foo.__bar(self) translates internally to _Foo_bar(self), and if inherited to some class Foz, then Foz.__bar(self) will translate to _Foz_bar(self), but will otherwise allow you to write different code for the same interface).

    2) __init__ is not a constructor. It "initializes" instance slots / variables. Notice that __init__ takes the argument self, which means the instance (self) is already constructed by the time __init__ is called!!! If you want to make a constructor, use a class method (using @classmethod as the decorator) and make a constructor that way (e.g. MyClass.constructor(1) vs. MyClass.alt_constructor("1"))

    Barring the "gross mistakes" above, there are other issues, such as the focus being placed on inheritance instead of on composability. Inheritance is fine, but you need to remember (1) the "is a" relationship and more importantly that it may be better to compose or aggregate classes and interfaces instead of inheriting everything. This is more tractable, more abstract, and easier to reason about. The major difference is that you might inherit "dragon" from LivingThing. On the other hand, you could instead make a dragon class that aggregates a HealthMeter class, a MagicMeter class, and Inventory class, a BreathesFire class, etc. Rather than try to structure relationships and come up with fancy ontologies, it's better to build your objects from smaller ones from the ground up.

    For more information on how Python's classes / object system works, I highly suggest https://www.youtube.com/watch?v=HTLu2DFOdTg. I think it would be a grave error to start beginners off by using the terminology and examples you're using here, especially so when you're outright wrong with some things, such as calling __init__ a constructor, or trying to enforce public / private relationships in a language where there is no such inherit relationship.

    1. 1) I point this out in the blog post: "In Python, there's no such concept as "private" and "public"." and the rest of that paragraph. I completely understand this about the Python language, and point it out in this tutorial.

      2) __init__() essentially does the same thing as a constructor function, though I guess you can make the argument that __new__() is the constructor function. But this gets into pedantic details that new programmers (and even experienced programmers) won't have to deal with. Discussion on this here: https://stackoverflow.com/questions/8985806/python-constructors-and-init and https://stackoverflow.com/questions/674304/pythons-use-of-new-and-init

      3) I point out the problem of abusing inheritance as well: "In practice, inheritance is easy to abuse though..."

      1. Hehehe, haters gonna hate... The guy is nuts! I think you should delete his post because could be misinterpreted by someone new to Python. He used some technical details (not important at all for this tutorial) to say that you make "gross mistakes".

        By the way, very nice intro. I fully agree with you that "Most OOP tutorials stink". Will share this a lot.

    2. PEP-8 declares that an underscore prefix on a method is an "internal use indicator", and uses the example that "from M import * does not import objects whose name starts with an underscore". That certainly sounds a lot like "private" to me. Even "consenting adults" make exceptions (in both directions) about what they might allow. Consenting adults don't walk around naked all the time to demonstrate their maturity.

      I've been using Python and C++ for 10 years and while I can tell you the difference between a "constructor" and an "initializer", it's a pretty subtle one which you almost never need to care about in real life. In fact, the biggest trouble I can imagine from a newbie calling __init__ a "constructor" is that some anonymous person on the internet will post a comment on a blog, and will call this terminology a "gross mistake" and use three exclamation marks to signify their extreme disapproval!!!

      You say it's a "grave error" to start beginners on anything less than a 45-minute lecture on the Python object system (including @decorators and __slots__), but every single Python programmer I've ever met has managed to overcome this apparent catastrophe in their education.

  2. I really, really, really like this explanation. I have one minor suggestion to make, and that is for you to add an explanation about self being just a convention in Python (unlike this in Javascript for example).

    I have written a different introduction to OOP which, in the first two short sections, tries to provide an explanation about the OOP notation in Python, including the use of self as a convention. However, my introduction strongly relies on familiarity with either the introductory tutorial I wrote and/or the programming environment I used, whereas yours is completely self-contained.

    Finally, a small nitpick: __init__ is not a constructor in Python. Before __init__ is called, the instance has already been created and __new__ has already been called. In some other languages, there is a method called init which is a true constructor (as I understand it).

  3. What DeeEff said. You've given a perfectly fine explanation of Java's OO model, though it is a bit of mystery why you have written it in Python. :-/

    You remind me of my friend who recently wrote a book about Python, in which it says "A variable is a name for a memory location in which an object can be put". When I point out to him that Python doesn't work that way, he says "but a lot of other languages do".

    Of course. But if you're not writing about Python, be honest. Beginners usually want to learn _one_ programming language, not five of them simultaneously. Hardcore Java programmers _will_ write Java-like Python code when they start to learn Python, that's inevitable. But beginners don't have to go through that phase. It's no use.

    Please put some kind of disclaimer, "this is how Java OO can be (sort of) emulated in Python. But this is not Python OO". And please watch that Hettinger's video. You have a good knowledge of Python's imperative features, this article is under your league.

  4. "OOP is a very abstract concept."

    Agreed!

    "I hope the RPG example has at least given you a glimpse as to why OOP can be helpful."

    I have an understanding of why (Python-style) OOP is helpful for RPGs, but RPGs seem like they're just an OOP system which is implemented and simulated by hand (with dice) for fun. Why do so many OOP tutorials implement RPGs, and not any other kind of programming problem? It seems like you're begging the question here.

    The vast majority of software problems I've had to solve in my life don't look anything like an RPG implementation, regardless of whether they use OOP or not. I've worked on commercial million-line codebases which were not OOP, and I can't say that this made them any less able to be modified, or any more susceptible to code duplication. There are lots of other factors that I find help far more than OOP.

    There are some problems (like RPGs) which match very well to OOP. (Similarly, if you're writing a Python interpreter, Python is one of the best languages for it! You'd have to be nuts to use Forth or APL for that particular job.) That doesn't mean most programs are necessarily a good fit for this style, though.

  5. I have thoroughly enjoyed this post, and reading through it I did pick up on the small bits and pieces that compared the different languages, despite DeeEff's and Veky's critiques.

    Problem is, even in the comments above it can be clear that even in the programming community small nitpicks can really darken an otherwise very helpful resource, even despite annotations being placed to reflect such "nitpicks". Not to mention you've even had links and details to justify your position. Good enough in my book.

    The point that you were trying to make was clear, and it was intended as a theoretical representation as to why OOP is better than simply stringing things together. And the links outside of "if you want to learn more" links to "python object oriented design" or "python design patterns" were good enough for me to get a grasp of the concept and move onto actual tutorials.

    Keep up the great work, as it has really helped me even understand the idea of public and private classes a bit better than I had originally anticipated. Cheers!

    1. Python injects it when you make the method call. So if you have this class:

      class SomeClass():
      def someMethod(self, spam, eggs):
      pass # some code here
      x = SomeClass()
      x.someMethod('hello', 'world')

      ...then Python automatically passes the object in x for the self parameter. (And the 'hello' and 'world' arguments get passed for the spam and eggs parameters.)

  6. Minor typos:

    "In Python, the first parameter for methods self" ->
    "In Python, the first parameter for methods is self"

    "call creates a new LivingThing object is created and stored" ->
    "call creates a new LivingThing object which is created and stored"

    "it's easy for takeDamage() to get lost in among them." ->
    "it's easy for takeDamage() to get lost."

  7. Thanks so much for writing this—this would've saved me countless hours.
    I wish I found this earlier when I started learning programming.

    I didn't truly understand the importance of classes or inheritance until I tried making my own Pokemon (GBA) game. All Pokemon have HP (health) and have Experience points. So I made a Pokemon class with those parameters. And I would make subclasses that inherit those traits, but have their own methods. Something clicked in my head. Aha!

    Again, I wish I found this earlier, but I also have a deep appreciation for people who write articles like this because of the struggle to learn it on my own. I'm sure there will be many others who will appreciate this post. Thanks for contributing to the Programming community.

  8. Excellent explanation. I understand some of the principles of OOP (from reading Barry Burd's Java for Dummies) and have been teaching my son Python. We are at early stages and haven't touched OOP in our coding so far but I always try to explain OOP concepts to him when I can and do so in terms he will "get" and RPG's are one of those things he enjoys.

    This was very informative. Thank you.

  9. Thanks for the Great post!!
    I am new to programming and chose Python as my first programming language,
    and this tutorial helped me understand the concepts of OOP language.
    You wrote at the end that you don't like to teach this topic to beginners, but for me, it helped a lot!

  10. This post is very helpful.
    I am new to OOP and not fluent is coding at all.
    I watched like 20 OOP tutorial videos but this makes most sense.
    Thank you.

  11. What a hugely fun article! I want to blow off work now and do some fun programming. I hope you follow up this article with more in depth OOP through RPG explanations.
    class Please ()
    class PrettyPlease (Please)

  12. Hi, Great article. I know that the most important here are the concepts, but I really feel this concepts would be easier to understand and display using Java, since it's a strong typed language and we are able to define private members. Can I write a Java version of this article and link to yours?

  13. I've taken programming 1 (w/ java) and programming 2 (w/ c++), you have given me my "ahh-ha" moment. Thank you for this!

    Btw the first commenter obviously didn't read the "pedantics aside" note...

  14. Good morning, Al, and thanks for this article.
    I'm a student of development of cross platform apps in Spain, and we are ready to get into the OOP, finally. This read grants me a great understanding of the paradigm.

    I run a blog, called Geekstorming!, where I write about technology, augmented reality, programming... and I thought this could be helpful for me and my classmates. May I translate this to the blog? Attributed, no need to mention. Geekstorming! URL, in this comment.
    I'll keep reading about 'inheritance vs composition', a really interesting and immersive topic.

    Best regards, Elena (aka Beelzenef)

Leave a Reply

Your email address will not be published. Required fields are marked *