18
$\begingroup$

I have studied the book "C++ Demystified". Now I have started to read "Object-Oriented Programming in Turbo C++ first edition (1st edition)" by Robert Lafore. I do not have any knowledge of programming which is beyond these books. This book might be outdated because it's 20 years old. I do have the latest edition, I am using the old because I like it, mainly I am just studying the basic concepts of OOP used in C++ through the first edition of Lafore's book.

Lafore's book emphasizes that "OOP" is only useful for larger and complex programs. It is said in every OOP book (also in Lafore's book) that procedural paradigm is prone to errors e.g. the global data as easily vulnerable by the functions. It is said that programmer can make honest errors in procedural languages e.g. by making a function that accidentally corrupts the data.

Honestly speaking I am posting my question because I am not grasping the explaination given in this book: Object-Oriented Programming in C++ (4th Edition) I am not grasping these statements written in Lafore's book:

Object-oriented programming was developed because limitations were discovered in earlier approaches to programming.... As programs grow ever larger and more complex, even the structured programming approach begins to show signs of strain... ....Analyzing the reasons for these failures reveals that there are weaknesses in the procedural paradigm itself. No matter how well the structured programming approach is implemented, large programs become excessively complex.... ...There are two related problems. First, functions have unrestricted access to global data. Second, unrelated functions and data, the basis of the procedural paradigm, provide a poor model of the real world...

I have studied the book "dysmystified C++" by Jeff Kent, I like this book very much, in this book mostly procedural programming is explained. I do not understand why procedural(structured) programming is weak!

Lafore's book explains the concept very nicely with some good examples. Also I have grasped an intuition by reading Lafore's book that OOP is better than procedural programming but I am curious to know how exactly in practice procedural programming is weaker than OOP.

I want to see myself what are the practical problems that one would face in procedural programming, how the OOP will make the programming easier. I think I will got my answer just by reading Lafore's book contemplatively but I want to see with my own eyes the problems in the procedural code, I want to see how the OOP style code of a program removes the foregoing errors that would happen if the same program were to be written using procedural paradigm.

There are many features of OOP and I understand it is not possible for someone to explain me how all these features removes the foregoing errors that would generate by writing the code in procedural style.

So, here is my question:

Which limitations of procedural programming does OOP address and how does it effectively remove these limitations in practice?

In particular, are there examples for programs which are hard to design using the procedural paradigm but are easily designed using OOP?

P.S: Cross posted from: https://stackoverflow.com/q/22510004/3429430

$\endgroup$
6
  • 3
    $\begingroup$ The distinction between procedural programming and object-oriented programming is to some extent a matter of presentation and emphasis. Most languages that advertise themselves as object-oriented are also procedural — the terms look at different aspects of the language. $\endgroup$ Commented Mar 24, 2014 at 14:06
  • 2
    $\begingroup$ I reopened the question now; let's see how this goes. Keep in mind that any treatment that presents OOP is holy grail, solves-all-problems messiah of programming languages is spouting nonsense. There are pros and cons. You are asking for the pros, and that's a fair question; don't expect more. $\endgroup$
    – Raphael
    Commented Mar 25, 2014 at 8:32
  • $\begingroup$ The thought of designing a (modern) desktop GUI without OOP and some techniques that grew on it (e.g. event/listener pattern) scares me. Doesn't mean it can't be done (it certainly can), though. Also, if you want to see failure of PP at work, look at PHP and compare the API with, say, Ruby. $\endgroup$
    – Raphael
    Commented Mar 25, 2014 at 8:33
  • 2
    $\begingroup$ "No matter how well the structured programming approach is implemented, large programs become excessively complex..." that is very much true of OOP also but it basically does manage the complexity better if applied in the prescribed way by developers... and does it largely through better/sharper scope boundaries/restrictions ie a sort of compartmentalization system... aka APIE: Abstraction, Polymorphism, Inheritance, Encapsulation $\endgroup$
    – vzn
    Commented Mar 25, 2014 at 20:22
  • 1
    $\begingroup$ identical question OOP vs procedural programming on Software Engineering $\endgroup$
    – vzn
    Commented Mar 27, 2014 at 20:29

9 Answers 9

11
$\begingroup$

