6

I recently started learning about OOP, and I have read that OOP is a different programming paradigm than procedural programming. But I didn't find any examples that shows in what way they are different.

I mean say that I am using OOP and I created a Car class with a moveForward() function and then I called this function:

Car car = new Car();    // Car is a class here
car.moveForward();

Now say that I am using procedural programming and I created a Car struct and a moveForward() function that takes a Car struct as an argument, and then I called this function:

Car car = new Car();    // Car is a struct here
moveForward(car);

So it seems to me that OOP and procedural programming are only different in the way you call a function of an object, but the way you think about a problem and implement its solution is almost the same!

1
  • 1
    In C++ language, the difference between the two is not an example of OOP vs Procedural difference. It is only a member function vs. non-member function difference. If your code example is not based on C++, please clarify the language of your code sample.
    – rwong
    Mar 11, 2018 at 15:49

5 Answers 5

5

As you've observed, for much so-called object-oriented code, the difference to procedural programming is purely syntactic.

There are many different opinions what Object Oriented Programming actually means. One viewpoint is: objects communicate by sending messages to each other. So instead saying “hey procedure, execute with this data”, we say “hey object, handle this message”. It is the object's responsibility of figuring out which code should be executed in response to a message, and the caller is not guaranteed that any specific procedure will be executed.

Making the function dispatch the responsibility of the receiving object opens up some interesting opportunities: we have to make a difference between the public interface promised by the objects to its users, and the internal implementation of this interface. This also allows inheritance and dependency injection: we can create a new object that conforms to the same interface (answers the messages in a compatible way), and replace the original object – without having to change the calling code. Since messages are (supposed to be) values, they can also be sent over network, so object-oriented thinking lends itself to distributed problems such as microservices.

In practice, most languages don't actually use messages, and instead use a technique called dynamic dispatch: the object (or the class of an object) contain a table of function pointers. The layout of this table forms the interface of the object. So I can have many different objects that conform to the same interface, but implement it differently.

For example, let's think about a traffic simulation with cars and bikes. Cars and bikes have very different characteristics but for our simulation code, they are both traffic participants. A procedural approach would be to check with ifs and elses what kind of traffic participant we have:

Car_moveForward(Car*) { ... }
Bike_moveForward(Bike*) { ... }

// in using code:
for (int i = 0; i < len; i++) {
  if (participants[i]->type == TP_CAR)
    Car_moveForward((Car*) participants[i]);
  else if (participants[i]->type == TP_BIKE)
    Bike_moveForward((Bike*) participants[i]);
  else
    error("unexpected traffic participant type");
}

That's how a lot of code in C has to be written.

With OOP, we instead define an interface that describes which operations we need. All objects implement this interface:

Car::moveForward() { ... }
Bike::moveForward() { ... }

// using code:
for (int i = 0; i < len; i++) {
  // doesn't need to know which implementation to call
  participants[i]->moveForward();
}

So this interface allows us to remove explicit if/else checks for the concrete type. This also gives us a lot of extensibility: If we want to add another type of traffic participant (e.g. pedestrians), we would have to update every if-else type check in procedural code. But with an interface, we only have to implement that interface and our new type can already be used everywhere: code using an interface doesn't have to change when the interface is implemented by another type.

Many problems don't need these interfaces. Quite a lot of code can happily be procedural with OOP-ish syntax. One of the biggest use cases for interfaces is decoupling and dependency injection, especially for unit testing. For example, an application might make database requests. How can I test this without setting up a database? First, introduce an interface that describes what I need from the DB, then implement that interface for the DB. But my tests can use a mock implementation of this interface that just returns static data instead of the real DB implementation.

The Design Patterns book contains a collection of problems where object-oriented techniques can provide an elegant solution. They are all based on the idea of using interfaces so that calling code doesn't need to know the concrete type in advance.

1
  • I think Heads First Design Patterns is a better book for a beginner into OOP. At least it is a lot more fun to read.
    – Bent
    Mar 11, 2018 at 17:36
3

I disagree that the difference between procedural and object-oriented code is purely syntactic. It is exactly the opposite.

There is almost no difference syntactically, Java for example could be thought of as procedures with the first argument being the "object" or "structure" it is defined on. You can very easily write procedural code in Java, and most projects unfortunately actually do.

The real difference is in the "thinking", how you approach a problem. Objects are supposed to be cooperating autonomous agents. Contrast that to the "procedural" thinking, where you think about "steps", like: I need to open the file, need to parse the CSV, add all the columns and write the sum to the output. In oo you would think: there are Readouts which can generate a ReadoutReport (or something along those lines).

The thinking is completely different. While it may result in code that is sometimes similar to what a procedural developer would do, this similarity is purely coincidental.

It also has nothing to do with most of the technical stuff people mention. Classes, inheritance, patterns, DI, whatever are secondary things at best. Some of those are good tools, don't get me wrong, but they are secondary to the nature of oo, which is again about object (autonomous agents) collaborating to achieve some functionality.

