- Objects are responsible for preserving the integrity (e.g.,some invariant) of the data representation.
An invariant is just something that needs to stay true at all times. For example, if you have an object that represents a range of some sort, and stores a lower limit (let's call it a
) and an upper limit (b
), an invariant would be the requirement that a <= b
.
The object would maintain this invariant by preventing client code (the code that uses it) to change it in a way that results in a > b
. For example, let's say that the user can set these two values; in the setter method/property, before accepting the user provided value, you would check if a > b
, and throw an exception if it is, or use some other mechanism to maintain the invariant.
- The data representation is hidden from other objects
This is known as encapsulation; basically, you provide restricted set of public methods (the public interface) that other objects can use to interact with this object. This is like a public "contract" of sorts: your object basically declares "I provide these services, in this way and in this format". However, the internal state can be represented and manipulated in various ways (you can have various implementations) as long as the object confirms to the interface defined. This internal state is "invisible" to other objects, which is a good thing: this theoretically enables you to change the implementation details of the object without affecting (and thus having to change) the code in any other part of the system.
For example, consider a simple game where you have level that is an NxN grid with some items on it.
// (pseudocode)
class Grid
{
// represent items locations as an NxN 2D array of Booleans
// (omitted)
bool IsItemOn(x, y)
{
// check the 2D Boolean array
}
}
But, then, for one reason or another, you decide change the internal representation from the 2D Boolean array to a 1D Boolean array:
class Grid
{
// represented as 1D array of NxN Booleans
// (omitted)
bool IsItemOn(x, y)
{
// calculate the index into the 1D array from x and y
// check the value at the calculated index
}
}
However, because of some other considerations, you later decide to change the scheme, and just store the list of item locations. Maybe there are just 3 items in the level, and it seemed wasteful to use an array of NxN elements. This way, you'll just store 3 two-dimensional points.
class Grid
{
// just store item locations as (x, y)
// (omitted)
bool IsItemOn(x, y)
{
// check if this location matches any of the ones stored
}
}
In all cases, only the internal representation changes, and any external code remains unaware of that, as it's only the IsItemOn method that's visible from the outside. No other code needs to change, everything works as is.