In a procedural language you can't necessarily express restrictions that are required to prove that the caller is using a module in a supported fashion. In the absence of a compiler checkable restriction, you have to write documentation and hope that it is followed, and use unit tests to demonstrate intended uses.

Declaring Types is the most obvious declarative restriction (ie: "prove that x is a float"). Forcing data mutations to pass through a function known to be designed for that data is another. Protocol enforcement (method invoke ordering) is another partially supported restriction ie: "constructor -> othermethods* -> destructor".

There are also a real benefits (and a few drawbacks) when the compiler knows about the pattern. Static typing with polymorphic types are a bit of a problem when you emulate data encapsulation from a procedural language. For example:

the type x1 is a subtype of x, t1 is a subtype of t

This is one way to encapsulate data in a procedural language to have a type t with methods f and g, and a subclass t1 that does likewise:

t_f(t, x, y, z, ...), t_g(t, x, y, ...) t1_f(t1, x, y, z, ...)

In order to use this code as it is, you have to do a type check and switch on type of t before deciding the kind of f you will invoke. You could work around it like this:

type t { d : data f : function g : function }

So that you invoke t.f(x,y,z) instead, where a typecheck and switch to find the method is now replaced with just having each instance store method pointers explicitly. Now if you have a huge number of functions per type, this is a wasteful representation. You could then would use another strategy like having t point to a variable m which contains all of the member functions. If this feature is part of the language, then you can let the compiler figure out how to deal with making an efficient representation of this pattern.

But data encapsulation is recognition that mutable state is bad. The object oriented solution is to hide it behind methods. Ideally, all of the methods in an object would have a well defined calling order (ie: constructor -> open -> [read | write] -> close -> destruct); which is sometimes called a 'protocol' (research: "Microsoft Singularity"). But beyond construct and destruct, these requirements are generally not part of the type system - or well documented. In this sense, objects are concurrent instances of state machines that are transitioned by method calls; such that you might have multiple instances and use them in an arbitrarily interleaved fashion.

But in recognizing that mutable shared state is bad, it can be noted that object orientation can create a concurrency problem because the object data structure is a mutable state that many objects have a reference to. Most object oriented languages execute on the thread of the caller, which means that there are race conditions in the method invocations; let alone in non-atomic sequences of function calls. As an alternative, every object could get async messages in a queue and service them all on the object's thread (with its private methods), and respond to the caller by sending it messages.

Compare Java method calls in a multi-threaded context with Erlang processes sending messages (that only reference immutable values) to each other.

Unrestricted Object Orientation in combination with parallelism is a problem due to locking. There are techniques ranging from Software Transactional Memory (ie: ACID transactions on in memory object similar to databases) to using a "share memory by communicating (immutable data)" (functional programming hybrid) approach.

In my opinion, Object Orientation literature focuses FAR too much on inheritance and not enough on protocol (checkable method invocation ordering, preconditions, postconditions, etc.). The input that an object consumes should usually have a well defined grammar, expressible as a type.

$\endgroup$
3
  • $\begingroup$ Are you saying that in OO languages, the compiler can check whether methods are used in the prescribed order, or other restrictions on the use of modules? Why is "data encapsulation [...] recognition that mutable state is bad"? When you talk of polymorphism, are you assuming that you are using an OO language? $\endgroup$
    – babou
    Commented Mar 28, 2014 at 16:15
  • $\begingroup$ In OO, the most important capability is in being able to hide the data structure (ie: require references to be from like this.x) to prove that all accesses pass through methods. In a statically typed OO language, you are declaring even more restrictions (based on types). The note about method ordering is just saying that OO forces constructor to be called first and destructor last; which is the first step in structurally forbidding bad calling orders. Easy proofs is an important goal in language design. $\endgroup$
    – Rob
    Commented Mar 31, 2014 at 14:26
  • $\begingroup$ Sorry for the thread necromancy, but this is one of the most fantastic posts I have read on OOP (and I have read a lot of them!). I've encountered the idea that "inheritance = secret vtable" before, but the part relating constructors/destructors and encapsulation to provability of method sequence and location of mutation site has made something click in my mind. Thanks! $\endgroup$
    – Movpasd
    Commented Nov 15, 2022 at 9:33
9
$\begingroup$

A bit of history is in order, I think.

