大家好,我是老田,今天我要跟大家分享設計模式中的原型模式
#。用貼切的生活故事,以及真實專案場景來講設計模式,最後用一句話來總結這個設計模式。
#還記得大四那年找工作,無意中我得從網路上找到一份相對漂亮的程式設計師履歷模板,然後全班同學開啟瘋狂的履歷拷貝(U盤)。同時也鬧出了一個笑話,有幾位同學,拷貝過去的履歷,內容完全沒改,名字都沒有改,截止投給面試官(校招面試官)。後來,結果大家也應該可以猜出來,大家都去實習了,部分人還在找工作。 後面公司面試官和同伴的其他同學回饋:收到一毛一樣的履歷,好幾份,回來大家一聊就知道問題出哪裡了,承認了自己拷貝過去完全沒改就拿出去投了,害,尷尬的一匹。
把履歷拷貝分成為兩種:
Specify the kinds of objects to create using a prototype instance ,and create new objects by coping this prototype
大致意思:用原型實例指定創建物件的種類,並且透過複製這些原型創建新的物件。
原型模式:Prototype Pattern
,屬於創建型模式。
呼叫者不需要知道任何建立細節,也不用呼叫建構方法來建立物件。
#原型模式有以下使用場景:
scope='prototype'
我們可以將一些getter和setter之類封裝成一個工廠方法,然後對於使用的人來說,呼叫方法就可以了,不需要知道裡面的getter和setter是怎麼處理的。我們也可以使用JDK
提供的實作Cloneable
接口,實作快速複製。
建立物件的四種方式:
new、反射、複製、序列化
大家是否有遇過這種常見,就是專案中規定,不能把與資料庫表映射的entity類別回傳給前端,所以通常回傳給前端的有各種O,例如:XxxVO、XxxBO、XxxDTO...
這時候就會出現下面的場景,大家也想已經猜到了。
下面是與資料庫表格對應的UserEntity
實體類別。
public class UserEntity { private Long id; private String name; private Integer age; //....可能还有很多属性 //省略getter setter }
傳回給前端或呼叫方的UserVO實體類別。
public class UserVO { private Long id; private String name; private Integer age; //....可能还有很多属性 //省略getter setter }
此時,從資料庫查出來的UserEntity需要轉換成UserVO,然後再回傳給前端(或呼叫方)。
public class ObjectConvertUtil { public static UserVo convertUserEntityToUserVO(UserEntity userEntity) { if (userEntity == null) { return null; } UserVo userVo = new UserVo(); userVo.setId(userEntity.getId()); userVo.setName(userEntity.getName()); userVo.setAge(userEntity.getAge()); //如果还有更多属性呢? return userVo; } }
从这个util类中,我们可以看出,如果一个类的属性有几十个,上百个的,这代码量是不是有点恐怖?
于是,我们通常都会使用一些工具类来处理,比如常见有以下:
BeanUtils.copy(); JSON.parseObject() Guava工具类 .....
这些工具类就用到了原型模式。
通过一个对象,创建一个新的对象。
也把原型模式称之为对象的拷贝、克隆。
其实对象的克隆分浅克隆和深克隆,下面我们就来聊聊浅克隆和深克隆。
我们先来聊聊浅克隆,都喜欢由浅入深。
比如,我现在相对用户信息User进行克隆,但是User中有用户地址信息UserAddress
属性。
以下是代码的实现:
//用户地址信息 public class UserAddress implements Serializable{ private String province; private String cityCode; public UserAddress(String province, String cityCode) { this.province = province; this.cityCode = cityCode; } } //用户信息 public class User implements Cloneable { private int age; private String name; //用户地址信息 private UserAddress userAddress; //getter setter 省略 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } //测试 public class UserTest { public static void main(String[] args) throws Exception { User user = new User(); user.setAge(20); user.setName("田维常"); UserAddress userAddress = new UserAddress("贵州", "梵净山"); user.setUserAddress(userAddress); User clone = (User) user.clone(); System.out.println("克隆前后UserAddress比较:" + (user.getUserAddress() == clone.getUserAddress())); } }
输出结果
克隆前后 UserAddress 比较:true
两个对象属性 UserAddress
指向的是同一个地址。
这就是所谓的浅克隆,只是克隆了对象,对于该对象的非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
关系如下:
关于深克隆,我们来用一个很经典的案例,西游记里的孙悟空。一个孙悟空能变成n多个孙悟空,手里都会拿着一个金箍棒。
按照前面的浅克隆,结果就是:孙悟空倒是变成很多孙悟空,但是金箍棒用的是同一根。
深克隆的结果是:孙悟空变成了很多个,金箍棒也变成很多个根。
下面我们用代码来实现:
//猴子,有身高体重和生日 public class Monkey { public int height; public int weight; public Date birthday; }
孙悟空也是猴子,兵器 孙悟空有个金箍棒:
import java.io.Serializable; //孙悟空的金箍棒 public class JinGuBang implements Serializable{ public float h=100; public float d=10; //金箍棒变大 public void big(){ this.h *=10; this.d *=10; } //金箍棒变小 public void small(){ this.h /=10; this.d /=10; } }
齐天大圣孙悟空:
import java.io.*; import java.util.Date; //孙悟空有七十二变,拔猴毛生成一个金箍棒 //使用JDK的克隆机制, //实现Cloneable并重写clone方法 public class QiTianDaSheng extends Monkey implements Cloneable, Serializable { public JinGuBang jinGuBang; public QiTianDaSheng() { this.birthday = new Date(); this.jinGuBang = new JinGuBang(); } @Override protected Object clone() throws CloneNotSupportedException { return this.deepClone(); } //深克隆 public QiTianDaSheng deepClone() { try { //内存中操作完成、对象读写,是通过字节码直接操作 //与序列化操作类似 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream bis = new ObjectInputStream(bais); //完成一个新的对象,底层是使用new创建的一个对象 //详情可以了解readObject方法 QiTianDaSheng qiTianDaSheng = (QiTianDaSheng) bis.readObject(); //每个猴子的生日不一样,所以每次拷贝的时候,把生日改一下 qiTianDaSheng.birthday = new Date(); return qiTianDaSheng; } catch (Exception ex) { ex.printStackTrace(); return null; } } //浅克隆,就是简单的赋值 public QiTianDaSheng shalllowClone(QiTianDaSheng target) { QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); qiTianDaSheng.height = target.height; qiTianDaSheng.weight = target.weight; qiTianDaSheng.jinGuBang = target.jinGuBang; qiTianDaSheng.birthday = new Date(); return qiTianDaSheng; } }
接着我们就来测试一下:
public class DeepCloneTest { public static void main(String[] args) { QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); try { QiTianDaSheng newObject = (QiTianDaSheng) qiTianDaSheng.clone(); System.out.print("深克隆后 "); System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang)); } catch (Exception ex) { ex.printStackTrace(); } QiTianDaSheng newObject=qiTianDaSheng.shalllowClone(qiTianDaSheng); System.out.print("浅克隆后 "); System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang)); } }
输出结果为:
深克隆后 金箍棒是否一直:false 浅克隆后 金箍棒是否一直:true
结论
深克隆后每个孙悟空都有自己的金箍棒,而浅克隆后每个孙悟空用的金箍棒实质上还是同一根。
切记:深和浅,指的是克隆对象里的属性(引用类型)是否指向同一个内存地址。
为了更深刻的理解深克隆和浅克隆,我们回答文中的简历拷贝的故事。
優點:
缺點:
我們從原型模式的定義,使用場景,真實案例、淺克隆、深克隆、優缺點等方面,對原型模式進行了一個全面的講解。
以上是五分鐘 掌握 原型模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!