Object and Data Structure 物件和資料結構
Chapter 6 : Object and Data Structure
-
資料的抽象化
public class Point { public double x; public double y; }
以上展現的是一種資料結構,這暴露了程式的實踐過程,這是一個直角座標系。
public class Point { double getX(); double getY(); void setCartestion(double x, double y); double getR(); double getTheta(); void setPolar(double r, double theta); }
以上將實現的過程隱藏,不只是加上一層函式的介面而已,確切來說這是一種抽象化的過程,讓使用者在不需要知道實現的過程狀態下,還能夠操作資料的本質。
\\具體化的交通工具類別 FuleTankCapacityInGallons() { double getGallonsOfGasoline(); } \\抽象化的交通類別 public interface Vehicle { double getPercentFuelRemaining(); }
以上的例子,抽象化的例子較佳,不用將資料的細節暴露在外,利用抽象化的詞彙來表達資料,並不只是透過介面及讀取、設定函式就能完成,更嚴謹一點的做法是想辦法找到最能詮釋「資料抽象概念」的方式。最糟糕的做法就是天真的加上讀取函式及設定函式的做法。
-
資料與物件的反對稱性
物件:物件將它們的資料在抽象層後方隱藏起來,然後將操作這些資料的函式暴露在外。
資料結構:將資料暴露在外且沒有提供有意義的函式。
Geometry類別操控了三種圖形類別,這三種類別是簡單的資料結構:
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.141592653589793 ; 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(); }
以上是程序式(結構化:Procedural)程式設計,當你要新增一個新的perimeter函式到Geometry類別時,這些圖形類別完全不會受到影響!任何其他相依於圖形類別的類別也不會受到影響,另一方面,如果我新增了一個新的圖形類別,則我必須改變在Geometry裡所有的函式來處理它。
以下是物件導向的解法: 如果我新增一個圖形類別,沒有任何一個已存在的函式需要修改,但如果我要新增一個新的函式則所有的圖形類別都必須要修改。
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; } } public class Circle implements Shape { private Point center; private double radius; public final double PI = 3.141592653589793; public double area() { return PI * radius * radius; } }
這揭露了物件和資料結構的二分性,結構化的程式碼(使用資料結構的程式碼)容易添加新的函式,而不需要變更既有的資料結構,而物件導向的程式碼,容易添加新的類別,而不用變動既有的函式。
成熟的設計師需知道一個概念,要讓每件事情都是一個物件是一個神話。某些時候,你真的只想使用簡單的資料結構,並透過結構化的程式碼來操作這些資料結構。
-
德摩特爾法則(The Law of Demeter)
模組不應該知道關於它所操作物件的內部運作,一個物件不應該透過存取者暴露其內部結構。
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
以上違反了法則,因為在回傳物件上又呼叫了其他方法,因為這個模組知道ctxt物件含有選項(options),而選項又包含了暫時目錄(ScratchDir),暫時目錄又包含了絕對路徑(AbsolutePath),這裡有著太多的資訊讓一個函式事先知道了。
如果ctxt、Options、ScratchDir是物件則它的內部應該被隱藏起來,而非暴露在外。
如果它們是資料結構,那在本質上必然會揭露內部的結構,所以法則在這情況並不適用。
當資料結構沒有函式,且僅使用公有變數時;或物件僅有私有變數及公用函式時,這個問題較不會讓人疑惑。
-
資料傳輸物件(Data Transfer Object, DTO)
最佳的資料結構形式,是一個類別裡只有公用變數,沒有任何函式。這種資料結構有時被稱為資料傳輸物件或DTO
-
總結
物件可輕易添加新類型的物件,但添加新行為較困難
資料結構添加新行為較易,但添加新資料結構較難