Reoriented


“Education is what remains after one has forgotten what one has learned in school.”

-Albert Einstein

What have I learned about programming, and OOP in general?

One of the biggest eye-openers is very simple. An idea that can easily get lost when using high level languages or discussing the philosophy of what’s right and wrong in programming. It’s also lost on anyone who doesn’t study computer science in school and only programming (/raisehand). Just like we don’t consciously think about tying our shoes, we don’t always think about the intricate roots of programming when we’re doing it.

The idea is that math is the basis for programming, and follows much of the same rules of mathematics. Math and programming both share certain protocols for operating on data sets. Those protocols include things like inputs, outputs, domains, ranges, abstractions, and functions. I’m no math whiz, but I’ll do my best to explain what I mean.

The data set we deal with in math is the set of all numbers. We operate on the set by using functions like addition, subtraction, etc. Each operation has a domain, a range, and maps inputs to outputs, for example the addition function’s domain is the set of all numbers; range is the set of all numbers; takes two inputs; and gives one output (also from the set of all numbers). This is a guarantee made by the mathematical protocols. Given two numbers, the addition operation will give you a number equal to the total of the amounts. There are other properties of addition, such as commutativity and associativity. All of these rules for using addition and any other mathematical functions describe the operation’s expected behavior. These protocols tell us that we shouldn’t expect 2 + 2 = “fish” to ever be true.

I’d never heard the word abstraction in math class during my school days, and that’s a shame. Algebra and higher levels of math obviously build upon abstractions. We’ve all seen functions, such as f(x) = 2x + 3. This function like any algebraic equation uses a variable (and parameter), in this case x, to abstract the values that multiply by 2 in the function. This function shares the same domain and range as addition. Some functions, like g(x) = 2/(3x), can only accept certain inputs and give certain outputs. The domain for this is all numbers excluding zero, and the range is the same. This is because inputting zero would cause a division by zero, and due to the nature of the function, the set of possible outputs doesn’t contain zero. Numbers themselves are abstractions for quantities of specific items. A quantity of rocks is equal to the same quantity of jellybeans. Numbers are abstractions for quantities of anything.

What’s My Point?

My point is that these protocols are in place so we can correctly use math. In programming we are trying to achieve the same thing. Our programs define new protocols, have inputs, outputs, domains, and ranges, and various other properties that we must define, and make sure our program guarantees. This goes for any style of programming including OOP. Whatever style of programming we use, we should deliberately define and document the protocols of our programs. The difference is that we often deal with different data sets, specifically the data sets that are the focus in our programs.

OOPsie

There’s an idea in OOP that’s particularly antagonizing for new programmers and relates to the phrase “modeling the real world”. One of the first things you hear when starting out with OOP is that it’s all about creating classes that model the real world. This is absolutely untrue and causes nothing but problems for new programmers. Classes in an OO language are often named after real-world objects that closely resemble the behavior of the class to make their purpose easier to understand. Take for instance a graphical button in a user interface library. The class for a button will probably be called Button. We could easily name it TinyRectangularUIElementThatPerformsAnActionWhenYouActivateIt, but that’s not very intuitive. Naming it after a real-world analog makes it simpler to understand its purpose. The methods for Button also won’t resemble the behaviors of an actual physical button. It’s methods support its usage within the system. Ultimately, its superficial behavior is that a user can click it to activate some action. That’s the only relationship between the real world and OOP. You don’t search for real world objects and then create an OO version, you create an abstraction specific to implementing a feature of the system and name it something that makes its purpose clear and recognizable, which are typically real-world objects. If you want a great explanation of the faults of trying to model the real world, read Heuristics and Coffee from ObjectMentor.com which uses an example of creating software for a coffee maker.

About Abstraction

Abstraction, specifically in programming, is the means by which the set of all possible implementations of an idea map to a single general entity, such that clients (users) of an abstraction can’t tell what implementation is in use. Each style of programming uses some base element of abstraction to map implementations to generalizations, such as procedures, classes, modules, functions, etc.

In the book, “Program Development in Java: Abstraction, Specification, and Object-Oriented Design” by Barbara Liskov, she discusses four types of abstraction: abstraction by parameterization, procedural abstraction, data abstraction, and iteration abstraction. OOP specifically focuses on data abstraction, but of course uses procedural abstraction (object methods), and procedures sometimes use parameters. Data abstraction is usually explained using concepts like encapsulation and information hiding, which leads to more questions, like what do these terms mean and how are they used correctly? Each type of abstraction is a particular technique for generalizing ideas. Some of the key concepts are:

Abstraction by Parameterization

  • They use parameters to ignore specific data items allowing operation on any one of a set of like items.

Procedural Abstraction

  • They map a set of inputs to a set of outputs.
  • Users of a procedure need not be concerned with how the procedure generates output (its implementation).
  • Different languages represent procedures differently, e.g., functions, subroutines, static methods.
  • The data used to implement procedural abstractions may exist locally and globally.
  • Procedure parameters expose the representation of the data they operate on.

Data Abstraction

  • They use procedures (methods) to operate on local data, preferring to avoid global data.
  • The data used to implement data abstractions remains hidden.
  • Methods of data abstractions expose the type of their parameters, but the types can be other abstractions, thereby hiding the representation of the parameters.

Iteration Abstraction

  • Hides the type of collection in the implementation.
  • Hides the means by which we retrieve items from the collection.

Liskov mentions a fifth type of abstraction, abstraction by specification, which is when comments, and documentation for an abstraction permits sufficient usage of the abstraction without having to look at its implementation.

Politics, Religion, and Getters and Setters

Search online and you’ll find countless philosophical battles regarding the use of “getters” and “setters”, otherwise known as object methods that read state and modify state respectively. Read about the debate enough and you’ll always think you’re doing something wrong. Philosophy aside, Liskov proposes some actual metrics for determining whether methods are destructive, such as maintaining invariants and aversion to exposing object representation. We achieve this by using the correct method categories, and correct usage of immutability and mutability.

For those unfamiliar, an invariant is something that must always be true about an abstraction for its state to be valid. “Invariant” is another mathematical concept, and is defined in Wolfram MathWorld as “A quantity which remains unchanged under certain classes of transformations”. Class methods should transform the object, always leaving the state (composed of member variables) in a valid state.

Exposing representation, or “exposing the rep” as Liskov says, refers to leaking internal data in such a way that allows clients to break the object. Knowing implementation details of a class not only makes clients dependent on whatever member they access, but if the relationship is more than a weak relationship, i.e., the client modifies this representation item, then the client is inadvertently transforming the object and avoiding any checks that are in place to maintain the class invariant.

Some people say getters and setters are evil, which is misleading. Class methods that break the invariant, and/or expose the representation are evil, whether we call them getters, setters, or some word from an alien language on a planet not yet discovered.

Liskov breaks methods into four categories. The categories are creators, producers, mutators, and observers. These categories are also related to mutability and immutability. For those who are unfamiliar, mutable types are classes whose object instance data changes, and immutable types are classes whose object instances do not change state, rather they produce new versions of themselves.

Operation Type Description Used By Type
Creators Create objects where none existed, i.e., constructors. Mutable/Immutable
Producers Create objects of the same type from an existing instance. Immutable
Mutators Modify the state of an object. Mutable
Observers Create objects of other types used to get information about an object. Mutable/Immutable

Keep in mind I’m not citing doctrine here. These are just guidelines, for example when using certain design patterns a class may not have a public constructor, or you may design a class that has no observers, etc. Most importantly, immutable classes cannot have mutators; they must always use producers to get instances with different values.

Lets look at a trivial example. Most languages provide a String class. A String class in most cases is immutable. Lets say we wanted to design our own TrivialString class and we did the following in Java:

public class TrivialString
{
	private char[] characters;

	// Constructors...

	public char[] toCharArray()
	{
		return this.characters;
	}
}

Some people might look at this and say it’s bad because you’re returning a character array which is the same type used to implement the class, but this is not the problem at all. The problem is that clients of TrivialString can change the internal value of a string object using the returned reference to the internal array.

public class TrivialString
{
	private char[] characters;

	// Constructors...

	public char[] toCharArray()
	{
		return Arrays.copyOf(characters, characters.length);
	}
}

There’s no reason we can’t return an array of characters if you want that as part of this class’ behavior, and as long as we do it correctly. In the second version we don’t expose the representation because we return a copy. Clients can observe the TrivialString‘s characters as an array, but don’t disturb the object’s state. The first example is the reason why they say getters (and setters) are evil, because programmers create a class and immediately write getters and setters for every member that get and set the members directly without considering the effects.

In the second version, we could easily rewrite the implementation to use a vector, or other structure and no clients of toCharArray would ever know the difference.

That’s all for now.

Comments!

This site uses Akismet to reduce spam. Learn how your comment data is processed.