The era from the mid-1960s to the mid-1970s is known today as the "software crisis". I can't put it better than Dijkstra in his Turing award lecture from 1972:

The major cause of the software crisis is that the machines have become several orders of magnitude more powerful! To put it quite bluntly: as long as there were no machines, programming was no problem at all; when we had a few weak computers, programming became a mild problem, and now we have gigantic computers, programming has become an equally gigantic problem.

This was the time of the first 32-bit computers, the first true multiprocessors and the first embedded computers, and it was clear to researchers that these were going to be important for programming in the future. It was a time in history when customer demand outpaced programmer capability for the first time.

Unsurprisingly, it was a remarkably fertile time in programming research. Before the mid-1960s, we did have LISP and AP/L, but the "main" languages were fundamentally procedural: FORTRAN, ALGOL, COBOL, PL/I and so on. From the mid 1960s to the mid 1970s, we got Logo, Pascal, C, Forth, Smalltalk, Prolog, ML, and Modula, and that's not counting DSLs like SQL and its predecessors.

It was also a time in history when many of the key techniques for implementing programming languages were being developed. In this period we got LR parsing, dataflow analysis, common subexpression elimination, and the first recognition that certain compiler problems (e.g. register allocation) were NP-hard, and trying to tackle them as such.

This is the context in which OOP came about. So the early-1970s answer to your question of what problems OOP solves in practice, the first answer is that it appeared to solve many of the problems (both contemporary and anticipated) that were facing programmers in that period of history. However, this is not the time when OO became mainstream. We'll get to that soon enough.

When Alan Kay coined the term "object oriented", the picture that he had in mind was that software systems would be structured like biological system. You would have something like individual cells ("objects") which interact with each other by sending something analogous to chemical signals ("messages"). You couldn't (or, at least, wouldn't) peer inside a cell; you would only interact with it via the signalling paths. Moreover, you could have more than one of each kind of cell if you needed to.

You can see that there are a few important themes here: the concept of a well-defined signalling protocol (in modern terminology, an interface), the concept of hiding implementation from the outside (in modern terminology, privacy), and the concept of having multiple "things" of the same type hanging around at the same time (in modern terminology, instantiation).

One thing you may note is missing, and that's inheritance, and there's a reason for this.

Object-oriented programming is an abstract notion, and the abstract notion can be implemented in different ways in different programming languages. The abstract concept of a "method", for example, could be implemented in C using function pointers, and in C++ using member functions, and in Smalltalk using methods (which should be unsurprising, since Smalltalk implements the abstract concept pretty much directly). This is what people mean when they point out (quite rightly) that you can "do" OOP in (almost) any language.

Inheritance, on the other hand, is a concrete programming language feature. Inheritance can be useful for implementing OOP systems. Or at least, this was the case until the early 1990s.

The time from the mid-1980s to the mid-1990s was also a time in history when things were changing. During this time, we had the rise of the cheap, ubiquitous 32-bit computer, so businesses and many homes could afford to put a computer that was almost as powerful as the lowest-end mainframes of the day on every desk. It was also the heyday of This was also the era of the rise of the modern GUI, and the networked operating system.

It was in this context that Object Oriented Analysis and Design arose.

The influence of OOAD, the work of the "three Amigos" (Booch, Rumbar and Jacobson) and others (e.g. the Shlaer–Mellor method, responsibility-driven design, etc), can't be understated. It is the reason why most of the new languages that have been developed since the early 1990s (at least, most of the ones you've heard of) have Simula-style objects.

So the 1990s answer to your question is that it supports the best (at the time) solution for domain-oriented analysis and design methodology.

Since then, because we had a hammer, we applied OOP to pretty much every problem that came up since. OOAD and the object model that it used encouraged and enabled agile and test-driven development, cluster and other distributed systems, and so on.

Modern GUIs and any operating systems that was designed in the last 20 years tends to provide its services as objects, so any new practical programming language needs, at the very least, a way to bind to the systems that we use today.

So the modern answer is: It solves the problem of interfacing to the modern world. The modern world is built on OOP for the same reason that the world of 1880 was built on steam: we understand it, we can control it, and it does the job well enough.

That's not to say that research stops here, of course, but it does strongly indicate that any new technology will need OO as a limiting case. You don't have to be OO, but you can't be fundamentally incompatible with it.

