Encapsulation
What is encapsulation? Well, in a nutshell, encapsulation is the hiding of data implementation by restricting access to accessors and mutators. First, lets define accessors and mutators:
Accessor An accessor is a method that is used to ask an object about itself. In OOP, these are usually in the form of properties, which have, under normal conditions, a get method, which is an accessor method. However, accessor methods are not restricted to properties and can be any public method that gives information about the state of the object.
Mutator Mutators are public methods that are used to modify the state of an object, while hiding the implementation of exactly how the data gets modified. Mutators are commonly another portion of the property discussed above, except this time its the set method that lets the caller modify the member data behind the scenes.
Ok, now lets look at a different example that contains an accessor and a mutator:
So, the use of mutators and accessors provides many advantages. By hiding the implementation of our Person class, we can make changes to the Person class without the worry that we are going to break other code that is using and calling the Person class for information. If we wanted, we could change the fullName from a String to an array of single characters (FYI, this is what a string object actually is behind the scenes) but they callers would never have to know because we would still return them a single FullName string, but behind the scenes we are dealing with a character array instead of a string object. Its transparent to the rest of the program. This type of data protection and implementation protection is called Encapsulation. Think of accessors and mutators as the pieces that surround the data that forms the class.
Abstraction
Data abstraction is the simplest of principles to understand. Data abstraction and encapuslation are closely tied together, because a simple definition of data abstraction is the development of classes, objects, types in terms of their interfaces and functionality, instead of their implementation details. Abstraction denotes a model, a view, or some other focused representation for an actual item. Its the development of a software object to represent an object we can find in the real world. Encapsulation hides the details of that implementation.
Abstraction is used to manage complexity. Software developers use abstraction to decompose complex systems into smaller components. As development progresss, programmers know the functionality they can expect from as yet undeveloped subsystems. Thus, programmers are not burdened by considering the waysin which the implementation of later subsystesm will affect the design of earlier development.
The best definition of abstraction I've ever read is: "An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of object and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer." -- G. Booch, Object-Oriented Design With Applications, Benjamin/Cummings, Menlo Park, California, 1991.
Lets look at this code for a person object. What are some things that a person can do? Those things must be represented here in our software model of a person. Things such as how tall the person is, and the age of the person; we need to be able to see those. We need the ability for the person to do things, such as run. We need to be able to ask the person if they can read.
So, there we have started to create a software model of a person object; we have created an abstract type of what a person object is to us outside of the software world. The abstract person is defined by the operations that can be performed on it, and the information we can get from it and give to it. What does the abstracted person object look like to the software world that doesn't have access to its inner workings? It looks like this:
You can't really see what the code is that makes the person run. This is encapsulation that we discuseed.
So, in short, data abstraction is nothing more than the implementation of an object that contains the same essential properties and actions we can find in the original object we are representing.
Inheritance
Now lets discuss inheritance. Objects can relate to eachother with either a “has a”, “uses a” or an “is a” relationship. “Is a” is the inheritance way of object relationship. The example of this that has always stuck with me over the years is a library (I think I may have read it in something Grady Booch wrote). So, take a library, for example. A library lends more than just books, it also lends magazines, audiocassettes and microfilm. On some level, all of these items can be treated the same: All four types represent assets of the library that can be loaned out to people. However, even though the 4 types can be viewed as the same, they are not identical. A book has an ISBN and a magazine does not. And audiocassette has a play length and microfilm cannot be checked out overnight.
Each of these library’s assets should be represented by its own class definition. Without inheritance though, each class must independently implement the characteristics that are common to all loanable assets. All assets are either checked out or available for checkout. All assets have a title, a date of acquisition and a replacement cost. Rather than duplicate functionality, inheritance allows you to inherit functionality from another class, called a superclass or base class.
Let us look at loanable assets base class. This will be used as the base for assets classes such as book and audiocassette:
This LibraryAsset is a superclass, or base class, that maintains only the data and methods that are common to all loanable assets. Book, magazine, audiocassette and microfilm will all be subclasses or derived classes or the LibraryAsset class, and so they inherit these characteristics. The inheritance relationship is called the “is a” relationship. A book “is a” LibraryAsset, as are the other 3 assets.
Let’s look at book and audiocassette classes that inherit from out LibraryAsset class:
Now, lets create an instance of the book class so we can record a new book into the library inventory:
You see, when we create a new book, we have all the properties of the LibraryAsset class available to us as well, because we inherited the class. Methods can be inherited as well. Let’s add a few methods to our LibraryAsset class:
Now, our “myBook” we created above automatically inherited these methods, and we didn’t even have to touch the Book class in order for it to happen. The book and audiocassette classes above automatically inherited the abilities to be checked out and checked in. In our “myBook” above, now we can check the book out by calling “myBook.CheckOut()”. Simple! One of the most powerful features of inheritance is the ability to extend components without any knowledge of the way in which a class was implemented.
Declaration options, such as Public and Private, dictate which members of a superclass can be inherited. For more information on this, see the Declaration Option section of Eric's post.
Polymorphism
Polymorphism means one name, many forms. Polymorphism manifests itself by having multiple methods all with the same name, but slighty different functionality. Many VB6ers are familiar with interface polymorphism. I'm only going to discuss polymorphism from the point of view of inheritance because this is the part that is new to many people. Because of this, it can be difficult to fully grasp the full potential of polymorphism until you get some practice with it and see exactly what happens under different scenarios. We’re only going to talk about polymorphism, like the other topics, at the basic level.
There are 2 basic types of polymorphism. Overridding, also called run-time polymorphism, and overloading, which is referred to as compile-time polymorphism. This difference is, for method overloading, the compiler determines which method will be executed, and this decision is made when the code gets compiled. Which method will be used for method overriding is determined at runtime based on the dynamic type of an object.
Let’s look at some code:
You see our library asset class. Pay attention to the overridable function CalculateFineTotal(). In LibraryAsset, we have defined the default functionality for this method that any derived classes can use. Any class derived from LibraryAsset can use this default behavior and calculate fines based on the default implementation of $1.25 per day late. This is true for our Magazine class. We didn’t override the function so when late fees are calculated for late magazine returns, it will use the default implementation.
Now look at the book class. We have overridden the CalculateFineTotal to use a different value when determining late fees. The overrides keywork in VB tells the caller that any method call will use the virtual method found in Book, not the default implementation found in LibraryAsset. We have implemented runtime polymorphism – method overriding.
Lets move on to AudioCassette. Here we have the same method overriding we found in the book class. Fines are calculated based on $0.25 per day. Notice we’ve added something extra. We’ve added the Overloads keywork to our function and to a new function with the same name, except the new function now accepts a parameter. Now the caller can call either method, and depending on whether or not a parameter is passed, that determines with method will be executed. Notice we do not include the overrides keywork in the 2nd function with a parameter. This is because not method exists in LibraryAsset with that same signature (accepting a parameter of type double). You can only override methods with the same signature in a base class.
Now lets look at some code that creates all these library items and checks them in and cacluates our fines based on returning them 3 days late:
The output will look like the following:
Magazine: 3.75
Book: 2.25
AudioCassette1: 0.75
AudioCassette2: 9
You can see how all of our output was different, based on the method that was executed. We created a new Magazine, which is a type of LibraryAsset. That is why the instantiation says “myMagazine As LibraryAsset”. However, since we actually want a magazine, we create a “New Magazine”. Same thing with book. For Book, its a little bit more tricky. Since we created a Book of the type LibraryAsset, this is where the polymorphism comes into play. Book overrides the CalculateFineTotal of LibraryAsset. Audiocassette is a little bit different. It actually extends the implementation of LibraryAsset by including an overloaded function for CalculateFineTotal(). If we weren’t going to use the function that took a parameter, we would create it the same way we created the Book and Magazine classes. But in order to use the overloaded function, we have to create a new AudioCassette of the type AudioCassette, because LibraryAsset doesn’t support the overloaded function.
Only the Magazine used the default method found in the base class. Book and AudioCassette used their own implementations of the method. Also, at compile time, the decision was made which method would be used when we calculate amountDue for the AudioCassette class. The first call used the 1st method in AudioCassette without parameters. The 2nd call used the 2nd method with a parameter.