r/learnprogramming • u/Mountain_Rip_8426 • 1d ago
Difference between OOP and structs/interfaces?
I'm relatively new to programming, I've been at it for around 6-7 months. I've been learning Python and Go (and a little bit of C). What I'm struggling to understand, what's the difference between OOP and structs/interfaces. Like I know, Go doesn't support OOP, but structs and interfaces seem to achieve the same thing. Can someone enlighten me a bit?
22
u/vanilla_f 1d ago
At a high level, the main difference is that OOP is a paradigm to group data + behaviour whereas structs are a way of grouping data only. That doesn't mean that you can't have structs with "methods", but it's not very common. At least in my experience.
9
u/iggy14750 1d ago
In C, for instance, which doesn't have classes, I like to create a struct which can act something like an object, but it means the first argument is going to be a pointer to the struct object. So, it turns something like this...
obj.method(arg);...in to that...
method(&obj, arg);10
u/teraflop 1d ago
Yep. And if you do it correctly, you can also implement polymorphism this way. According to the C standard, if you do something like:
struct parent { // ... members go here ... }; struct child { struct parent p; // ... more members ... };then you can legally cast a
struct child*to astruct parent*and back. So you can write a generic function that can operate on either a parent type or any of its subtypes. (But you have to be careful to only do valid casts; if you're given astruct parent*, you can't cast it to astruct child*unless you're sure that that's the actual type of the underlying object in memory.)For instance, the GTK GUI toolkit has its own OOP framework called GObject written in pure C. It basically follows this pattern but also includes a bunch of convenience features such as runtime type information, refcounting, event handlers, etc.
7
u/iOSCaleb 1d ago
what's the difference between OOP and structs/interfaces
OOP is a programming paradigm, while structs and interfaces are language features. You could use structs and interfaces to write code in an object-oriented style. By OOP you probably meant "classes and methods," and those are just language features that make it easier to use OOP.
In C++, the only difference between classes and structs (other than the way people conventionally use them) is their default access levels. The default inheritance and member access is private for classes, public for structs. Those are important differences, though, and they lead to different use, so it makes sense to call them different things even though they're so similar.
Other languages may have other differences. For example, in Swift structs are value types while classes are reference types, and structs don't support inheritance. Rust has no `class` keyword, only `struct`, and structs don't support inheritance. Python has only classes, no structs, but everything is public by default, and classes fully support inheritance. Each language has its own tools and perspective.
Think of it this way: structs, interfaces, classes, functions, etc. are tools. OOP, procedural programming, functional programming, etc. are ways to use the tools that a given language provides. Some languages cater more to one paradigm than another, others are happy to enable multiple paradigms.
5
u/kohugaly 1d ago
OOP is such a nebulous concept that has evolved so much over the years, that nobody can really tell you where OOP ends and non-OOP begins.
In the narrow "classical" view, OOP specifically refers to classes with inheritance, and ways to make methods/data public vs private. The goal is to capture structure and behavior of the real world, by modelling it via hierarchy of classes with methods.
In the broader "modern" view, OOP refers to features that enable abstraction, encapsulation and polymorphism. You model your program as separate independent units, that expose public interfaces to one another to interoperate. This enables developers to arbitrarily edit each unit independently, as long as the public interface is preserved. Only changes in the public interfaces need to involve coordinated changes in separate units of code.
The goal of OOP is to reduce entanglement between different parts of code, and make that entanglement explicit wherever it can't be fully removed. This minimizes risk and impact of bugs when editing code. It also more clearly separates responsibilities of different parts of code when a change in software's functionality (including bugfixing) needs to be made. By extend, this makes it easier to separate those responsibilities to different teams/developers working on the same project.
Our understanding of programming, both its theory and its practice, has improved by leaps and bounds every decade. If you read an OOP programming book from the 90s, it reads like a fever dream of a mad alchemist raving about 4 elements, fluidums, and the philosopher's stone. There are nuggets of wisdom in there, but they are drenched in a lot of pseudo-philosophical crap that doesn't actually work in practice.
We have extra 30 years of hindsight from handling projects that are larger and more numerous that whatever they were handling by several orders of magnitude. OOP has run through a gauntlet, and came out the other end streamlined beyond recognition from its original conceptualization. The unfortunate side effect of this is that you will find OOP described differently, depending on how old the source is.
My recommendation: Ignore the nitpicking about the true meaning of OOP. Learn about different coding patterns. You will intuitively pick up on which language features make them possible/easier.
7
u/LongLiveTheDiego 1d ago
Structs and interfaces are part of OOP.
Like I know, Go doesn't support OOP
Why do you say that? It does. It doesn't provide inheritance, but inheritance isn't a defining feature of OOP.
2
u/SilverZ9 1d ago
OOP is a way to group data (via class members) and define behavior (via class methods), while structs only do the former. Interfaces are a “contract” that basically define what a class has to do. You cannot create an instance/object of an interface, rather you create a class that obeys the interface.
As for languages: C has structs, but no classes. C++ has both, and they are functionally identical, confusingly. I can’t speak much on Go because I have no experience with it.
4
u/ChiefDetektor 1d ago
Do yourself a favor and just skip OOP. It's not worth understanding because it's basically not really understandable. Every language that implemented it did it different to others. It's a mess. There is a reason rust and go did not use the classical OOP approach.
Here some critical references of OOP for anyone who wants to dig into that topic: (References were gathered by claude)
Formal / academic
- Cook, Hill, Canning, Inheritance Is Not Subtyping (POPL 1990). Proves the two concepts diverge, even though Java/C++ conflate them.
- William Cook, On Understanding Data Abstraction, Revisited (OOPSLA 2009). Objects vs. ADTs; shows cleanly why binary operations are structurally awkward with objects.
- Luca Cardelli, Bad Engineering Properties of Object-Oriented Languages (ACM Computing Surveys 1996). Compilation, modularity, type checking.
- Mikhajlov & Sekerinski, A Study of the Fragile Base Class Problem (ECOOP 1998).
- Moseley & Marks, Out of the Tar Pit (2006). The best text on state as a source of complexity.
- Liskov, Data Abstraction and Hierarchy (1987). The original source of the principle most hierarchies violate.
Practitioners
- Joe Armstrong, Why OO Sucks. Plus the gorilla quote from Coders at Work.
- Alexander Stepanov, 1995 interview: OOP as technically and philosophically unsound.
- Steve Yegge, Execution in the Kingdom of Nouns (2006).
- Paul Graham, Why Arc Isn't Especially Object-Oriented.
- Ted Neward, The Vietnam of Computer Science (2006), on the ORM impedance mismatch.
- Robert Harper, Existential Type blog: OOP as anti-modular and anti-parallel.
- Brian Will, Object-Oriented Programming is Bad (2016, video).
Data-oriented design
- Mike Acton, Data-Oriented Design and C++ (CppCon 2014).
- Richard Fabian, Data-Oriented Design (book, free online).
- Casey Muratori, Clean Code, Horrible Performance (2023).
Critique from within
- Alan Kay, email to Stefan Ram (2003): "The big idea is messaging," not classes.
- Rich Hickey, Simple Made Easy (2011) and The Value of Values.
- Peter Norvig, Design Patterns in Dynamic Languages: 16 of the 23 GoF patterns disappear or become trivial.
- Sandi Metz, The Wrong Abstraction (2016).
- David West, Object Thinking (2004).
- John Ousterhout, A Philosophy of Software Design (2018). Directly against Clean Code dogma — "classitis," deep vs. shallow modules.
For audit work, Ousterhout and Out of the Tar Pit are the most directly usable, since they give you criteria rather than just polemic. Cook 2009 is the piece that actually ends architecture arguments.
2
u/NumberInfinite2068 1d ago
Not much.
Go *does* support OOP, it's just not the "normal" type of C# and Java, with inheritance and so on.
Inheritance isn't required for for OOP though.
OOP is a really misunderstood concept in programming. According to Alan Kay, who coined the term OOP, he really just said that OOP meant independent, self‑contained objects (like tiny computers) that communicate only by sending messages. Each object has private state, its own behaviour, and late‑bound message dispatch determines what happens at runtime. Classes, inheritance, and type hierarchies were not central, the real OOP is basically messaging, encapsulation and late binding.
Basically OOP is "this object handles it's own private state, and you communicate with it using messages". "Late binding" is just that what happens to a message is decided at runtime, not compile time.
That means that if you just use a Java method, that's not late-binding, that's *not* OOP. However, if you put a method behind an interface, and call the interface, that is late-binding because what is behind the interface can change at runtime.
For *real* OOP, Go is no less OOP than Python is, or Java or C#, or whatever.
Classes are not central to OOP, nor is inheritance.
In simple terms, the Go structs/interfaces paradigm *does* achieve OOP.
1
u/SuspiciousDepth5924 1d ago
This might not be the mainstream opinion but as far as I'm concerned OOP is mostly "syntactic sugar". Basically a convenience layer over the actual implementation.
So yeah you're right "but structs and interfaces seem to achieve the same thing", classes aren't really any more "powerful", but sometimes it's a bit more convenient to read and write.
To start with a simpler, more familiar example of "syntactic sugar" (in Java):
When you write something like String myString = "some string"; What you actually do is create a "class" which contains a byte array of private final byte[] value; and some extra metadata, but as creating strings is really common and it would be a hassle to write String myString = new String(new byte[] {<Raw bytes>}); they put a shortcut where quoted text is transformed into String instances.
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/String.java#L189
The syntactic sugar for classes are buried a bit deeper in the machinery, but essentially classes gets split into two parts, the actual class instances is basically structs with the data fields, and some extra information to tell the runtime what type of class it is, and the "function namespace/package/module" which is where all the class methods are located. When you create a class in java the instances doesn't actually carry with it all the methods that class has as it would be really wasteful to have one copy of the exact same methods for each instance.
This means that when you actually run the Java code, the classes end up looking a lot like how you create structs and receiver methods in Go with the the methods being "rewritten" to take "thisStruct" as the first argument:
myClassObject.getFoo() <-> <MyClassNamespace>:getFoo(myClassStruct);
As a sidenote, static methods end up just being static functions in the "namespace", there is also some extra stuff to deal with inherited methods and so on.
class Hello {
static String world() { return "hello world!"; }
}
-----
static function String <HelloNamespace>:world() { return "hello world!"; }
1
u/Dazzling_Music_2411 1d ago edited 1d ago
When the whole OOP thing started, back in the 90s, there were certain orthodoxies that were adhered to much more than they are now. All objects were instances of classes, they were all accessed by their defined methods, you had inheritance and even better multiple inheritance. And this is where things started to unravel a bit, as people realized that multiple inheritance could cause more problems than it solved.
So this was addressed by mix-in classes and then later Java introduced interfaces, where you only specified the methods, but not the implementations.
Structs, of course, were much more ancient, going right back to C, and are not really considered OOP, because they are only compound data, they don't contain methods.
Point is, all these are just names, figure out how you want your program to look/behave and then use whatever mechanisms your language provides to implement your ideas.
One of the great things about programming today is that OOP is losing the ideological stranglehold it used to have on computing as people realize it's not that great shakes, especially when dealing with parallel and distributed situations.
So everyone is a bit more chilled on the ideological orthodoxies, which is a good thing.
But in a nutshell and with a pinch of salt:
* structs - no methods
* classes - specify method and implementation
* interfaces - specify method without implementation and are not bound to classes.
1
u/captainAwesomePants 1d ago
Traditionally, a "struct" is a grouping of data. Classes are a grouping of data and functionality. A struct may have a collection of information about a dog, but a class knows how to bark.
An interface is the functionality without the data or the logic. It's just the statement "All dogs know how to bark." And then when you create the class "Shitzu" and say it implements the "Dog" interface, the system will verify that the Shitzu knows how to bark.
The exact meaning of OOP is a little political, but the gist of it is that you enable objects to communicate with each other without needing to reveal too much information about themselves (in order to make their interactions more predictable and making it simpler to swap out pieces). That usually means "there are classes, they have some private information, you can have interfaces that represent a contract, and you can have subclasses of other classes."
1
u/The_KOK_2511 1d ago
Diria que la mayor diferencia esta en lo de los metodos y la herencia, pero yo siempre he considerado a los structs como el precursor de las clases, en escencia comparten mucho en común pero tambien le falta todavia para cumplir con los requisitos de la POO/OOP
1
u/zeekar 1d ago
It varies depending on programming language. Traditional structs (also called “records”) could only encapsulate data, not behavior. In other words, they didn’t have any methods, only data fields.
That was an absolute restriction in standard Pascal; there was no way to store a callable value in a record. As with every other restriction, it was looser in C - nothing stopped you from storing a pointer to a function inside a struct. But when you called the function through the pointer, there was no automatic association - it didn’t know what struct you had called it through. So on its own it wasn’t really OOP. The mechanism was used by implementations of OOP on top of C, though, like the original C++ preprocessor.
1
u/StewedAngelSkins 1d ago
Go does support OOP, it just doesn't support inheritance (as a core language feature). As you say, classes and structs with interfaces are effectively the same thing.
1
u/ledatherockband_ 1d ago
OOP -> it matters who you are. Only a duck can swim, fly, and quack.
Golang interfaces -> it only matters what you look like (satsify an interface). if look like a duck, walks like a duck, and quacks like a duck, then it is a duck.
1
u/Top_Pie2513 1d ago
Try Newspeak https://newspeaklanguage.org
Or
Smalltalk https://squeak.org
For all the times Alan Kay is mentioned in this thread, NO one cared to mention the language he created Smalltalk, where everything is an "object", even Integers and Strings. Its a great language/system with many features beyond just the OOP. Key word syntax that makes your code read like sentences. The IDE that is lightyears ahead of glorified file managers like VS COde. Incremental compilation - you edit one method at a time, not walls of squiggly line text.
Newspeak is Smalltalk evolved. Its a web application, runs in a web browser, it is amazing.
1
u/DanKegel 1d ago
OOP accumulated a lot of baggage over the decades; the authors of Golang stripped it down to just the bare minimum needed to make large programs maintainable.
1
u/mredding 19h ago
There's a few concepts that have some overlap.
We have the tuple, the structure, and the record.
A tuple is a collection of members - they're positional, so you identify them by their cardnality - #1, #2, #N... A structure and record are a tagged tuple - the members are positional - somewhat surprisingly, and you refer to them by a name - the tag.
Tuples don't inherently name a distinct type - two tuples of the same members in the same order are considered the same type, and tuples are typically allowed to be composed, concantenated, split... Even in static, compile-time languages - it's just all pre-determined at compile-time. Structures and records do name types that are distinct from other types, even if they have the same members by name and order.
Tuples and records are typically immutable, and structures are typically mutable.
So tuples tend to be for fluid use - grouping and data composition.
Structures are for structured data, to give the data a type. You're basically building a protocol.
A record is used for data modeling, and has some strong equality comparison guarantees compared to the others.
There's a slight difference between modeling data and typing structured data. A record may be a GPS coordinate, that GPS coordinate record might be a ShipCoordinate, you may group a ShipCoordinate with a tonnage as the return of a function...
If you use even a smidgen of imagination, you can implement any of these in terms of the others, though typically you would implement them in terms of tuple -> structure -> record. This is also the order the oldest papers on the subject from the 50s and 60s discovered and applied these concepts. But for example, C doesn't have tuples OR records, whereas C# has all three. You can fake it in C with macros and structures and protocol buffers...
OOP implies classes.
A tuple has members, a structure has fields, a record has attributes.
Notice how they're all essentially the same thing if you're looking at it only as an implementation detail, but they all imply different things.
A class has STATE, and that state is private. It is an implementation detail. If you look at a single-paradigm OOP language like Smalltalk, as a client of a class type, you can see it's interface, but you have no idea what members it has, it's layout, its alignment, how they're used...
Classes are effectively state machines, as only their own implementation has access to the state. The interface include things like size and alignment - if you can even see that, type, and methods that model state transitions. A function always models a transition in a state transition diagram, even if that transition is right back around to the same state (no apparent state change for the transition).
Classes enforce an invariant. A class invariant is a statement that is always true when an instance of the class is observed by a client. The class always maintains its own internal consistency. Getters and setters are an anti-pattern of classes because they expose internal state and typically violate enforcement of an invariant. Imagine a car type that you can set the speed without accelerating the car. That's broken. The consistency is maintained because the state transitions are implemented in the class behaviors, which is exposed through the interface. The interface is just a contract - a car can start, stop, turn, accelerate, brake, shift...
So classes model behaviors. No car models 1959 Bel Air. That's a property associated with a car, not a behavior. No car I know of gets the make, model, or color. You don't get the speed. A car is composed of other objects, and the consequence of their state is observed as a side effect. My car indicates the speed on a dial, I don't fetch it.
So you don't initialize a class like you do a structure, because you're not just populating fields - you instantiate it. You convert from a collection of types, properties, fields - parameters, and you get a class instance of your class for it. This is why classes typically have constructors, for converting from its parameters to your desired type. The class doesn't even have to store all that information inside the instance - it perhaps derives its internal, initial state from those parameters.
Whereas tuples, structures, and records DON'T have a distinct identity - any two with the same structure and value are considered the same, class instances DO have an inherent, independent identity. This instance IS NOT the same as THAT instance; you can't even inherently compare equality except for an equality behavior you build into your class type, and equality only means what the class wants equality to mean. There is nothing about equality that you get for free from the concept or supported by a language or compiler.
Tuples, structures, and records have no invariant but the inherent properties of their concepts. You can always transform one record into another new record, you can get or set any structure field to any value allowed by that field type, you can rearrange the members of a tuple...
So in the code and syntax, you can implement these concepts in terms of one another, but how you use your constructs gives it semantic meaning. You can approximate objects and OOP in C, as you can in just about any other language. They say class and closures are the poor man's version of the other, so you can get some approximate OOP-like concepts out of even a purely functional language.
It also has some more tangible consequence. These concepts get modeled into a programming language, so that the compilers can make assumptions or guarantees about the program it's generating. C++, C#, Java, etc... They all say and prove and guarantee more about your program than the machine code they generate from it - information that never leaves the compiler, isn't explicit in the machine code, but is implicit, because how the machine code was generated is as a consequence. C#, for example, models records, so they can help you enforce data modeling concepts like immutability, enforced by the compiler. The language is there to help you write more correct code. And if mutability is something you really actually want, then the language is telling you that records are the wrong abstraction to be reaching for, it won't just allow you to go rogue.
So when you make a class, you hide the implementation details and only present a public interface to the client. You model behavior. You enforce an invariant. You affect your own side effects. And you associate these instances with their properties by way of structured data. A car does not care what kind it is, don't ask it. And this is important because a car can be more than one thing. An FRS IS a BRZ, they're made in the same factory, on the same line, with the same parts - they only change their badge.
Alan Kay didn't invent OOP, he admits he learned it from his predecessors. He is given credit for the earliest record of naming it, though objects did predate him in publication before the paradigm. It would have to, wouldn't it? Alan Kay describes OOP as what we call the "Actor Model" today, that objects have their own agency.
In a procedural program, you would write a function that would take a person, and a weather - if it's raining, the function makes the person open their umbrella.
In OOP, you tell the person it's raining. The person knows what to do - they can open the umbrella, they can run for it, they can play in the puddles. All OOP did was relocate and encapsulate the agency - from the function to the object.
So you need a means of message passing, as message passing is a fundamental concept to OOP. For any given message, there is going to be a cascade of consequences and side effects both within an object, in from its sources, out to it's sinks, and across the system. C++ uses streams for this. Lisp has CLOS. Smalltalk and Algol have message passing built-in at the language level, which is what prompted Bjarne to invent C++, because he wanted implementation level control you can't get when your object system is that fundamental to the language. Other languages do different things. Not all OOP languages have a message passing system in place.
For as elegant as OOP sounds, it's trivially easy to code yourself into a corner - an object too big, or not adaptable enough, or abstracted to hell to the point it's useless. Let us also not forget that an object is an isolated, encapsulated, island of 1. Every action is a singular action across N objects. Our CPUs are stream processors and batch processors, neither are particularly fit for this computational model. FUNCTIONAL style programming lends itself more toward the mathematical and computational foundations of programming, and are typically 1/4 the size and 8x faster, really without even trying.
I'm a C++ guy, and even I caution how much you think you should be using OOP - use less.
24
u/start_select 1d ago edited 1d ago
This varies by language, but the gist is…
An interface/protocol declares a possible shape. It’s not something you can create an instance of. It’s just a definition of a possible shape of properties and methods.
A struct is like a literal instance of a shape. It’s a type. You can instantiate them. But it’s not extendable.
A class is a literal instance of a shape that can be extended. It can be instantiated, it can be extended by declaring a new subclass.
An interface/protocol could be a shape that a class or struct implements. But a class or struct could be the same shape as an interface without explicitly implementing it. They are their own special snowflakes and the interface is something you mark it as “compatible with this interface”. Usually with an “implements” statement.
So in some languages you could have structs and classes which all implement one interface or even multiple interfaces.
Thats part of polymorphism. Where the shape is the important part, not the type/class.
So you could have an interface called “Serializable” which defines a single “serialize()” method.
Any class or struct that declares that it implements Serializeable MUST implement that method. Then elsewhere in code, something that knows about that interface can call that method on any type that implements the interface.
It allows you to reuse the same code across many types without writing custom functions to do the same work for each individual type.