Perhaps an example can help explaining the benefits of object oriented programming.
Consider a linked list, it consist of node structures containing some data and pointing to other nodes:
struct Node {
Node* next;
int data;
}
Assume a list of these (I named them for convenience):
Node c{null, 4};
Node b{&c, 73};
Node a{&b, 42};
Node* list = &a;
You can use this list as follows:
std::cout << list->data; // prints 42
std::cout << list->next->data; // prints 4
All is fine here, but imagine somewhere in your program the following happens:
Node d{&a, 17};
list->next->next = &d;
Now the list is messed up, it is: [a, b, d, a, b, d, …]
; node c
is lost and the list has become an endless repetition.
1a
To answer the first part of part 1 of your question, when Node::next
is private the problematic assignment list->next->next = &d;
can not happen.
1b & 2
Given a public setter:
void Node::setNext(Node* new_next) {
this->next = new_next;
}
As you say, then anyone can just use list->next->setNext(&d);
. This leads to the answer to part two of your question: having a setter setNext()
is bad. Lists should have modifying operations like insert(value)
, append(value)
, and remove(value)
.
This is an example of the general rule: classes should have operations that fit their intended meaning and use, not getters and setters for every field the implementation happens to have. Or the version you read: "Don't ask for the information you need to do the work; ask the object that has the information to do the work for you."
3
Your question, "I don't understand why implementing objects is more efficient than just if-statements and function calls", is a difficult one. First the word 'efficient' can have two meanings:
- the programs run faster and/or consume less resources
- writing a program takes less effort
For the first point running faster or consuming less resources, both object oriented programs and programs using if-statements and function calls can be efficient in this sense or very inefficient. For some problems thinking about them in an object oriented manner, leads to a more elegant and efficient solution than the if-statements and function calls approach. For other problems object oriented programming just gets in the way and causes overhead.
On the second interpretation of 'efficient': writing a program takes less effort.
- As @JB_King states object orientation helps to organize things that belong together.
- Once you have the list class, in other parts of the code you can think in terms of maintaining a list of 'x'es and sending a list of 'y's, instead of pointer manipulations. In addition when you look at the C++ standard library, the java runtime library, Boost Collection, Commons collections, Google guava, and others you'll find that others have already written List classes and that perhaps for your programming problem a list is not the best class but some other kind of collection.
So once you have (or someone else has) written a class you can reuse it and you do not have to write it again.
NOTE: of course the same holds for good libraries of functions, my answer is lacking a good explanation of why object oriented is better reusable than functional libraries.
Elaborations and side nodes
Even if you decide that this list class really does require a setNext()
-method, then the method can have advantages over directly manipulating the Node::next
-field. The method can perform extra checks; e.g.:
Node:setNext(Node* newNext) {
if (this->next == null)
{
this->next = newNext;
}
else
{
throw new Error();
}
}
Making fields private does not prevent malicious programmers/code from reading and manipulating the field; i.e.
class SecurityController {
private:
std::string secretPassword;
};
and
struct SecurityController2 {
std::string secretPassword;
}
are both equally (in)secure.
Declaring things private (and other methods of hiding implementation details) only helps well behaving programmers to create better code.
Initialization
Sometimes initializing an object is the only time that direct access to an object's fields is required. Then you can define a constructor that accepts and sets the field, e.g.:
Node::Node(int value_) {
this->value = value_;
}
However, often there are better constructors that do not require the caller providing values. For example:
List::List() {
…
}
to construct an empty List
, and
List::List(std::iterator<int> start, std::iterator<int> end) {
…
}
to create a list containing copies of the values from the C++ STL range [start, end>.