2

The thing that you have missed is that in procedural programming if you have items that differ in the way they move forward you would have different functions to move different items like:

MoveForwardCar(car);
MoveForwardBike(bike);

and so on.

In OOP you can move that functionality into the methods on the objects so that the rest of the code does not need to know about how to move anything forward, it only have to know that it can move forward. The moving forward is encapsulated by the classes. So that you do like this:

car.MoveForward();
bike.MoveForward();

Only the Car and Bike classes knows what goes on when they are moving forward.

If you add a new class Ship you only have to add the MoveForwardmethod to the ship class. The rest of the code does not have to change, because when moving forward ships behave exactly like cars and bikes to the rest of the code.

2
  • 1
    Depends on what “MoveForwards” does. If it is about checking traffic lights, pedestrians or vehicles in the way, using indicators, observing speed limits, then MoveForwards” can be written completely independent of the individual vehicle implementation.
    – gnasher729
    Mar 11, 2018 at 15:11
  • 1
    The key point here is that two instances may share a "common interface", and then some other pieces of code can "invoke" behavior through this "common interface", without necessarily being too concerned about the underlying type of the instance. This elevates into a level of abstraction (of the polymorphism). There are ways to achieve the same kind of abstraction in procedural languages, but the syntax differs.
    – rwong
    Mar 11, 2018 at 15:43
0

So it seems to me that OOP and procedural programming are only different in the way you call a function of an object, but the way you think about a problem and implement its solution is almost the same!

Yes, though only for simple examples. OOP offers capabilities that go further.

First, OOP supports the abstraction of an interface: teasing apart it's usage and its implementation, loosely coupling them, and thereby supporting substitution.

This provides polymorphism, which allows for a certain kind of code reuse: namely, the same client consuming an interface can (assuming liskov) be reused — meaningfully, and without change — with different implementations of the interface.

This is harder to do using a procedural programming style, as the interface and one implementation are inherently tightly coupled together. Something extra is needed to help decouple them, and that would include some OOP techniques.

Second, OOP's inheritance is a mechanism for implementation code reuse. Base class methods can be reused by specializing sub-classes. (Keep in mind the recommendation for composition over inheritance though.)

And those two: separation of interface from implementation, and code inheritance, can work together: a specializing sub-class that can reuse some of the implementation of the base classes, and consuming client code can be reused unmodified with that specialized sub class.


If we write code like this:

Car car = new Car();    // Car is a class here
car.moveForward();

in just two lines of code, we are conflating two different roles. The role played by the second line of code is that of an interface-consuming client, whereas the role played by the first line of code is that of decider, injector, factory, or configurator. Often, we will want to separate these roles, into different modules. Thus, the car might be created in one module, and used by another.

OOP encourages this separation, with the expectation that (when done properly) the consuming client module can perform on multiple potential implementations.


Struct vs. Class is a distinction that depends on the language you're using.

C, of course, doesn't have classes; struct's have by-value semantics until you use pointers. Once you're using pointers, you can apply some OOP techniques.

In C++ there is virtually no difference between struct and class (the access modifier default is different, public for structs, private for classes).

C# has both structs and classes where the former has by-value semantics and the latter by-reference semantics (e.g. local variables & parameters can be structs and can refer to objects, though cannot be objects).

Java and C#'s classes are similar; Java doesn't have structs — its only by-value types are the primitives (e.g. int).

So, in C#, for example, assuming Car is a class, Car car; creates a reference variable that can refer to an object that is of type Car, though also any subclass of Car. Whereas if Car is a struct, then that same declaration creates a struct of type Car.

0

As long as the language you are using has a powerful type system you can easily separate data and behaviour with no problems, and some benefits. Procedural programming is then basically another name for functional programming.

For instance, in the examples above:

MoveForwardCar(car);
MoveForwardBike(bike);

could become

MoveForward(participant);

as long as the participant type/struct/record/dataclass implemented the type IMoveForwardable.

For instance, in Scala, you can have purely data classes of a certain supertype so you can pass them to functions that know how to handle them:

sealed trait Transaction
case class Deposit(amount: Money, date: LocalDate) extends Transaction
case class Withdrawal(amount: Money, date: LocalDate) extends Transaction

This is great because you can easily make structs immutable. And immutable types are much easier to work with because they avoid race conditions and logic errors.

Modern programming languages in general are all moving towards the benefits of:

  • null safety
  • immutability
  • static typing.

But object orientated languages are older than this movement (except Rust) and don't support immutable objects and pure functions natively. Thus if your language doesn't have typed structs, because you need to call a constructor to instantiate any object, you can't do things like deepCopy() to create a copy of your immutable data to pass around without huge amounts of boilerplate code, or code generators. This 'immutability tax' is discussed here: https://medium.com/@davidmorgan_14314/the-mutability-tax-6403d84f21c0

See also this discussion of the benefits of separating data and code: https://blog.klipse.tech/databook/2020/10/02/separate-code-data.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.