$\endgroup$
3
  • $\begingroup$ One aside which I didn't want to put in the main essay is that WIMP GUIs and OOP appear to be an extremely natural fit. Many bad things can be said of deep inheritance hierarchies, but this is one situation (probably the ONLY situation) where it seems to make some kind of sense. $\endgroup$
    – Pseudonym
    Commented Mar 28, 2014 at 4:05
  • 1
    $\begingroup$ OOP showed up first in Simula-67 (simulation), in the internal organization of operating systems (the idea of "device class" in Unix is essentially a class from which drivers inherit). Parnas' "On the criteria to be used in decomposing systems into modules", CACM 15:12 (1972), pp. 1052-1058, Wirth's Modula language from the seventies, "abstract data types" are all precursors in one way or the other. $\endgroup$
    – vonbrand
    Commented Sep 10, 2015 at 16:44
  • $\begingroup$ That's all true, but I maintain that OOP wasn't seen as a "solution to a problem of procedural programming" until the mid-70s. Defining "OOP" is notoriously hard. Alan Kay's original use of the term doesn't agree with Simula's model, and unfortunately the world has standardised on Simula's model. Some object models have a Curry-Howard-like interpretation, but Simula's does not. Stepanov was probably correct when he noted that inheritance is unsound. $\endgroup$
    – Pseudonym
    Commented Sep 11, 2015 at 1:11
8
$\begingroup$

Procedural/functional programming is in no way weaker than OOP, even without going into Turing arguments (my language has Turing power and can do anything another will do), which do not mean much. Actually, object oriented techniques were first experimented in languages that did not have them built-in. In this sense OO programming is only a specific style of procedural programming. But it helps enforcing specific disciplines, such as modularity, abstraction and information hiding that are essential for program understandability and maintenance.

Some programming paradigms evolve from theoretical vision of computation. A language like Lisp evolved from lambda-calculus and the idea of meta-circularity of languages (similar to reflexivity in natural language). Horn clauses fathered Prolog and constraint programming. The Algol family also owes to lambda-calculus, but without built-in reflexivity.

Lisp is an interesting example, as it has been the testbed of much programming language innovation, that is traceable to its double genetic heritage.

However languages then evolve, often under new names. A major factor of evolution is programming practice. Users identify programming practices that improve properties of programs such as readability, maintainability, provability of correctness. Then they try to add to the languages features or constraints that will support and sometimes enforces these practices so as to improve the quality of programs.

What this means is that these practices are already possible in older programming language, but it takes understanding and discipline to use them. Incorporating them into new languages as primary concepts with specific syntax makes these practices easier to use and to readily understand, particularly for the less sophisticated users (i.e., the vast majority). It also makes life a bit easier for the sophisticated users.

In some way, it is to language design what a subprogram/function/procedure is to a program. Once the useful concept is identified, it is given a name (possibly) and a syntax, so that it can be readily used in all programs developed with that language. And when successful, it will be incorporated in future languages too.

Example: recreating object orientation

I now try to illustrate that on an example (which could certainly be further polished, given time). The purpose of the example is not to show that an object oriented program can be written in procedural programming style, possibly at the expense of lisibility and maintainability. I will rather try to show that some languages without OO facilities can actually use higher order functions and data structure to actually create the means to mimic effectively Object Orientation, in order to benefit from its qualities regarding program organization, including modularity, abstraction and information hiding.

As I said, Lisp was the testbed of much language evolution, including the OO paradigm (though what could be considered the first OO language was Simula 67, in the Algol family). Lisp is very simple, and the code for its basic interpreter is less than a page. But you can do OO programming in Lisp. All you need is higher order functions.

I will not use the esoteric Lisp syntax, but rather pseudo-code, to simplify the presentation. And I will consider a simple essential problem: information hiding and modularity. Defining a class of objects while preventing the user from accessing (most of) the implementation.

Suppose I want to create a class called vector, representing 2-dimensional vectors, with methods including: vector addition, vector size, and parallelism.

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

Then I can assign the created vector to actual function names to be used.

[vector, xcoord, ycoord, vplus, vsize, vparallel]= vectorclass ()

Why be so complicated? Because I can define in the function vectorrec intermediary constructs that I do not want to be visible to the rest of the program, so as to preserve modularity.

