Chapter 6 - Objects and Data Structures
Summary
- It's key to distinguish the two concepts and use the one more suited to the task at hand.
Objects
- Expose behavior and hide data.
- Easy to add new kinds of objects without changing existing behavior.
- Hard to add new behaviors to existing objects.
- If the system requires the flexibility to add new data types, we prefer objects.
Data Structures
- Expose data and have no specific behaviors.
- Easy to add new behaviors to existing data structures.
- Hard to add new data structures to existing functions.
Data / Object Anti-Symmetry
- Objects hide their data behind abstractions and expose functions that operate on that data.
- Data structures expose their data and have no meaningful functions.
- Below are 2 examples showing this juxtaposition and to demonstrate that one is not better than the other.
Procedural Example
- If you add
perimeter()
function to Geometry
, the shape classes, along with any other dependant classes will be unaffected.
- However if you add a new shape, you must change all the functions in
Geometry
to deal with it.
public class Square {
public Point topLeft;
public double side;
}
public class Rectangle {
public Point topLeft;
public double height;
public double width;
}
public class Circle {
public Point center;
public double radius;
}
public class Geometry {
public final double PI = 3.14;
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;
} else if (shape instanceof Circle) {
Circle c = (Circle)shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}
OO Example
- Now the
area()
method is polymorphic.
- No Geometry class is needed.
- If I add a new shape, no existing functions are affected.
- If I add a function, all the existing shapes must change.
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 width*height;
}
}
public class Circle implements Shape {
private Point center;
private double radius;
private final double PI = 3.14;
public double area() {
return PI*radius*radius;
}
}
Law of Demeter
- A module should not know about the innards of the objects it manipulates.
- LoD 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
Train Wrecks
- Chains of calls are often considered sloppy style and should be avoided.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Options opts = ctxt.getOptions;
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
- In terms of LoD; if ctxt, Options and ScratchDir are objects it violates the law due to their internal structure not being hidden.
- If they are data structures, they naturally expose their innards and LoD does not apply.
Hybrids
- Avoid half object half data structure implementations, you get the worst of both worlds.
- Tells the reader that the author has a muddled design.
Hiding Structure
- Going back to the previous example if ctxt, options, and scratchDir are objects with real behavior.
- We can hide its innards by telling to do something rather than asking for its internals.
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
Data Transfer Objects (DTO)
- Is essentially a
class
with no functions and only public variables.
- Very useful for communicating with databases and parsing messages from sockets.
- They often become the first in a series of translation stages that convert raw data in a database into objects in the application code.
Return