👈?上一篇:建造者模式 ? ?|?? 下一篇:創建型設計模式對比👉?
目錄
- 原型模式(Prototype Pattern)
- 概覽
- 定義
- 英文原話
- 直譯
- 3個角色
- 類圖
- 1. 抽象原型(Prototype)角色
- 2. 具體原型(Concrete Prototype)角色
- 3. 客戶(Client)角色
- 代碼示例
- 1. 抽象原型
- 2. 具體原型
- 3. 被復制的對象的類
- 4. 客戶端
- 5. 測試類
- 應用
- 優點
- 使用場景
- 原型模式示例解析:郵件群發
- 類圖
- 1. 抽象原型角色:Prototype.java(接口定義克隆方法)
- 2. 具體原型類:Mail.java
- 3. 測試類
- 補充知識點
- Lombok的@Builder注解原理:建造者模式
- 1. 先說下用法
- 2. 原理分析
原型模式(Prototype Pattern)
>>本文源碼<<
概覽
- 定義
- 3個角色
- 代碼示例
- 應用
- 優點
- 使用場景
- 案例分析
- 原型模式示例解析:郵件群發
定義
英文原話
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
直譯
指定要使用原型實例創建的對象類型,并通過復制該原型創建新對象。
3個角色
類圖
1. 抽象原型(Prototype)角色
為具體原型角色定義方法,指定統一標準
2. 具體原型(Concrete Prototype)角色
該角色是被復制的對象,必須實現抽象原型接口。
Java中內置了克隆機制,Object類具有一個clone()方法,能夠實現對象的克隆,使一個類支持克隆需要以下兩步。
- 實現Cloneable接口;
- 覆蓋Object的clone()方法,完成對象的克隆操作,通常只需要調用Object的clone()方法(“淺克隆”-即只復制關聯對象的引用)即可
3. 客戶(Client)角色
該角色提出創建對象的請求。
代碼示例
>>示例源碼<<
1. 抽象原型
抽象原型Prototype接口繼承 Cloneable 接口,以標明該接口的實現類可以被復制,并聲明一個 clone()方法,該clone()方法是對Object類的clone()方法的重寫
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;/** 實現本接口,表明實現類可以被復制*/
public interface Prototype extends Cloneable {// 克隆方法Prototype clone();
}
2. 具體原型
具體原型ConcretePrototype實現clone()
方法。這里調用的父類Object的clone()
方法
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;/** 具體原型ConcretePrototype實現clone()方法*/
public class ConcretePrototype implements Prototype {@Overridepublic Prototype clone() {try {// 此處調用的Object類的clone()方法,并向下轉型為抽象原型類型。// Java中Object提供的clone()方法采用的是“淺”克隆,即只復制關聯對象的引用;// 而不復制關聯對象的數據。如果需要“深”克隆,則需要在覆蓋clone()方法時手動控制克隆的深度。return (Prototype) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}
}
3. 被復制的對象的類
提供一個被復制的類,用于測試使用
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;/** 被復制的對象的類*/
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class User extends ConcretePrototype {private String name;private Integer age;
}
這里的@Builder是lombok的注解,加上該注解編譯出的class文件中,通過內部類的方式實現了建造者模式。便于用戶方便地創建對象。
(關于建造者模式看上一篇 建造者模式)
@Builder的原理,參考補充知識點:Lombok的@Builder注解原理:建造者模式
4. 客戶端
使用原型類的客戶類
public class Client {// 傳參傳入具體原型類示例,具體原型類實現了抽象原型的clone方法public Prototype operation(Prototype example) {// 得到example的副本Prototype p = example.clone();return p;}
}
5. 測試類
新建了一個用于被復制的
user
對象,調用客戶類進行復制,傳入原型類型的對象(User類實現了原型類,通過繼承具體原型類實現的),然后返回一個原型實例,
之后測試了復制對象與原始對象屬性是同一對象,
然后對復制對象的屬性值通過反射進行了獲取與打印
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;import java.lang.reflect.Field;/*** @author Polaris 2024/5/17*/
public class DemoTest {public static void main(String[] args) {User user = User.builder().name("歷史").age(5000).build();Client client = new Client();Prototype clone = client.operation(user);System.out.println("name屬性是同一個:"+((User)clone).getName().equals(user.getName()));System.out.println("age屬性(>127,非讀緩存值)是同一個:"+((User)clone).getAge().equals(user.getAge()));System.out.println("-----------------");Field[] declaredFields = clone.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {Object o;try {String name = declaredField.getName();declaredField.setAccessible(true);o = declaredField.get(clone);System.out.println(name + ":" + o);} catch (IllegalAccessException e) {e.printStackTrace();}}}
}/* Output:name屬性是同一個:true
age屬性(>127,非讀緩存值)是同一個:true
-----------------
name:歷史
age:5000*///~
輸出中:克隆前后的對象的屬性指向的是同一個對象。
也印證了:
Java中Object
提供的clone()
方法采用的是“淺”克隆,即只復制關聯對象的引用
應用
優點
原型模式的優點有以下幾個方面。
- 性能優良:原型模式是在內存二進制流的復制,要比直接new一個對象性能好,特別是在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。
- 逃避構造函數的約束:這既 是優點也是缺點,直接在內存中復制,構造函數是不會執行的,因此減少了約束,需要在實際應用時進行權衡考慮。
使用場景
原型模式的使用場景如下。
- 資源優化場景,類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。
- 性能和安全要求的場景,通過new產生一個對象需要非常煩瑣的數據準備或訪問權限,可以使用原型模式。
- 一個對象多個修改者的場景,一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式復制多個對象供調用者使用。
- [注]:在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone()方法創建一個對象,然后由工廠方法提供給調用者。
- 注意Java中Object提供的clone()方法采用的是“淺”克隆,即只復制關聯對象的引用,而不復制關聯對象的數據。如果需要“深”克隆,則需要在覆蓋clone()方法時手動控制克隆的深度。
原型模式示例解析:郵件群發
>>示例源碼<<<
本案例是一個對象多個修改者情況:
- 每次發送郵件都是對原始郵件對象克隆然后進行屬性修改
- 原始郵件對象實現了抽象原型接口
- 抽象原型接口繼承Cloneable接口,聲明提供抽象原型類型的clone方法聲明
- 原始郵件類型實現抽象原型類型的
clone()
方法,用于克隆對象 - 使用Object的克隆方法來克隆郵件類型對象,淺克隆,復制郵件類對象的引用
類圖
1. 抽象原型角色:Prototype.java(接口定義克隆方法)
package com.polaris.designpattern.list1.creational.pattern5.prototype;public interface Prototype extends Cloneable {//克隆方法Prototype clone();
}
2. 具體原型類:Mail.java
創建一個郵件類Mail:
Mail類實現Cloneable接口,并實現了
clone()
方法,該方法是實現原型模式的關鍵,只有實現
clone()
方法,在應用中才能對Mail進行復制克隆
package com.polaris.designpattern.list1.creational.pattern5.prototype;import lombok.Getter;
import lombok.Setter;public class Mail implements Prototype {//收件人@Getter@Setterprivate String recerver;//郵件標題@Getter@Setterprivate String subject;//稱謂@Getter@Setterprivate String appellation;//郵件內容@Getter@Setterprivate String contxt;//郵件尾部,一般是加上“xxx版權所有”等信息@Getter@Setterprivate String tail;//構造函數public Mail(String subject, String contxt) {this.subject = subject;this.contxt = contxt;}//克隆方法@Overridepublic Prototype clone() {Mail mail = null;try {mail = (Mail) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return mail;}
}
3. 測試類
通過郵件的克隆群發演示原型模式
package com.polaris.designpattern.list1.creational.pattern5.prototype;import java.util.Random;public class DemoTest {//發送賬單的數量,這個值從數據庫中獲得的private static int MAX_COUNT = 6;public static void main(String[] args) {//模擬發送郵件int i = 0;//定義一個郵件對象Prototype prototype = new Mail("某商場五一抽獎活動","五一抽獎活動通知:" +"凡在五一期間在本商場購物滿100元的客戶都有貨的抽獎機會!...");((Mail)prototype).setTail("解釋權歸某商場所有");while (i < MAX_COUNT) {//克隆郵件Mail cloneMail = (Mail) prototype.clone();//以下是每封郵件不同的地方cloneMail.setAppellation(getRandomString(5) + " 先生(女士)");cloneMail.setRecerver(getRandomString(5) + "@" + getRandomString(8) + ".com");//發送郵件sendMail(cloneMail);i++;}}//發送郵件public static void sendMail(Mail mail) {System.out.println("標題:" + mail.getSubject() +"\t收件人:" + mail.getRecerver() + "\t...發送成功!");}//獲取指定長度的隨機字符串public static String getRandomString(int maxLength) {String souce = "abcdefghijklmnopqrstuvwxyz" +"ABCDEFGHIJKLMNOPQRSTUVWXYZ";StringBuilder sb = new StringBuilder();Random random = new Random();for (int i = 0; i < maxLength; i++) {sb.append(souce.charAt(random.nextInt(souce.length())));}return sb.toString();}
}/* Output:
標題:某商場五一抽獎活動 收件人:TEDiM@LXFeyNdU.com ...發送成功!
標題:某商場五一抽獎活動 收件人:qyOWv@wieXPRga.com ...發送成功!
標題:某商場五一抽獎活動 收件人:wMloC@IgFOzjBh.com ...發送成功!
標題:某商場五一抽獎活動 收件人:hrDGv@HAjpARpN.com ...發送成功!
標題:某商場五一抽獎活動 收件人:lyWwt@cLdWntpC.com ...發送成功!
標題:某商場五一抽獎活動 收件人:ZVeap@HZlYxaCe.com ...發送成功!
*///~
補充知識點
Lombok的@Builder注解原理:建造者模式
1. 先說下用法
通過下面的鏈式調用方式,可以非常方便的得到一個需要的user對像
User user = User.builder().name("歷史").age(5000).build();
2. 原理分析
以下是標記了@Builder注解后,編譯出的類文件中看到增加了以下代碼:
public class User extends ConcretePrototype {private String name;private Integer age;User(String name, Integer age) {this.name = name;this.age = age;}public static User.UserBuilder builder() {return new User.UserBuilder();}public static class UserBuilder {private String name;private Integer age;UserBuilder() {}public User.UserBuilder name(String name) {this.name = name;return this;}public User.UserBuilder age(Integer age) {this.age = age;return this;}public User build() {return new User(this.name, this.age);}public String toString() {return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ")";}}
}
通過標記了@Builder注解的類調用類方法
builder()
方法,這里創建了一個內部類UserBuilder
對象接下來就是構造者模式的實現:
內部類
UserBuilder
就是建造者角色,User類的內部類
UserBuilder
的屬性和User
類的屬性完全一樣,然后提供了對這些屬性配置的方法,且每個方法都會將該實例返回,這樣就可以進行鏈式調用。最后提供了一個
build()
方法,將內部類的屬性賦值給User類的構造函數,來構造User類實例,返回給客戶端。這就是建造者模式的具體應用。
👈?上一篇:建造者模式 ? ?|?? 下一篇:創建型設計模式對比👉?