We can make another collection in polar coordinates

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

But I may want to use indifferently both implementations. One way to do it is to add a type component to all values an define all the above function in the same environment: Then I can define each of the returned functions so that it will first test the type of coordinates, then apply the specific function for it.

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

What have I gained: the specific functions remain invisible (because of of local identifiers scoping), and the rest of the program can only use the most abstract ones returned by the call to vectorclass.

One objection is that I could directly define each of the abstract functions in the program, and leave inside the definition of the coordinate-type dependent functions. Then would be hidden as well. That is true, but then the code for each coordinate-type would be cut in small pieces spread over the program, which is less redable and maintainable.

Actually, i do not even need to give them a name, and I could just keep the as anonymous functional values in a data structure indexed by the type and a string representing the function name. This structure being local to the function vector would be invisible from the rest of the program.

To simplify the use, instead of returning a list of function, I can return a single function called apply taking as argument an explicitly type value and a string, and apply the function with the proper type and name. This looks very much like calling a method for an OO class.

I will stop here, in this reconstruction of an object oriented facility.

What I tried to do is to show that it is not too hard to build usable object orientation in a sufficiently powerful language, including inheritance and other such features. Metacircularity of the interpreter can help, but mostly on a syntactic level, which still is far from negligible.

The first users of object orientation did experiment the concepts that way. And that is generally true of many improvements to programming languages. Of course, theoretical analysis also has a role and helped understand or refine these concepts.

But the idea that languages that do not have OO features are doomed to fail in some projects is simply unwarranted. If need be, they can mimic the implementation of these features quite effectively. Many languages have the syntactic and semantic power to do object orientation quite effectively, even when it is not built-in. And that is more than a Turing argument.

OOP does not address limitations of other languages, but it supports or enforces programming methodologies that helps write better program, thus helping less experienced users to follow good practices that more advanced programmers heve been using and developping without that support.

I believe a good book to understand all this might be Abelson & Sussman: structure and interpretation of computer programs.

$\endgroup$
6
$\begingroup$

None, really. OOP doesn't really solve a problem, strictly speaking; there's nothing you can do with an object-oriented system that you couldn't do with a non object-oriented system- indeed, there's nothing you can do with either that couldn't be done with a Turing machine. It all turns into machine code eventually, and ASM is certainly not object-oriented.

What the OOP paradigm does for you is it makes it easier to organize variables and functions, and allows you to move them around together more easily.

Say I want to write a card game in Python. How would I represent the cards?

If I didn't know about OOP, I might do it this way:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

I'd probably write some code to generate those cards instead of just writing them out by hand, but you get the point. "1S" represents the 1 of Spades, "JD" represents the Jack of Diamonds, and so on. I'd also need a code for the Joker, but we'll just pretend there's no Joker for now.

Now when I want to shuffle the deck, I need only "shuffle" the list. Then, to take a card off the top of the deck, I pop the top entry off of the list, giving me the string. Simple.

Now if I want to figure out which card I'm working with for the purposes of displaying it to the player, I'd need a function like this:

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

