5. Objects and Data Structures - tuansondinh/clean_code GitHub Wiki

"There is a reason that we keep our variables private. We don’t want anyone else to depend on them. We want to keep the freedom to change their type or implementation on a whim or an impulse."

Getters and Setters are also exposing those private variables, so don't add them.

5.1. Data Abstraction

A class "...exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation." S.94

BAD:

public interface Vehicle {
  double getFuelTankCapacityInGallons(); 
  double getGallonsOfGasoline();
}

These seem just to be accessors of variables.

GOOD:

public interface Vehicle {
  double getPercentFuelRemaining();
}}

"In the abstract case you have no clue at all about the form of the data." S.94

5.2 Data/Object Anti-Symmetry

Objects hide their data behind abstractions and expose functions that operate on that data.

Data structures expose their data an have no meaningful functions.


"Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures." S.97

EXAMPLE:

// Procedural Shape
public class Square { 
  public Point topLeft; 
  public double side;
}

public class Circle { 
  public Point center;
  public double radius;
} 

public class Geometry {
  public double area(Object shape) throws NoSuchShapeException {
    if (shape instanceof Square) {
      Square s = (Square)shape; 
      return s.side * s.side;
    } else if (shape instanceof Rectangle) {
      Rectangle r = (Rectangle)shape; 
      return r.height * r.width;
    }
    throw new NoSuchShapeException(); 
  }
}

"The Geometry class operates on the [...] shape classes. The shape classes are simple data structures without any behavior. All the behavior is in the Geometry class." S.95

  • Adding new functions won't affect the Shape classes.
  • BUT: It's necessary to change all functions in Geometry, if a new Shape is added.

"OO (Object oriented) code, on the other hand, makes it easy to add new classes without changing existing functions."** S.97

// Polymorphic Shapes
public class Square implements Shape {
  private Point topLeft;
  private double side;

  public double area() { 
    return side*side;
  } 
}

public class Rectangle implements Shape { 
  private Point topLeft;
  private double height;
  private double width;

  public double area() { 
    return height * width;
  }
}

  • area() function is polymorphic
  • we don't need to change the functions, if a new Shape is added
  • BUT: we need to change all of the Shapes, if we add a new function

Choose carefully depending on the specific situation.

  • new Datatypes will be added -> OO

  • new functions will be added -> data structures

5.3 Law of Demeter

This law says, that "[...] a module should not know about the innards of the objects it manipulates." S.97 "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 to f
  • An object held in an instance variable of C" S.97

"The method should not invoke methods on objects that are returned by any of the allowed functions." S.98

BAD:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
// methods call on return objects

If return value is a data structure, then it's okay. In that case, do this to avoid confusing:

GOOD:

final String outputDir = ctxt.options.scratchDir.absolutePath;

5.4 Train Wrecks

Don't chain function calls like in the bad example above. It's hard to read.

GOOD:

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

5.5 Hybrids

Don't use "hybrid structures that are half object and half data structure" S.99 "Such hybrids make it hard to add new functions but also make it hard to add new data structures. They are the worst of both worlds." S.99

5.6 Hiding Structure

"If ctxt(example above) is an object, we should be telling it to do something; we should not be asking it about its internals." S.99 In the example in 5.4, the goal was to get the absolute path. But in the end, the intent is to create a scratch file of a given name. So refactor like this:

GOOD:

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

5.7 Data Transfer Objects

"The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called a data transfer object, or DTO."

"DTOs are very useful structures, especially when communicating with databases or parsing messages from sockets, and so on." S.100

5.8 Active Record

"Active Records are special forms of DTOs. They are data structures with public variables; but they typically have navigational methods like save and find." S.101

5.9 Conclusion

"Objects expose behavior and hide data." -> easy to add new data types "Data structures expose data and have no significant behavior." -> easy to add new behavior -> choose depending on the task