Classes for dummies - not another Cookie Cutter!
Tags: oop, beginners, python, tutorial
Published: 2021-11-19 16:15:17

##The Setup
It might be because I'm stupid, but it took forever for me to understand what classes were and what you should do with them. Even long after I got them to work by blindly following articles on StackOverflow, I still couldn't wrap my head around how they worked, or what was happening. 

For me, I think a big piece of it, was the analogies used. Call me a simpleton, but I've yet to meet a single cookie cutter that you can use to create different shapes of cookies, let alone have it be your template for an entire bakery. And even though I've never stepped foot in an architecture firm, I presume that their blueprints don't keep changing depending on who's looking at them and what that particular person would like to see. So, having thought about this issue for a while, I have come up with an analogy that at least works for me, so if you're still confused about what a class is, how instantiation works and why you need to use those pesky key words like self in python please read along. (Even though my examples will be written in python, I think there is value for those trying out classes in any language). So, here goes:

Imagine you live in 1970 and you have to call your airline in order to book a ticket. I know this doesn't seem very relevant, but my developer friend told me that I couldn't use stuff that happens on websites, ie software, to explain software. Not sure why, but I will turn a blind eye if you want to imagine booking this ticket online...

Your airline agent will have to ask you a couple of questions in order to make that booking, he probably has a form that he is following. Imagine a notepad just filled with those forms from which he rips off one whenever you call. That notepad filled with those forms is the __*class*__! In this class, you have predefined a certain number of important fields in order to check availability and price. To keep it simple, we'll leave it at 5 questions:

1. Traveler Name
2. Origin
3. Destination
4. Date
5. Class (Coach, Business, First)

##Initiating a Class
Each time the phone rings, and the agent rips off an empty ticket form, we create a new __*class instance*__ (technically once we've filled out the new values, but hey, not as nice of a picture as ripping that form off the notepad). So, you've got the airline agent asking you questions, see here below what that would look like in code:

```
class Ticket:

    def __init__(self, traveler_name: str, origin: str, destination: str, class_: EconomicClasses, date_: str) -> None:
        
        self.traveler_name = traveler_name
        self.origin = origin
        self.destination = destination
        self.date_ = date_
        self.class_ = class_
```

So, what is happening above? We have a class called Ticket. In order to tell it that we're ripping off a new paper in python we need to initialise it. We do this by using the `__init__` method. The first argument that we pass into `__init__` (by convention called self) is telling the class that any values assigned to this, is added to the specific paper we're holding in our hands, not to the class as a whole. More technically speaking, we're passing in the instance as an argument to the initialiser function `def __init__`. So, when I then create an instance of the class like so: `my_ticket = Ticket(traveler_name='Mr. Swarm', origin='Los Angeles', destination='New York', class_=EconomicClasses.COACH, date_='2021-12-24')` the airline agent is writing the information I'm giving him over the phone on the form he just ripped off of his notepad.

With the information I've just given my smoking (it is the seventies after all) airline agent, he can then start to check availability and calculate the price. Swarm Airlines looks at three factors to determine the price of the ticket:

1. Distance of the route
2. Class
3. Availability

```
class Ticket:

    def __init__(self, traveler_name: str, origin: str, destination: str, class_: EconomicClasses, date_: str) -> None:
        
        ...
        self.class_ = class_
        self.distance = get_distance(self.origin, self.destination)
        self.availability = get_availability(self.origin, self.destination, self.date_)

```

Notice here above, that two of the instance variables are initialised through using functions outside of the class (`self.distance` as well as `self.availability`) although using instance variables as arguments for those functions, this because those values aren't necessarily only needed when calculating the price of one ticket. Other parts of your software might want to use distance to calculate fuel costs or availability to determine what campaigns should be used. Visualise this as your air line agent grudgingly finding out the distance and availability in two tables separate from your ticket form, using his ash tray as a ruler, because after all, he is a maverick. Once he has found those values, he enters them into the little formula they have to calculate the price, which he then does using one of those calculators that prints out the numbers on a small roll of paper, but with the paper long gone.

##Methods
###Within the Initialiser
The actual code equivalent of this is using a method (which is just the name we use for a function inside of a class).

```
class Ticket
    
    def __init__(self, traveler_name: str, origin: str, destination: str, class_: EconomicClasses, date_: str) -> None:

    ...
    self.price = self.calculate_price()

    def calculate_price(self):
        
        return round(self.distance * 0.3 * self.class_.value * self.filled_rate, 2)
```

In order to be able to reference the values that we just gave our agent, the agent of course refers to the paper. Which is the same as us referring to self here. This means that self.distance looks up the distance that was set during the initialising of the class, ie the instance variable, and the same goes for self.class_ and self_availability. Seeing as this method is called in the initialiser function, it is executed when we create an instance of the class, which we did above. 
However, you now ask the airline agent to mail you your ticket.

###Outside of the Initialiser
Some people prefer to pick them up over the counter, and so imagine this as a part of the ticket form that is optional. In order to send the ticket by post, they need your address as well as what priority you would like to have the ticket sent by.

```
class Ticket:
    
    ...

    def mail_ticket(self, street: str, number: str, zip_code: str) -> None:
        mailing_info = '\n'.join([self.traveler_name, street, number, zip_code])
        print(mailing_info)
```

As I wish to use the traveler_name, I need to pass in the instance, which of course just is the airline agent reading through the form he's just filled out instead of asking you to spell out your name again. However, your post address wasn't needed in order to create the ticket, so here he asks you for those details. This means that you can't use self.variable_name, because the variables in question haven't been initialised and aren't part of the instance, other than what they are being used for in that particular method. To bring it back to our analogy, there is nowhere that our dear airline agent can look for in the form to find out this information, rather this is new information that is provided to him.
##Summary
So, to recap, our `class` is the notepad with all the forms used by the airline agent when selling a ticket. Really just blanks waiting to be filled out, either directly or by calculations. The individual forms are the `class instances`. The minimum information asked for in order to even create a ticket, are the `instance variables` that are assigned during the initialisation (`__init__()`) method. Note that you don't have to initialise any particular variables in order to create a class instance. The methods are then the functions within the class. Methods can be called when the instance is initialised or as a separate step, depending on whether the execution of the method is optional or not.

I plan on using this analogy to explain even more in-depth concepts such as inheritance and abstract classes. If you enjoyed it please let me know. If you found any of it unclear, please tell me, and I'll try to update it with the feedback that I get. Please keep in mind that I'm still a noob myself :-)