A little big, long, and inefficient, but it works (and is very unpythonic, but that's beside the point here).

Now what if I want cards to be able to move around the screen? I have to store their position somehow. I could add it on to the end of their card code, but that might be a little unwieldy. Instead, let's make another list of where each card is:

cardpositions=( (1,1), (2,1), (3,1) ...)

Then I write my code so that the index of each card's position in the list is the same as the index of the card itself in the deck.

Or at least it should be. Unless I make a mistake. Which I very well might, because my code is going to have to be rather complex to handle this setup. When I want to shuffle the cards, I have to shuffle the positions in the same order. What happens if I take a card out of the deck entirely? I'll have to take its position out as well, and put it somewhere else.

And what if I want to store even more information about the cards? What if I want to store whether each card is flipped? What if I want a physics engine of some kind, and need to know cards' velocity as well? I'll need another list entirely to store the graphics for each card! And for all of these data points, I'll need separate code to keep them all organized properly so each card maps somehow to all its data!

Now let's try this the OOP way.

Instead of a list of codes, let's define a Card class and build a list of Card objects from it.

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

Now, suddenly, everything is much simpler. If I want to move the card, I don't have to figure out where it is in the deck, then use that to get its position out of the array of positions. I just have to say thecard.pos=newpos. When I take the card out of the main deck list, I don't have to have to make a new list to store all that other data; when the card object moves, all its properties move with it. And if I want a card that behaves differently when its flipped, I don't have to modify the flipping function in my main code so that it detects these cards and does that different behavior; I just have to subclass Card and modify the flip() function on the subclass.

But nothing I did there couldn't have been done without OO. It's just that with an object-oriented language, the language is doing a lot of the work of keeping things together for you, meaning you have much less chance to make mistakes, and your code is shorter and easier to read and write.

Or, to summarize even more, OO allows you to write simpler-seeming programs that do the same work as more complex programs by hiding a lot of the common complexities of handling data behind a veil of abstraction.

$\endgroup$
3
  • 2
    $\begingroup$ If the only thing you take away from OOP is "memory management" then I don't think you have understood it very well. There is an entire design philosophy and a generous bottle of "correct by design" there! Also, memory management is certainly nothing inherent to object orientation (C++?), even though the need for it becomes more pronounced. $\endgroup$
    – Raphael
    Commented Mar 25, 2014 at 19:29
  • $\begingroup$ Sure, but that's the one-sentence version. And I used the term in a rather non-standard way as well. Perhaps it'd be better to say "handling information" than "memory management". $\endgroup$
    – Schilcote
    Commented Mar 25, 2014 at 20:16
  • $\begingroup$ Are there any non-OOP languages which would allow a function to take a pointer to something, as well as a pointer to a function whose first parameter is a pointer to that same kind of thing, and have the compiler validate that the function is appropriate for the passed-in pointer? $\endgroup$
    – supercat
    Commented Apr 19, 2014 at 21:23
3
$\begingroup$

Having written embedded C for a few years managing things like devices, serial ports, and communications packets between the serial ports, network ports, and servers; I found myself, a trained electrical engineer with limited procedural programming experience, concocting my own abstractions from the hardware which ended up manifesting in what I later realized are what normal people call 'Object Oriented Programming'.

When I moved over to the server side I was exposed to a factory that set up the object representation of each device in the memory on instantiation. I didn't understand the words or what was happening at first-- I just knew I went to the file named thus and such and wrote code. Later, I found myself, again, finally recognizing the value of the OOP.

I, personally, think this is the only way to teach object orientation. I had an Intro to OOP (Java) class my freshman year and it was completely over my head. OOP descriptions built on classifying kitten->cat->mammal->alive->thing or leaf->branch->tree->garden are, in my humble opinion, absolutely ridiculous methodologies because no one is ever going to try to solve those problems, if you could even call them problems...

I think it's easier to answer your question if you look at it in less absolute terms-- not 'what does it solve', but more from a perspective of 'here's a problem, and here's how it makes it easier'. In my particular serial ports case, we had a bunch of compile-time #ifdefs that added and removed code that statically opened and closed serial ports. The port open functions were called all over the place, and could be located anywhere in the 100k lines of OS code we had, and the IDE didn't grey out what wasn't defined-- you had to track that down manually, and carry it in your head. Inevitably you could have several tasks trying to open a given serial port expecting their device on the other end, and then none of the code you just wrote works, and you can't figure out why.

The abstraction was, although still in C, a serial port 'class' (well, just a structure datatype) that we had an array of-- one for each serial port-- and instead of having [the DMA equivalent in serial ports] "OpenSerialPortA" "SetBaudRate" etc. functions called directly on the hardware from the task, we called a helper function that you passed all the communications parameters (baud, parity, etc.) to, which first checked the structure array to see if that port had already been opened-- if so, by which task, which it would tell you as a debug printf, so that you could immediately jump to the section of code you needed to disable-- and if not, it then proceeded to set all the parameters via their HAL assembly functions, and finally opened the port.

Of course, there are dangers to OOP, too. When I'd finally cleaned up that codebase and made everything neat and clean-- writing new drivers for that product line was finally down a calculable, predictable science, my manager EOL'd the product specifically because it was one less project he would need to manage, and he was borderline removable middle-management. Which took a lot out of me / I found very discouraging so I quit my job. LOL.

$\endgroup$
4
  • 1
    $\begingroup$ Hi! This seems more like a personal history than an answer to the question. From your example, I see that you rewrote some horrible code in an object-oriented style, which made it better. But it's unclear if the improvement had much to do with object-orientation or if it was just because you were a more proficient programmer by then. For example, a good deal of your problem seems to have been from code being scattered willy-nilly about the place. That could have been solved by writing a procedural library, with no objects at all. $\endgroup$ Commented Sep 9, 2015 at 19:57
  • 2
    $\begingroup$ @DavidRicherby we had the procedural library, but that's what we deprecated, it wasn't just about code being all over the place. The point was we did this backwards. Nobody was trying to OOP anything, it just happened naturally. $\endgroup$
    – paIncrease
    Commented Sep 9, 2015 at 20:27
  • $\begingroup$ @DavidRicherby can you give any examples of procedural library implementations so that I can make sure we're talking about the same thing? $\endgroup$
    – paIncrease
    Commented Sep 9, 2015 at 20:32
  • 2
    $\begingroup$ Thanks for your answer and +1. A long time ago another experienced programmer shared how OOP made his project more reliable forums.devshed.com/programming-42/… I guess OOP was designed very intelligently by some professionals who might have faced some problems in procedural approach. $\endgroup$
    – user31782
    Commented Sep 10, 2015 at 8:06
2
$\begingroup$

there are many claims and intentions about what/where OOP programming has an advantage over procedural programming including by its inventors and users. but merely because a technology was designed for a certain purpose by its designers does not guarantee that it will succeed in those objectives. this is a key understanding in the field of software engineering dating to Brooks famous essay "No silver bullet" which is still relevant despite the OOP coding revolution. (see also the Gartner Hype Cycle for new technologies.)

many who have used both also have opinions from anecdotal experience, and this has some value, but it is known in many scientific studies that self-reported analysis can be inaccurate. there does seem to be little quantitative analysis of these differences or if there is, it is not cited so much. it is somewhat amazing how many computer scientists speak authoritatively on certain topics central to their field yet do not actually quote scientific research to support their views and do not realize they are actually passing on conventional wisdom in their field (albeit widespread).

since this is a science site/forum, here is a brief attempt to put the numerous opinions on firmer footing and quantify the actual difference. there may be other studies & hope others might point them out if they hear of any. (zen question: if there really is a major difference, and so much massive effort in the commercial software engineering field and elsewhere has been applied/invested toward realizing it, why should scientific evidence of this be so difficult to come by? shouldnt there be some classic, highly-cited reference in the field that definitively quantifies the difference?)

this paper employs experimental/quantitative/scientific analysis & specifically supports that comprehension by novice programmers is improved with OOP coding methods in some cases but was inconclusive in other cases (relative to program sizes). note this is only one of the many/major claims about OOP superiority being advanced in other answers & by OOP advocates. the study was possibly measuring a psychological element known as "cognitive load/overhead" wrt coding comprehension.

  • A comparison of the comprehension of object-oriented and procedural programs by novice programmers Interacting with Computers. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L Corritore (1999)

    This paper reports on two experiments comparing mental representations and program comprehension by novices in the object-oriented and procedural styles. The subjects were novice programmers enrolled in a second course in programming which taught either the object-oriented or the procedural paradigm. The first experiment compared the mental representations and comprehension of short programs written in the procedural and object-oriented styles. The second experiment extended the study to a larger program incorporating more advanced language features. For the short programs there was no significant difference between the two groups with respect to the total number of questions answered correctly, but the object-oriented subjects were superior to the procedural subjects at answering questions about program function. This suggests that function information was more readily available in their mental representations of the programs and supports an argument that the object-oriented notation highlights function at the level of the individual class. For the long program a corresponding effect was not found. The comprehension of procedural subjects was superior to object-oriented subjects on all types of question. The difficulties experienced by the object-oriented subjects in answering questions in a larger program suggest that they faced problems in marshaling information and drawing inferences from it. We suggest that this result may be related to a longer learning curve for novices of the object-oriented style, as well as to features of the OO style and the particular OO language notation.

see also:

$\endgroup$
2
  • 1
    $\begingroup$ I have respect for experimental studies. There is however the issue of ascertaining that they address the right questions. There are too many variables in what may be called OOP, and in ways of using it, for a single study to be meaningful, imho. Like many things in programming, OOP was created by experts to meet their own needs. When discussing the usefulness of OOP (which I did not take as the OP's topic, which is rather whether it addresses a shortcoming of procedural programming), one may ask: what feature, for whom, for what purpose? Then only do field studies become fully meaningful. $\endgroup$
    – babou
    Commented Mar 27, 2014 at 23:05
  • 1
    $\begingroup$ Anecdote warning: If a problem is smallish (e.g. up to around 500-1000 lines of code), OOP makes no difference in my experience, it might even get in the way by having to worry about stuff that makes very little difference. If the problem is large, and has some form of "interchangeable pieces" that moreover must be possible to add later on (windows in a GUI, devices in an operating system, ...) the organization OOP discipline provides is unavoidable. You certainly can program OOP without language support (see e.g. the Linux kernel). $\endgroup$
    – vonbrand
    Commented Sep 10, 2015 at 16:52
2
$\begingroup$

I disagree with seeing an opposition between procedural programming and object-oriented programming.

Procedural programming means that the program describes explicitly (and implicitly by means of loops and recursion) the sequence of operations to be performed. That form of programming heavily relies on program states represented by the successive values of the work variables. This is by contrast with pure functional programming where the order of computation is irrelevant and the computational process is stateless.

Object-oriented programming is a way to structure data by forming logical entities and enforcing "access rights" meant to protect the programmer against himself, so to say. This is an evolution of structured programming towards larger systems.

Procedural as well as non-procedural programming can be object-oriented or not.

$\endgroup$
1
$\begingroup$

To be concise Object Oriented Programming addresses the data security issues present in procedural programming. This is done by using the concept of encapsulating the data, allowing only the legitimate classes to inherit the data. Access modifiers facilitate achieving this goal. Hope that helps.:)

$\endgroup$
14
  • $\begingroup$ What are the data security issues present in the procedural programming? $\endgroup$
    – user31782
    Commented Mar 26, 2014 at 9:59
  • $\begingroup$ In procedural programming one can't restrict the usage of a global variable. Any function could use it's value. However in OOP I could restrict the usage of a variable to a certain class alone or only the classes that inherit it perhaps. $\endgroup$
    – manu
    Commented Mar 27, 2014 at 2:00
  • $\begingroup$ In procedural programming too we can restrict the usage of global variable by using the variable to certain functions, that is by not declaring any data global. $\endgroup$
    – user31782
    Commented Mar 27, 2014 at 3:47
  • $\begingroup$ If you don't declare it globally it's not a global variable. $\endgroup$
    – manu
    Commented Mar 27, 2014 at 3:49
  • 1
    $\begingroup$ "Secure" or "Correct" don't mean anything without a specification. These things are attempts to put a specification on code towards that goal: Types, Class Definitions, DesignByContract,etc. You get "Security" in the sense that you can make the private data boundaries inviolable; assuming that you must obey the virtual machine instruction set in order to execute. Object Orientation won't hide internal memory from somebody that can read memory directly, and a bad object protocol design is handing out secrets by design. $\endgroup$
    – Rob
    Commented Mar 27, 2014 at 17:16
1
$\begingroup$

Be careful. Read the classic by R. King "My Cat is Object-Oriented" in "Object-oriented Concepts, Databases, and Applications" (Kim and Lochovsky, eds) (ACM, 1989). "Object oriented" has become more a buzz-word than a clearcut concept.

Besides, there are many variations on the theme, with little in common. There are prototype based languages (inheritance is from objects, there are no classes as such) and class based languages. There are languages which allow multiple inheritance, others which don't. Some languages have an idea like Java's interfaces (can be taken as a form of watered down multiple inheritance). There is the idea of mixins. Inheritance can be rather strict (as in C++, can't really change what you get in a subclass), or very liberally handled (in Perl the subclass can redefine almost everything). Some languages have a single root for inheritance (typically called Object, with default behaviour), others allow the programmer to create multiple trees. Some languages insist that "everything is an object", others handle objects and non-objects, some (like Java) have "most are objects, but these few types here aren't". Some languages insist on strict encapsulation of state in objects, others make it optional (C++' private, protected, public), others don't have encapsulation at all. If you squint at a language like Scheme from the right angle, you see OOP built in without any special effort (can define functions that return functions that encapsulate some local state).

$\endgroup$

Your Answer

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

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