As a non-professional and in essence beginner programmer who likes to learn about programming and writes simple to moderately complicated scripts I have to say I am currently in the mindset of not needing classes.
In fact the first part of the article I was like: yeah no need for classes there, a function is way simpler.
Now like I say I write fairly simple scripts, I mostly automate manual processes, but even after reading the article I'm still not sold on classes. I have to admit using dictionaries/associative arrays as the author showed and have often wondered over the difference between classes and objects. But I still don't see the problem.
I known my lack of knowledge and understanding is showing here; I'm no expert and have never written any massive software.
Can someone point me to an example where a class is more suitable than a function working on an object like an associative array? Genuine question from the ingnorant.
It's mostly a matter of scale. In some sense, classes are just "functions working on an object like an associative array", x.y(z) is a fancy way of writing y(x, z) and python even makes that explicit. The point is they're designed to keep those functions in the same place and not leaking all over the code, keep similar utility and data in the same place.
If you are writing small scripts, it really doesn't matter. If have a million lines of code and thousands upon thousands of source files, any structure you can impose on it makes a big difference.
That said, OOP is just one paradigm of imposing structure.
> Can someone point me to an example where a class is more suitable than a function working on an object like an associative array?
I/O or resources.
Any time you're interacting with a stateful system like an API or a database, there's inevitable setup and maintenance to keep access to that resource sound. Additionally, you rarely care how that resource is kept coherent, you just need to access it at parts of your program. Without a class or some system that keeps some bookkeeping data around with methods, every function has to have that bookkeeping state passed in as a handle, and all of a sudden all functions need to know something about how setup works, as opposed to what it does. This is a rare instance of where namespaces + globals relative to those namespaces reduce complexity; even languages like Clojure have DI "objects" for resource access, and lots of languages that have IO monads are really a data structure over some state + functions.
This setup of objects as things you can't interact with directly--aka not data--is the original one in the literature. Once upon a time you needed to interact with a device driver or a mouse or a screen or a hard drive, and logically encapsulating that device was useful. You'd then interact via "messages" dispatched to the object which are effectively what methods are these days (the term "dispatch" is still used in the literature). It also makes mocking out that physical real-world thing easier.
Notably, objects never were meant as a way to represent data. I'm one of those functional programming weirdos that likes most things to be immutable, and I have simple stateless functions in an enclosing namespace. I pass simple structs (or case classes) of data around in those functions and it works well for me. But the I/O or externally interfacing parts of my program are always in specific places in my program, and they always use dependency injection, and it's always obvious what they are. They look like a sandwich where those objects are at the edges of the program (APIs/ingress and database/egress), but the meat of the program is in the inside and is boring pure functions.
> Without a class or some system that keeps some bookkeeping data around with methods, every function has to have that bookkeeping state passed in as a handle, and all of a sudden all functions need to know something about how setup works, as opposed to what it does.
Classes make so much sense for me in this space, that I can hardly see why people would defend not using them.
Additional to your points, having things in classes with clear interfaces makes it possible to easily integrate new underlying functionality without changing the contract. For example you only store things on S3, have a nice class with store methods and so on. You can just add another different storage backend. How would you do that using functions only? If they also need to take care of setup? A nightmare.
>Can someone point me to an example where a class is more suitable than a function working on an object like an associative array? Genuine question from the ingnorant
1) A class is basically a contract known at compile time. If there's a mistake (for example, you're trying to reference a non-existing property), it can be caught before the program is even run, saving time.
2) A class often acts as a namespace/facade, i.e. you encapsulate a piece of logic inside a class, and its users only see a simple-to-use interface, they don't have to know about implementation details (such as what dictionary must have what keys).
3) A class can be made self-validating. For example, there can be a rule "price can't be negative". If we have a proper "Price" object, it can validate this rule itself, whenever it's constructed/updated. Thanks to data encapsulation, we can prevent users from updating the price value directly, only by asking the object to update itself (and it can refuse to do so!) -- making sure it never can be below zero. If you use free-standing functions on associative arrays, you can forget to validate certain rules in some places, leading to data corruption.
3) Classes allow polymorphic behavior. I.e. implementation can be changed dynamically based on the current context.
4) If you follow domain-driven design, it's easier to discuss and model new requirements with "domain experts" (not necessarily programmers) using the concept of "objects", with well established properties and behavior attached to them, rather than some ad hoc associative arrays and functions.
Not all of this is unique to OOP, of course. OOP is just one of the tools.
There probably isn't a single example. But "quantity is a quality in itself". Classes sort of evolve when you need to manage dozens or hundreds of those associative arrays and don't want to keep in your head exactly what keys are in a given array. And when you have a dozen of arrays and a hundred of functions, which functions go with each array.
In itself every element is easy, but together they are hard.
I expected one of the other answers to encapsulate how I feel about this, but they didn't, so here goes:
You have probably noticed in your "fairly simple scripts" that your language has some notion of types. Your variables are one type or another type, in some languages they can change type and in others they mustn't, but likely the language has the notion that, for example, while dividing 9 by 3 is a reasonable thing to do, dividing your array of business departments by the name of a CSV file is not.
Now, these types aren't like the elements (whether classical or chemical) they were chosen by the language authors. There are probably a handful of obvious ones, probably strings like "Clown" and numbers like 19.03 are different types, your dictionary isn't a number, and if your scripts are allowed to access files probably a file "Handle" or equivalent isn't the same kind of thing as a dictionary.
So, in this sense a class is merely a type that you can define in your program.
This has any number of useful properties, for example suppose you deal with a lot of Social Security Numbers, if you had an SSN class it'd be clearer that, while SSNs might well be "numbers" in some sense, you can't go around summing them up, any place that seems to be happening is a bug waiting to happen, as a class there's no reason it should be possible to do arithmetic on SSNs. Your chosen language might provide easy ways to say e.g. "This dictionary is for numbers, don't put anything else in it" and chances are if that's available you can likewise say "Only for SSNs" and now there's no risk you accidentally got the total number of people processed as a Social Security Number.
Does that justify classes as an idea to you?
The specific word "class" is associated with a particular approach to programming called Object Oriented Programming, which is much less popular today than it was at the end of last century, but this general idea of being able to make types for your application is more general.
That's not very convincing. Why can't a "dog" be a perfectly ordinary data structure that you manipulate the usual way?
while True:
for dog in dogs:
if dog["hungry"] == True:
dog["lastfed"] = time()
dog["hungry"] = False
Okay, I hear you say, but the fact that a dog is an associative array is an implementation detail. Supposing I want to abstract that - don't I need objects then? No! Just define some functions:
while True:
for dog in dogs:
if is_hungry(dog) == True:
update_last_fed(dog)
satiate_dog(dog)
So you end up with an abstract type (dog) and a bunch of functions that operate on that abstract type. Is there any advantage to "gluing" the functions to the data? Well yes, but it's subtle. Imagine you had two types of animal:
while True:
for pet in pets:
if species_of(pet) == "dog":
if is_dog_hungry(pet) == True:
update_last_fed_dogfood(pet)
satiate_dog(pet)
elif species_of(pet) == "canary":
if is_canary_hungry(pet)
...etc etc ad nauseam
With objects, you can make this elegant with pet.satiate() etc. Note however that this problem goes away with multiple dispatch - there's no reason why the language couldn't be designed so that satiate(pet) called different functions contingent on the type of pet. We already do this for math! Adding two floats is mechanically distinct from adding two ints, but it's a rare language that makes the programmer use different functions.
If you don't have any imagination and you treat dogs as database entries then yes you might do just that. If you are only person writing it and using that code, the same.
You did not address interaction between dogs. Some other idea, how do you pass dogs to a playpen, how would they interact - maybe you want different playpens where you can send your dogs into, some playpens having food, some not having a food, different types of food. What if some other developer will have to write code to heal the dog? If function to dog.Heal() will be in some different module that you never heard of, how do you find it?
You don't want to make each playpen having "if species_of(pet)" code duplication, if pet alone can act on its own will.
Writing code in here seems not that useful here because you would really have to imagine possibilities and see how much code would go into wiring up such a world. Then seeing how having methods in object are helping organize code and in the end prevent whole classes of errors.
Building all of that as an example is just too much work for one off discussion. Just play with imagination of how much complexity you can add to a simple example and soon you will notice.
Well respectfully, you provided the original project spec as an example of where objects were required, and I demonstrated that no, they mostly weren't. I agree that it's unproductive for you to keep adding things to the spec so that I can demonstrate that nope, you still don't need objects to do all that.
All that objects are, are functions glued to data. That's it. Whether or not you choose to glue them together, the behavior of the dog lies in the functions, and the state of the dog lies in the data. Frankly, your examples seem to lead away from objects - how will another developer add the ability to heal dogs, for instance? They can't change the class definition, so they'll have to do something awful like subclass it into HealableDog(), which doesn't interoperate with all the existing dog code at all. Meanwhile, if 'dog' is simply a big ol' struct of data, they can write a function heal(dog) and call it a day.
I highly recommend the first part of this video, which explains the composability problems with objects very well - with dogs and cats even! https://www.youtube.com/watch?v=kc9HwsxE1OY
I would not agree that one cannot change the class definition as it really depends on the system one is writing. I change class definitions on daily basis in business line applications. If I would be making frameworks then maybe I would not do that so often.
Subclass Dog into MyDog, which is exactly the same as Dog. From there, use and modify MyDog as though you would be modifying Dog. Any class that references base class Dog isn't modifiable, but any future work you can use MyDog instances to get all the functionality you need (with some ugly casting). There are other solutions as well.
> Why can't a "dog" be a perfectly ordinary data structure that you manipulate the usual way?
In statically typed languages, it's easier to read data out of a struct with fixed types, than a map[string]any and convert it to the expected type on each access.
> So you end up with an abstract type (dog) and a bunch of functions that operate on that abstract type.
C has no multiple dispatch and no classes. So libraries based around opaque objects (GTK and ALSA etc.) prefix each function name with its struct name, which is verbose and gets tedious to read or write after a while. Is multiple dispatch possible in a statically typed languages with compile-time nonvirtual function resolution? I haven't looked into it, it might be possible.
> Is there any advantage to "gluing" the functions to the data?
Depends on the capabilities of the language. If the language supports function overloading and type-checking, then no.
Otherwise the "fun" begins if you add different variations of the same basic data that require subtle changes for working with them.
In the case of Python you'd end up with a manual type-checking pattern and nested if-else statements within the functions. In the OO case you can leverage protocols, composition and (to a lesser extent) inheritance.
I can't reply to everyone so thanks to you for your replies.
My take is that for simple stuff I don't need classes. An example is I'm writing a simple GUI calculator for my brother who's a stone mason. It's a stone calculator that accepts quality, height, width, length measurements in mm and output a tables with volume costs in either metres or feet cubed.
I could model it as a job classes that accepts a stone list class, and customer name, cubed cost, tax, parameters. And methods to get total costs, taxes etc.
The stone list class is a list of stone classes. Each stone class has quality, height, width, length parameters and methods to get cost, get taxes, etc.
But all I do is store the measurements in a list, each list (stone) is stored in another list, and I iterate. Calculations are done on adding a stone, or when you recalculate the whole list. I know it's not big, complicated, or impressive example but for me it was easier to implement it classless.
A file is a class of objects you probably perform operations on with your scripting. You could instead use some kind of function instead to change, say, the entries in the disk index used to map sectors but using a class function like "rename" is just so much more understandable from the human point of view.
You asked for an example where a class is more suitable than a function. I can think of no better example than the abstract concept of files on a modern operating system. In fact it's so useful you will probably have difficulty coming up with ways to perform the same operations without using an object-oriented strategy.
a class is also a type (if you have a typed language),
a class is also a module, you group together values that makes sense together (the x and the y of a point) with the functions (methods) that interact with it.
When you have a lot of codes flying around, you can add encapsulation (private/public thingy) so the user of a code does not see the implementation which helps to create libraries that can evolve independently from the applications using them.
Also compared to an associative array, a class is more compact in memory (granted JavaScript runtimes see dictionaries as hidden classes).
To be honest, I don't care about classes itself. What I do like are the interfaces - which are usually expressed as collection of methods. Classes provide easy grouping of those methods, so you can not only type-safe implement particular methods, but make sure that whole group of methods implement particular contract.
To be honest, this does not require classes per se - Golang manages to have interfaces without classes, Rust has traits.
Classes are about encapsulation. They contain state and have funtions (methods) to change said state. It's nice to have things encapsulated when programs get very big. For one off scripts though, not really needed.
In fact the first part of the article I was like: yeah no need for classes there, a function is way simpler.
Now like I say I write fairly simple scripts, I mostly automate manual processes, but even after reading the article I'm still not sold on classes. I have to admit using dictionaries/associative arrays as the author showed and have often wondered over the difference between classes and objects. But I still don't see the problem.
I known my lack of knowledge and understanding is showing here; I'm no expert and have never written any massive software.
Can someone point me to an example where a class is more suitable than a function working on an object like an associative array? Genuine question from the ingnorant.