### 前言
面向對象中的另外一個設計模式是原型(prototype)模式,這種模式使用非常簡單,使用的場合不是很多,所以不是很常用。
定義:將一個對象實例設為原型,通過clone該原型實例得到新的一個實例,而不是通過new得到原型對象實例,這點java和C++的對象拷貝是一樣的。
適用場景:
1、提升對象創建的效率。如果一個對象的創建比較復雜,需要消耗很長的時間才能完成該對象的創建,那么這個時候使用原型模式比較好,即通過一個已有該對象的實例克隆出一個新的實例而不是通過new。
2、保護原型實例的安全。對于一個已經有的原型對象的實例,如果要操作該原型實例(讀取數據),又要保證安全性,那么我們可以將該實例復制出一個新的實例給要讀取該實例的客戶端使用,而不是直接將該實例返回。這樣客戶端就無法更改原來那個對象的實例的數據了。
### 原型模式的實現
以java語言為例,原型模式的實現非常簡單。下面模擬一個用戶登錄的過程來實現原型模式。
1、原型對象,User.java
~~~
public class User implements Cloneable{
private String username;
private String password;
private Role role;
public User(String username, String password) {
this.username = username;
this.password = password;
}
//.. -> setter
@Override
protected User clone(){
User user = null;
try {
user = (User) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
@Override
public String toString() {
return "User [username=" + username + ", password=" + password
+ ", role=" + role + "]";
}
}
~~~
需要注意的是clone方法是重寫Object對象的,這里重寫clone方法沒有處理引用類型的復制,為淺復制。
> java對象的復制(需要實現Cloneable接口)
> 1. 淺復制:沒有對引用類型進行處理,這樣會導致復制出來的對象,如果存在引用類型,那么復制出來的對象和原來的對象里面的引用類型對象指向的是同一個內存空間。
> 1. 深復制:對引用類型也進行復制,這樣可以使得克隆對象和原對象完全脫離關系,即怎么操作克隆對象都不會影響原對象。
2、登錄的Session,LoginSession.java
~~~
public class LoginSession {
private static User currentUser;
public static void login(User user) {
if (currentUser == null) {
currentUser = user;
System.out.println("登錄用戶:"+currentUser);
}
}
public static User getCurrentUser() {
return currentUser.clone();
}
}
~~~
注意:
1. login()方法模擬用戶實際登錄的過程,用戶實際登錄則會在Session中添加一個登錄的用戶(原型實例)。
1. getCurrentUser()不是返回原型實例,而是返回原型實例的克隆實例。這樣保證了當前Session中登錄的用戶信息只能由登錄的時候決定,登錄后不能修改用戶名或者密碼了。而這個克隆實例則給程序的其他地方使用(即使改變了里面的數據,原型實例中的數據也不變)。
3、模擬用戶從瀏覽器登錄網站和其它地方操作登錄的用戶信息,Test.java
~~~
public class Test {
public static void main(String[] args) {
User currentUser = new User("lt","123");
currentUser.setRole(new Role("程序員"));
// 模擬登陸
LoginSession.Login(currentUser);
// 模擬其他地方操作當前登錄用戶的情況
User clone = LoginSession.getCurrentUser();
clone.setUsername("ydx");
Role role = clone.getRole();
role.setName("老板");
System.out.println("cloneUser:"+clone);
System.out.println("登錄用戶:"+currentUser);
}
}
~~~
這是一個模擬用戶登錄和其它地方操作登錄的用戶的例子,下面我們運行看輸出:

其它地方得到當前登錄的用戶實例(實際上是一個克隆實例),并修改了克隆實例的用戶名和角色信息,發現原型實例用戶名沒變而角色卻變了。這里我們明明改的是克隆實例的信息,為什么原型實例角色信息卻變了!這里涉及到了淺復制和深復制。我們對User的克隆方法改成下面這樣并修重寫Role的clone(注意需要實現CloneAble方法,否則克隆的時候會報CloneNotSupportedException異常)。
修改后的User的clone()
~~~
@Override
protected User clone(){
User user = null;
try {
user = (User) super.clone();
user.setRole((Role) user.getRole().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
~~~
重寫了Role的clone方法,需要實現Cloneable接口(告訴外界這個對象可以被復制),Role無引用類型,淺復制和深復制一樣,所以直接給個空的實現即可。
~~~
public class Role implements Cloneable{
private String name;
public Role(String name) {
super();
this.name = name;
}
//.. -> setter
@Override
public String toString() {
return "Role [name=" + name + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
~~~
再次運行Test.java:

可以看到我們修改了克隆實例的信息后,原型實例什么信息也沒有改變,包括引用類型的對象信息也沒變,這就是深復制和淺復制的區別。到此這個模擬過程結束,這個模擬過程保證了其它地方可以讀取到原型實例的信息但無法影響原型實例信息,滿足了我們安全性要求和邏輯需要。
總結:原型模型的實現很簡單,就是對象的拷貝(克隆或復制),但我們要注意對象的克隆有深和淺之分。