使程式執行更高效——原型模式

HunterArley發表於2019-01-24

《Android原始碼設計模式解析與實戰》讀書筆記(四) 《Android原始碼設計模式解析與實戰》PDF資料下載

一、原型模式簡介

原型模式是一個建立型的模式。原型二字表明瞭該模式應該有一個樣板例項,使用者從這個樣板物件中複製出一個內部屬性一致的物件,這就是“克隆”。被複制的例項就是我們所稱的“原型”。

原型模式多用於建立複雜的或者構造耗時的例項,因為這種情況下,複製一個已經存在的例項可使程式執行更高效。

1.1、定義

用原型例項指定建立物件的種類,並通過拷貝這些原型建立新的物件。

1.2、使用場景

  1. 類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等,通過原型拷貝避免這些消耗。
  2. 通過new產生一個物件需要非常繁瑣的資料準備或訪問許可權,這時可以使用原型模式。
  3. 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用,即保護性拷貝。

注意: 通過實現Cloneable介面的原型模式在呼叫clone函式構造例項時並不一定比通過new操作速度快,只有當通過new構造物件較為耗時或者說成本較高時,通過clone方法才能獲得效率上的提升。

二、原型模式的簡單實現

/**
 * 文件型別,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
 */
public class WordDocument implements Cloneable {
    //文字
    private String mText;
    //圖片名列表
    private ArrayList<String> mImages = new ArrayList<>();

    public WordDocument() {
        System.out.println("----------------WordDocument建構函式-----------------");
    }

    @Override
    protected WordDocument clone() {
        try {
            WordDocument doc = (WordDocument) super.clone();
            doc.mText = this.mText;
            doc.mImages = this.mImages;
            return doc;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getText() {
        return mText;
    }

    public void setText(String mText) {
        this.mText = mText;
    }

    public ArrayList<String> getImages() {
        return mImages;
    }

    public void addImages(String img) {
        this.mImages.add(img);
    }

    /**
     * 列印文件內容
     */
    public void showDocument() {
        System.out.println("------------Word Content Start-------------");
        System.out.println("Text:" + mText);
        System.out.println("Images List:");
        for (String imgName : mImages) {
            System.out.println("image name:"+imgName);
        }
        System.out.println("------------Word Content End---------------");
    }
}
複製程式碼

Cloneable是一個標識介面,它表明這個類的物件是可拷貝的。如果沒有實現Cloneable介面卻呼叫了clone()函式將丟擲異常。

呼叫程式碼如下:

		WordDocument originDoc = new WordDocument();
        //2.編輯文件,新增圖片等
        originDoc.setText("這是一篇文件");
        originDoc.addImages("圖片1");
        originDoc.addImages("圖片2");
        originDoc.addImages("圖片3");
        originDoc.showDocument();

        //以原始文件為原型,拷貝一份副本
        WordDocument doc2 = originDoc.clone();
        doc2.showDocument();
        //修改文件副本,不影響原始文件
        doc2.setText("這是修改過的Doc2文字");
        doc2.showDocument();

        originDoc.showDocument();
複製程式碼

輸出結果:

原型模式.png

doc2是originDoc的一份拷貝,它們的內容是一樣的,而doc2修改了文字內容以後並不會影響originDoc的文字內容,這就保證了originDoc的安全性。還需要注意,通過clone拷貝物件時並不會執行建構函式

原型模式的核心問題就是對原始物件進行拷貝,在這個模式的使用過程中需要注意的一點就是:深、淺拷貝的問題

三、原型模式實戰

這是一個簡化版的客戶端,在使用者登陸之後,通過LoginSession儲存使用者的登入資訊,這些使用者資訊可能在APP的其他模組被用來做登入校驗、使用者個人資訊顯示等。但是,這些資訊在客戶端程式是不允許修改的,而需要在其他模組被呼叫,因此,需要開放已登入使用者資訊的訪問介面。

/**
 * 使用者實體類
 */
public class User {
    public int age;
    public String name;
    public String phoneNum;
    public Address address;

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                ", addrss=" + address +
                '}';
    }
}
複製程式碼
/**
 * 使用者地址類,儲存地址的詳細資訊
 */
public class Address {
    //城市
    public String city;
    //區
    public String district;
    //街道
    public String street;

    public Address(String city, String district, String street) {
        this.city = city;
        this.district = district;
        this.street = street;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", district='" + district + '\'' +
                ", street='" + street + '\'' +
                '}';
    }
}
複製程式碼
//登陸介面
public interface Login {
    void login();
}
複製程式碼
//登入實現
public class LoginImpl implements Login {

    @Override
    public void login() {
        // 登入到伺服器,獲取到使用者資訊
        User loginedUser = new User();
        //將伺服器返回的完整資訊設定給loginedUser物件
        loginedUser.age = 22;
        loginedUser.name = "Mr.Simple";
        loginedUser.address = new Address("北京市", "海淀區", "花園東路");
        //登入完之後將使用者資訊設定到Session中LoginSession.getLoginSession()裡
        LoginSession.getLoginSession().setLoginedUser(loginedUser);
    }
}
複製程式碼
//登入Session
public class LoginSession {
    static LoginSession sLoginSession = null;
    //已登入使用者
    private User loginedUser;

    public LoginSession() {
    }

    public static LoginSession getLoginSession() {
        if (sLoginSession == null) {
            sLoginSession = new LoginSession();
        }
        return sLoginSession;
    }

    //設定已登入的使用者資訊,不對外開放
    void setLoginedUser(User user) {
        loginedUser = user;
    }

    public User getLoginedUser() {
        return loginedUser;
    }
}
複製程式碼

LoginSession中的setLoginedUser函式是包級私有的,因此外部模組無法呼叫,這在一定程度上實現了外部客戶端程式不能修改已登入的使用者資訊。

但是,也會存在類似如下程式碼:

//獲取已登入的User物件
User newUser = LoginSession.getLoginSession().getLoginedUser();
newUser.address = new Address("北京市", "朝陽區", "大望路");
複製程式碼

類似的程式碼也會更新使用者的地址。因此,需要使用原型模式來進行保護性拷貝,也就是說在LoginSession的getLoginUser()函式中返回的是已登入使用者的一個拷貝,當更新使用者地址的網路請求完成時,在通過包級私有的LoginSession中的setLoginedUser更新使用者資訊。

於是在User類中覆寫了clone方法:

/**
 * 使用者實體類
 */
public class User implements Cloneable {
    public int age;
    public String name;
    public String phoneNum;
    public Address address;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                ", addrss=" + address +
                '}';
    }
}
複製程式碼

四、總結

原型模式本質上就是物件拷貝。使用原型模式可以解決構建複雜物件的資源消耗問題,能夠在某些場景下提升建立物件的效率。還有一個重要的用途即使保護性拷貝

優點:

  • 原型模式是在記憶體中二進位制流的拷貝,要比直接new一個物件效能好很多,特別是要在一個迴圈體內產生大量的物件時,原型模式可以更好地提現其優點。

缺點:

  • 直接在記憶體中拷貝,建構函式是不會執行的,在實際開發當中應該注意這個潛在的問題。

學海無涯苦作舟

我的微信公眾號

相關文章