A look at Encapsulation as provided by Object Oriented languages and how it combines data and functionality.
This is the second post in a series on Object Oriented design. In order to fully understand why the study of Design Patterns is important we need to start by looking at what good Object Oriented (OO) software design is. Object Oriented languages offer some unique features that facilitate good design.
These design principles can be applied to non-objected oriented languages. However, applying OO concepts to non-OO languages takes some more effort.
What is Encapsulation?
Objects are the building blocks used in OO languages. They are the structures that encapsulate data and functionality together, this is encapsulation at its simplest.
Objects can be uniquely identified by a reference
The concept of an object may be hard to understand coming from procedural or functional languages. OO developers are often confronted with arguments that OO offers nothing more than say a procedural language. Let me explain OO encapsulation in the form of a conversation:
Me: Do you own a car?
Me: Where is it now?
You: Out in the parking lot
Me: What color is it?
Me: Well that about narrows it down to a quarter of the cars. Give me more details.
You: OK. Its a Honda Accord, 2010
Me: There are two cars matching the description. Can you be more specific?
You: My license plate is ?????????
Me: So is this your car?
Me: Are you sure?
You: It matches the details I know about it.
Me: So if I replaced your car with the same color, make, model and license plate would it be your car?
You: Well… no. I won’t be able to tell immediately that its not, but I’ll figure it out once I get in and try to start it with my key.
Me: What if it has an identical lock?
You: You are very annoying, you know that?
Me: Yeah, Yeah. Can I ask one last question?
You: OK, but you are testing my …
Me: Can you really know if any car is yours without inspecting every last attribute?
Me: How could you possibly do that? Sorry, I guess that was not my last question
You: Simple, all I’d have to do is watch it all the time
An object is essentially just a reference (pointer) to a structure that encapsulates both data and allows specific functionality to be associated with that type. Two different objects will have two different references. This reference would be the equivalent to always watching the car. Modifying a car would de-reference the variable and modify the underlying data. As you’ll learn soon, we actually don’t want this, but objects offer protection against dangerous direct access. So far you may say that you can do the same with pointers to structs (records). You may be right as far as C++ is concerned, since C++ structs and classes are pretty much the same except for default visibility of members. In languages like Delphi objects always exist on the heap, while records can exist on the stack or the heap. The line between records and objects have blurred, but only because records are now behaving more like objects. Here is the big difference: an object is referenced via a variable defined as the class of the object or one of its ancestoral classes. We can also reference an object via one or more of the Interfaces that it supports. This allows for safe access of the type behind the reference.
Objects keep data and functionality together
So what makes an object more than just a pointer to a structure in memory? It combines functionality closely with data. Let us return to the parking lot:
Me: What happens when you turn the key in the ignition?
You: The car starts.
You: I don’t know, all I do is turn the key.
Me: And then?
You: I place it in drive, step on the gas and go?
Me: But how does the car actually work?
You: I only care that it does.
Suddenly our distinction becomes even more clear. We can directly operate on the car: turn the key, put it in drive, step on the gas. If we were to model this in non OO languages we may have procedures where the pointer to the car data structure is passed in. In the case of an OO language the procedures that operate directly on the object (methods) are easily found and directly defined in the class definition
Objects allows hiding internal functionality and private data
The car object hides the real operation under the hood, we just see the instrumentation and controls. This form of abstraction is specifically facilitated by the encapsulation of data and functionality in the object. In contrast, we may have a hard time hiding some of the functionality in a procedural language because the procedures operating on the car would need access to the inner workings (lower level functionality and data) of our car since they are required to operate on them.
In C++ the class definition is usually in a header (.h, .hpp, .hxx) file and the implementation is usually in a code (.c, .cpp, .cxx) file. Similarly in Delphi a class definition is usually placed in the Interface section of a unit and the actual code for the class’ functions are placed in an Implementation section of a unit. Other units that use this unit (
uses statement) are essentially including the interface section. In Java\C# the definition and implementation is combined in contained structure, but consumers of the class have limited visibility. So from C++ to Delphi to C# we progressively see less of a physical separation of interface and implementation as far as code files, but it is not as concerning as it sounds. It does not affect visibilty outside of the class.
Objects provides access permissions
In most object oriented languages we have the concept of accessibility control sections. In the case of Delphi class members are in one of the following sections: Public, Protected or Private. Private is accessible within the class and protected within the same unit (file) or in decendent classes. Public is what consumers of objects of this class can access.
Besides the sections access can be further controlled via properties. Properties appear as fields and may actually have a one-to-one relationship with fields, but they can allow for error checking or controlling consumer’s ability to read or write values to the object. For instance in the case of our TCircle we could have an internal field FRadius to represent the radius we can directly read radius private field via the property, but setting the radius is done via a “setter”. In this method we may, for instance, ensure that the radius is positive.
Encapsulation organizes code and controls access. It allows us to refer to a group of related data via a reference yet still only operating on it via well defined properties and well defined methods.