How will encapsulation help when making changes in code and from its
rippling effects. For a data member, if I change its type from int to
float, (even if I am exposing this using property) I will need to
change variable type where I am using already using this code.
The benefit of encapsulation is that it lets you change the internal implementation without breaking client code. It doesn't protect you if you decide that you need to change the interface to your code, but that's a different matter.
Example: Say you have a value representing the price per unit of some commodity. The price is expressed in cents, and because you don't deal in fractional cents you decided to make the property an integer (I'll use C here because I'm not very familiar with C#):
int _price
int pricePerUnit(void) {
return _price;
}
int priceForUnits(int units) {
return units * _price;
}
That all works out fine until one day when somebody notices that your firm is losing a lot of money due to rounding errors. Many of the commodities that you track are bought and sold in lots of many thousands of units, so you need to start tracking the price to an accuracy of at least 0.001 cent. Because you were smart enough to encapsulate the price instead of letting clients access it directly, you can make that change pretty quickly:
double _dprice
int pricePerUnit(void) {
return (int)_dprice;
}
int priceForUnits(int units) {
return (int)(units * _dprice);
}
The interface that clients use to obtain prices stays the same, but the data they get back is now more accurate. If the price per unit is $1.001, priceForUnits(1000000)
will now return a price that's $1000 greater than before. That happens even though you haven't changed the interface to your system at all, and you therefore haven't broken any client code.
Now, that may not always be all that you need to do. Sometimes you'll need to change or augment your interface so that you can report the price more accurately to clients, too:
double pricePerUnit() {
return _dprice;
}
A change like that will break client code, so you might instead keep the old interface and provide a newer, better routine:
int pricePerUnit() {
return (int)_dprice;
}
double accuratePricePerUnit() {
return _dprice;
}
You and the rest of your team can then embark on the process of converting all the clients of your system to use the newer, better accuratePricePerUnit()
. The client code will get more accurate as you make progress on that task, but even the old stuff should continue to work as well as it did in the past.
Anyway, the point is that encapsulation lets you change the way the internals work while presenting a consistent interface, and that helps you make useful changes without breaking other code. It doesn't always protect you from having to update other code, but it can at least help you do that in a controlled manner.
I think that OOP lends itself best to when you have state that you can't destroy and re-create on a whim, or where that's a bad model- for example, if you consider 3D rendering, then the state of the renderer is not trivially destroyed and recreated, and it would be suicide for your performance to do so each frame.
In C++, then you need to add the fact that it doesn't have garbage collection and many FP techniques depend upon that for remotely acceptable performance.
Otoh, from a FP POV, a Eat() function simply allows an Animal to turn
Food into Poop and Energy. It's harder to imagine Eat() being anything
else (for me, at least).
That's completely equivalent to the OOP variant. There's no meaningful difference between
struct Animal { ... };
std::tuple<Poop, Energy> Eat(Animal*, Food);
and
class Animal {
public:
std::tuple<Poop, Energy> Eat(Food);
};
A functional variation would return a new Animal which has eaten.
Typically, I recursively divide my program to obtain modules/submodules/classes/functions and member functions, then I often attempt to implement those functions or member functions functionally if possible.
Best Answer
First off, your example isn't an object-oriented program. It's a procedural program that happens to store data in objects, because that's the tool that your language (Java?) provides for structured data.
A true object-oriented program consists of objects that interact with each other -- it's about behavior rather than data (I realize that is a controversial statement, so here's a link where you can see multiple definitions of object-orientation from people with more credentials than me; note that behavior appears in most of them).
In a true object-oriented program, according to the definition I use, you have independent objects interacting with each other. The role of the
main
function is to create the initial objects and wire them together.As a simple example, consider a web-application that's built on top of a database. This application could be broken into objects in many ways, but here's one of them: a
Networking
object that accepts connections, parses the HTTP request, and dispatches to an appropriateController
object, which interacts with aDatabase
object and produces the response (if you'd like to associate one or moreView
objects with each controller, feel free to do so). You could also add aThreadpool
object to provide separate streams of execution.The role of
main
in this application might be to:Database
objectController
objects, and associate them with theDatabase
objectNetwork
object, and associate all of theController
objects with it.Network
object running (which might also involve creating theThreadpool
and wiring it into theNetwork
).These setup steps could be explicitly specified in
main
, or they could be handled by some other object. For example, in a typical Spring application, all that themain
function does is create the application context (a single object). This triggers creation and wiring of all the objects mentioned in the configuration for that application context.