We do not want to expose the details of our data. Rather we want to express our data in abstract terms.
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.
Data structures expose data and have no significant behavior. This makes it easy to add new functions to existing data structures but makes it hard to add new data structures to existing functions because all the functions must change.
Objects expose behavior and hide data. This makes it easy to add new kinds of objects without changing existing behaviors. It also makes it hard to add new behaviors to existing objects because all the classes must change.
In any given system, we will sometimes want the flexibility to add new data types, and so we prefer objects for that part of the system. Other times, we will want the flexibility to add new behaviors, and so in that part of the system, we prefer data types and procedures.
Example of Data structure:
publicclassSquare {privatedouble side;}publicclassRectangle {privatedouble height;privatedouble width;}publicclassApp {publicvoiddraw(Object shape) throwsNoSuchShapeException {if (shape instanceof Square) {log.info("Draw Square with side="+s.getSide()); } elseif (shape instanceof Rectangle) {Rectangle r = (Rectangle) shape;log.info("Draw Rectangle with with="+r.getWidth() +" and height="+r.getHeight()); }thrownewNoSuchShapeException(); }// Easy to add new functionspublicvoiddrawV2(Object shape) {if (shape instanceof Square) {log.info("DrawV2 Square with side="+s.getSide()); } elseif (shape instanceof Rectangle) {Rectangle r = (Rectangle) shape;log.info("DrawV2 Rectangle with with="+r.getWidth() +" and height="+r.getHeight()); }thrownewNoSuchShapeException(); }publicvoiddrawV3(Object shape) {log.info("DrawV3 shape="+shape.getClass().getSimpleName()); }publicdoublearea(Object shape) {// ... }}
It's hard to add a new data structure like Circle, because must change all functions like draw() and drawV2() to have the Circle data structure.
Example of Object:
publicinterfaceShape {voiddraw();}publicclassSquareimplementsShape {privatedouble side; @Overridepublicvoiddraw() {log.info("Draw Square with side="+ side); }}publicclassRectangleimplementsShape private double height; private double width; @Override public void draw() { log.info("Draw Rectangle with with=" + width + " and height=" + height); }}// Easy to add new data types publicclassCircleimplementsShape { privatedouble radius; @Overridepublicvoiddraw() {log.info("Draw Circle with radius=" radius); }}
It's hard to add new behaviors to existing objects Shape like adding functionsdrawV2(), because all the classes must change like Square, Rectangle, and Circle.
Avoid Hybrids
Hybrid structures are half object and half data structures.
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.
publicabstractclassShape {publicvoiddraw() throwsNoSuchShapeException {if (thisinstanceof Square) {log.info("Draw Square with side="+s.getSide()); } elseif (thisinstanceof Rectangle) {Rectangle r = (Rectangle) shape;log.info("Draw Rectangle with with="+r.getWidth() +" and height="+r.getHeight()); }thrownewNoSuchShapeException(); }publicvoiddrawV2() {if (thisinstanceof Square) {log.info("DrawV2 Square with side="+s.getSide()); } elseif (thisinstanceof Rectangle) {Rectangle r = (Rectangle) shape;log.info("DrawV2 Rectangle with with="+r.getWidth() +" and height="+r.getHeight()); }thrownewNoSuchShapeException(); }// Hard to add new functions// public abstract double area();}publicclassSquareextendsShape {privatedouble side;}publicclassRectangleextendsShape {privatedouble height;privatedouble width;}// Hard to add new data types// public class Circle extends Shape { }
It's hard to add a new data structure like Circle, because must change all functions like draw() and drawV2() to have the Circle data structure.
It's hard to add new behaviors to existing objects Shape like adding functions area(), because all the classes must change like Square, Rectangle.
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.
DTO is an object that carries data between processes. DTO is a simple object that should not contain any business logic but may contain serialization and deserialization mechanisms for transferring data over the wire.