2013年11月13日 星期三

保護回傳參考型態的物件

      今天在 "最新 Java 7 程式語言 施威銘" 這本書中看到了一篇內容,大概的內容是講述當我有一個類別 Circle, 該類別裡面包含了另一個類別 Point 物件紀錄 Circle 的中心點,為了保護 Point 物件,所以在 Circle 裡面使用 private 來修飾 Point 物件;但當外部需要取得中心點資料時,便在 Circle 類別寫了一個傳回 Point 的參考物件 method,此時因為外部可以取得 Point 的物件參考,所以外部就可透過 所得到的 Point 物件參考來設定修改資料,這樣就違背了當初我們不想讓外部的使用者來隨便設定 Circle 的中心點的用意,也失去了把 Point 設成 private 作用。
     所以為了避免這種情況發生,可以使用下列方式:
  1. new 一個新的 Point 物件,拷貝目前 Circle 物件內的 Point 物件資料。
  2. 將 Point 變成 immutable 的類別,也就是不提供設定(setXX ) method。
  3. Point 類別 implements Cloneable 類別,然後回傳 clone 物件。
    書本使用第一個方式,在回傳 Point 物件參考的 method 中,可以先用 new 的方式將目前在 Circle 類別內的 Point 物件拷貝一份,然後傳回去。這樣外部修改 Point 物件時就不會影響原本的 Circle 物件裡 Point 物件資料了。

範例程式:

class Point implements Cloneable {
    private int x;
    private int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    // 2. 把 setPoint 移除,讓 Point 變成 immutable 的類別
    public void setPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Circle {
    private Point point;
    private int radius;
    public Circle(int x, int y, int r) {
        point = new Point(x, y);
        radius = r;
    }
    public Point getPoint() {
        return point;
    }
    public Point getReadOnlyPoint() {
        try {
            // 3. 讓 Point 類別實作 Cloneable,然後回傳 clone 物件 
            return (Point)point.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    public Point getReadOnlyPoint2() {
        //1. 使用 new 的方式,將目前的Point 資料拷貝一份
        return new Point(point.getX(), point.getY());
    }
    
    @Override
    public String toString() {
        return String.format("(%d, %d) ,radius = %d", point.getX(), point.getY(), radius);
    }
}

public class ProtectReferenceObj {
    public static void main(String[] args) {
        Circle circle = new Circle(1,1,2);
        Point pt = circle.getPoint();
        System.out.println("Before: " + circle);
        pt.setPoint(10, 20);
        System.out.println("After: " + circle);
        System.out.println("================================");
        System.out.println("Before: " + circle);
        Point pt2 = circle.getReadOnlyPoint();
        pt2.setPoint(11, 22);
        System.out.println("After: " + circle);
        System.out.println("pt2.x =  " + pt2.getX() + " pt2.y = " + pt2.getY());
        System.out.println("================================");
        System.out.println("Before: " + circle);
        Point pt3 = circle.getReadOnlyPoint2();
        pt3.setPoint(55, 99);
        System.out.println("After: " + circle);
        System.out.println("pt3.x =  " + pt3.getX() + " pt3.y = " + pt3.getY());
    }
}


輸出結果:
Before: (1, 1) ,radius = 2
After: (10, 20) ,radius = 2
================================
Before: (10, 20) ,radius = 2
After: (10, 20) ,radius = 2
pt2.x =  11 pt2.y = 22
================================
Before: (10, 20) ,radius = 2
After: (10, 20) ,radius = 2
pt3.x =  55 pt3.y = 99


沒有留言: