I'm stuck on this concept.
This is part of an explanation I saw on a site:
Hiding the Implementation
A primary consideration in object-oriented design is separating the things that change from the things that stay the same.
This is particularly important for libraries. Users (client programmers) of that library must be able to rely on the part they use, and know that they won't need to rewrite code if a new version of the library comes out. On the flip side, the library creator must have the freedom to make modifications and improvements with the certainty that the client code won't be affected by those changes.
This can be achieved through convention. For example, the library programmer must agree to not remove existing methods when modifying a class in the library, since that would break the client programmer's code. The reverse situation is thornier, however. In the case of a field, how can the library creator know which fields have been accessed by client programmers? This is also true with methods that are only part of the implementation of a class, and not meant to be used directly by the client programmer. But what if the library creator wants to rip out an old implementation and put in a new one? Changing any of those members might break a client programmer's code. Thus the library creator is in a strait jacket and can't change anything.
But I can't imagine a real situation where this can happen.
Can someone show me a practical example of this in real life?
This is how I imagine this:
public void print(String message)
{
System.out.println(message);
}
What difference does it make if a library client knows this implementation or not?
There are many examples in the real world for this. For instance, if you consider buying a grocery previously v/s buying a grocery now. Or building a house previously v/s building a house now.
As time goes on the implementation changes of anything, but still the end result is same like bought grocery or built house. So, the library must have methods like buyGrocery()
or buildHouse()
, the implementation of which keeps on changing, but the user of the library still calls the same methods to attain the end results. Hope this answered your query.
Cheers!
Say for example, you're working with a LinkedList
of names, and you make a utility method to print all the names in the list:
public void print(LinkedList<String> names)
{
for (String name : names)
System.out.println(name);
}
This works, but it limits you to only using a LinkedList
. What if for some reason, some other code that you have runs slowly with a LinkedList
, so you change it to an ArrayList
? Well, now you have to go and change this method too.
public void print(ArrayList<String> names)
{
for (String name : names)
System.out.println(name);
}
But within this method, it doesn't matter if it is a LinkedList
or an ArrayList
, as long as you can iterate over it. This is where you want to hide the implementation details from your code, by using an interface:
public void print(Iterable<String> names)
{
for (String name : names)
System.out.println(name);
}
Now, you don't even have to pass in a List
- you could pass in a HashSet
, and it would still work. So, from your original qoute:
A primary consideration in object-oriented design is separating the things that change from the things that stay the same
The things that change, in this case, would be how you iterate over the collection, represented by the ArrayList
and LinkedList
implementations.
The thing that stays the same is the fact that you can iterate over the collection, which is what the Iterable
interface represents.
A simple example could be if a log
method was used for logging errors. Maybe first all it did was print to the console. Then later you wanted to write errors to a file:
public void log(String message)
{
// write to file
}
Then later you decided to write to a database or make a remote call:
public void log(String message)
{
// make network call
}
As long as the implementation is hidden, clients can continue calling log()
and nothing will break because you did not change the method signature.
You can also have multiple implementations of this method by extracting an interface, as shown by @tima.
The implementation is not really hidden from view. Especially since most libraries out there that you will use are open source. "Hidden", in this case, refers to anything that is not part of the public API. The public API of a library consists of the public methods and fields that a library exposes. Once a library releases a version, it should continue to support the public API on future versions. Anything else is considered hidden and users of that library cannot rely on that "hidden code" being there in future version of that library.
EDIT:
But how could the client rely on code if it was not hidden, for example?
So lets say that a library comes out with a method that looks like this
public void doSomething(CharSequence x);
This is considered not hidden because it is public. Me as a user of that method is expecting it to exist in future versions of that library. So I am expecting that the input to that method is a CharSequence
. If the author of that library wants to change that to String
then that would be wrong. They should not change that CharSequence
to a String
because the users of the previous version are expecting CharSequence
and switching it to a String
might have negative consequences when the previous users of the library upgrade to the new version. In this case, the code might not compile.
So basically, if it is not hidden (aka part of the public API) then the author should not make any changes to the public API that would make previous versions of the library to not work.
This comes up all the time and there are ways around it. For example, when log4j
upgraded to version 2, their changes were so dramatic that their API had to break and hence created a whole new library called log4j 2
. So its a completely different library with different package names. It is not a new version of the old library, its a new library with similar name.
As a contrast to that, take a look at the Java SE's HashTable
class. This is old class that should not be used anymore. But Java has a strict rule that the old public API of previous versions must still be supported in new versions. So Java cannot do what log4j
did.
Another example is Spring. Spring tries to follow the approach that Java did, in that you only need to update the version and your old code should work. But Spring does deprecate parts of its public API. It will remove old classes and methods that are public but that it really does not want people to use anymore. In this case, me as a user of Spring might find it hard to upgrade from version 1 to version 4, for example. Mainly because some of the public API might have been deprecated.
So here I have given three different ways library writers have tackled this situation.
1) Side steps the old version and create a whole new library. E.g. Log4j.
2) Be strict and always support old public AIP's. E.g. Java.
3) Slowly deprecate old public API methods or classes. E.g. Spring.
Me as a user of these libraries would prefer that old public API be supported. I have used all three of those examples. I have had to work with old HashTable
classes on legacy code I was upgrading to Java 8 and was happy that it was still supported. I have felt the pain of upgrading from log4j
1 to log4j 2
. I have also upgraded Spring on a complicated project and have had adverse affects that needed to be troubleshooted. I can tell you that being strict to your old public API's is easiest on the users of said library.