百年名校校長涉嫌學術造假?原型模式(Prototype Pattern)來解釋

目的

通過複製來降低創建複雜對象的時候耗時佔用的資源和降低耗時

例子代碼

鍋從天上來, 現任某開校長被指涉嫌抄襲, 論文換個圖就發了個新論文, 情況大概是這樣的:

我們簡化論文的結構如下:

<code>@Datapublic class Paper { private String title; private String introduction; private Content content; private String conclusion;}/<code>

其中正文類如下:

<code>@Datapublic class Content { private String text; private String picture; @SneakyThrows public Content(String text, String picture) { this.text = text; this.picture = picture; //很耗時的操作 Thread.sleep(1000); }}/<code>

我們用 Client 模擬遠程操作:

<code>public class Client { //模擬遠程查詢操作 public static String queryTitle() { return "氣功治癒癌症的研究"; }}/<code>

寫出二篇論文:

<code> Paper firstPaper = new Paper(); firstPaper.setTitle(Client.queryTitle()); firstPaper.setIntroduction("青蛙是用腿叫的"); firstPaper.setContent(new Content("青蛙本來叫的好好的, " + "切了腿就不叫了, 所以青蛙是用腿叫的", "a.jpg")); firstPaper.setConclusion("氣功對於治癒癌症確實有效果"); System.out.println("第一篇論文是 " + firstPaper); //將圖片轉個 90 度變成新的論文 Paper secondPaper = new Paper(); secondPaper.setTitle(Client.queryTitle()); secondPaper.setIntroduction("青蛙是用腿叫的"); secondPaper.setContent(new Content("青蛙本來叫的好好的, " + "切了腿就不叫了, 所以青蛙是用腿叫的", "b.jpg")); secondPaper.setConclusion("氣功對於治癒癌症確實有效果"); System.out.println("第二篇論文是 " + secondPaper);/<code>

問題分析

我們發現第一篇是沒辦法, 得寫, 第二篇不用一個個字的重新打, 思路也不用重新整理, 我們單純替換一下照片就是一個新的論文了, 省去了遠程調用的 IO 資源佔用和耗時

初步解決

我們為 paper 定義一個 clone 方法:

<code>@Datapublic class Paper implements Cloneable { private String title; private String introduction; private Content content; private String conclusion; @Override public Paper clone() { Paper clonePaper = new Paper(); clonePaper.setTitle(this.title); clonePaper.setIntroduction(this.introduction); clonePaper.setContent(this.content); clonePaper.setConclusion(this.conclusion); return clonePaper; }}/<code>

我們創建第二個代碼可以這麼寫:

<code>Paper firstPaper = new Paper();firstPaper.setTitle(Client.queryTitle());firstPaper.setIntroduction("青蛙是用腿叫的");firstPaper.setContent(new Content("青蛙本來叫的好好的, " + "切了腿就不叫了, 所以青蛙是用腿叫的", "a.jpg"));firstPaper.setConclusion("氣功對於治癒癌症確實有效果");System.out.println("第一篇論文是 " + firstPaper);Paper secondPaper = firstPaper.clone();secondPaper.getContent().setPicture("b.jpg");System.out.println("第一篇論文是 " + firstPaper);System.out.println("第二篇論文是 " + secondPaper);/<code>

看似沒什麼問題, 可是我們輸出的時候變成了這樣:

<code>第一篇論文是 Paper(title=氣功治癒癌症的研究, introduction=青蛙是用腿叫的, content=Content(text=青蛙本來叫的好好的, 切了腿就不叫了, 所以青蛙是用腿叫的, picture=a.jpg), conclusion=氣功對於治癒癌症確實有效果)第一篇論文是 Paper(title=氣功治癒癌症的研究, introduction=青蛙是用腿叫的, content=Content(text=青蛙本來叫的好好的, 切了腿就不叫了, 所以青蛙是用腿叫的, picture=b.jpg), conclusion=氣功對於治癒癌症確實有效果)第二篇論文是 Paper(title=氣功治癒癌症的研究, introduction=青蛙是用腿叫的, content=Content(text=青蛙本來叫的好好的, 切了腿就不叫了, 所以青蛙是用腿叫的, picture=b.jpg), conclusion=氣功對於治癒癌症確實有效果)/<code>

因為我們是淺複製, 我們複製後對象的屬性變了, 原來的對象的屬性也跟著變了, 這肯定不是我們希望的

原型模式

我們可以用序列化來實現深度複製:

<code>public class BeanUtil { public static T deepClone(T source, Class tClass) { return GsonUtil.convert(GsonUtil.toJson(source), tClass); }}/<code>

其中的 GsonUtil 如下:

<code>public class GsonUtil { private static final Gson GSON = new GsonBuilder() .enableComplexMapKeySerialization() .create(); private GsonUtil() { } public static T convert(String jsonStr, Class classOfT) { return GSON.fromJson(jsonStr, classOfT); } public static String toJson(Object target) { return GSON.toJson(target); }}/<code>

課後作業

使用深度複製完成 Paper 對象的複製一個報銷單的一行如果由一個部門報銷就直接插入庫中, 由三個部門分攤報銷就需要複製報銷單的其他信息, 把原有的id後面加上1,2,3 這樣, 請用原型模式實現:

<code>@Data@EqualsAndHashCode(callSuper = false)@Accessors(chain = true)@TableName("m_c_expense_ReportEntry")public class MCExpenseReportEntry implements Serializable { private static final long serialVersionUID = 1L; @TableField("ExpenseTypeName") private String expenseTypeName; @TableField("CurrencyCode") private String currencyCode; @TableField("ApprovedAmount") private BigDecimal approvedAmount; @TableField("LocationCountry") private String locationCountry; @TableField("LocationName") private String locationName; @TableField("BusinessPurpose") private String businessPurpose; @TableField("JoinNum") private String joinNum; @TableField("AbnormalProblems") private String abnormalProblems; @TableField("ProblemsDescription") private String problemsDescription; @TableField("RecheckOpinion") private String recheckOpinion; @TableField("SecondInstance") private String secondInstance; @TableField("BearToPayDepartmentCode") private String bearToPayDepartmentCode; @TableField("BearToPayDepartment") private String bearToPayDepartment; @TableField("ReportID") private String reportID; @TableField("TransactionDate") private LocalDateTime transactionDate; @TableField("Report_Entry_Id") private String reportEntryId; private String isTax; private String invoiceType; @TableField("Percentage") private String percentage; @TableField("LastModifiedDate") private String lastModifiedDate; @TableField("financial_approval_date") private LocalDateTime financialApprovalDate;}/<code>