Fundamental Object Oriented Design Principles (Part 2): Encapsulation

6 minute read

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.

This article assumes that you understand at least one object oriented programming language. C++, Delphi, C# and Java are examples of object oriented languages, Javascript is an object-based language (it uses object prototyping instead of classing). If you are unsure: a true object oriented language has classes, objects and very often interfaces.

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?

You: Yes

Me: Where is it now?

You: Out in the parking lot

Me: What color is it?

You: Blue

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?

You: Yes

Me: Are you sure?

You: Yes

Me: Why?

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?

You: Yes

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.

Me: How?

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.

Type
  TClassName = Class(TObject)
    private
      {private fields}
      {private methods}
    protected
      {protected fields}
      {protected methods}
  public
      {public fields}
      {public methods}
  end;

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.

TArc = class
private  
  FRadius: double;
  procedure SetRadius(const AValue: double);
public
  property Radius: double read FRadius write SetRadius;
end;

In Summary

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.

Leave a Comment