Clean Code: Object and Data Structure 物件和資料結構

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

  • 總結

    物件可輕易添加新類型的物件,但添加新行為較困難

    資料結構添加新行為較易,但添加新資料結構較難