patternModerate
What problems of procedural programming does OOP solve in practice?
Viewed 0 times
whatpracticeprogrammingproceduralsolvedoesproblemsoop
Problem
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
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
Solution
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.
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.
Context
StackExchange Computer Science Q#22867, answer score: 10
Revisions (0)
No revisions yet.