<<Clean Code>> Quotes: 6. Objects and Data Structures

Wenzhi Lin
4 min readNov 18, 2018

--

Data Abstraction

Hiding implementation is not just a matter of putting a layer of functions between the variables. Hiding implementation is about abstractions! A class does not simply push its variables out through getters and setters. Rather it exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation.

We do not want to expose the details of our data. Rather we want to express our data in abstract terms.

Data/Object Anti-Symmetry

Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions.

Procedural code (code using data structures) makes it easy to add new functions without changing the existing data tuhrcutres. OO code, on the other hand, makes it easy to add new classes without changing existing functions.

Procedural code makes it hard to add new dat structures because all the functions must change. OO code makes it hard to add new functions because al the classes must change.

The Law of Demeter

There is a well-known heuristic called the Law of Demeter that says a module should not know about the innards of the objects it manipulates.

More precisely, the Law of Demeter says that a method f of a class C should only call the methods of these:

  • C
  • An object created by f
  • An object passed as an argument of f
  • An object held in an instance variable of C

The following code appears to violate the Law of Demeter because it calls the getScratchDir() function on the return value of getOptions() and then calls getAbsolutePath() on the return value of getScratchDir().

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

Train Wrecks

Options opts = ctxt.getOptions();
File scratchDir = opts. getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

Whether this is a violation of Demeter depends on whether or not ctxt, Options, and ScratchDir are objects or data structures. If they are objects, then their internal structure should be hidden rather than exposed, and so knowledge of their innards is a clear violation of the Law of Demeter. On the other hand, if ctxt, Options, and ScratchDir are just data structures with no behavior, then they naturally expose their internal structure, and so Demeter does not apply.

Hybrids

This confusion sometimes leads to unfortunate hybrid structures that are half object and half data structure.
Such hybrids make it hard to add new functions but also make it hard to add new data structures.

Hiding Structure

What if ctxt, options, and scratchDir are objects with real behavior? Then, because objects are supposed to hide their internal structure, we should not be able to navigate through them. How then would we get the absolute path of the scratch directory?

ctxt.getAbsolutePathOfScratchDirectoryOption();

or

ctxt.getScratchDirectoryOption().getAbsolutePath();

The first option could lead to an explosion of methods in the ctxt object. The second presumes that getScratchDirectoryOptions() returns a data structure, not an object. Neither option feels good.

If ctxt is an object, we should be telling it to do something; we should not be asking it about its internals.

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

Data Transfer Objects

The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called data transfer object, or DTO. DTOs are very useful structure, especially when communicating with databases or parsing messages from sockets, and so on.

Somewhat more common is the “bean form shown in Listing 6–7. Beans have private variables manipulated by getters and setters. The quasi-encapsulation of beans seems to make some OO purists feel better but usually provides no other benefit.

Listing 6–7

public class Address {
private String street;
private String streetExtra;
private String city;
private String state;
private String zip;
public Address(String street, String streetExtra,
String city, String state, String zip) {
this.street = street;
this.streetExtra = streetExtra;
this.city = city;
this.state = state;
this.zip = zip;
}
public String getStreet() {
return street;
}
public String getStreetExtra() {
return streetExtra;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZip() {
return zip;
}

Active Record

Active Records are special forms of DTOs. They are dat structures with public (or bean-acccessed) variables; but they typically have navigational methods like save and find.

Unfortunately we often find that developers try to treat these data structures as though they were objects by putting business rule methods in them. This is awkward because it creates a hybrid between a data structure and an object.

The solution, of course is to treat the Active Record as a data structure and to create separate objects that contain the business rules and that hide their internal data (which are probably just instances of the Active Record).

--

--

Wenzhi Lin
Wenzhi Lin

Written by Wenzhi Lin

A climber who enjoys skiing and scuba diving, and writes iOS code during the day. Made in China, evolving in the USA.

No responses yet