原型模式简单来说就是基于原型来进行创建新的对象,这种创建的方法可以有复制 拷贝等,创建出来的方式就叫做原型设计模式,简称原型模式
那么什么时候会出现对象的创建成本比较大的时候,也就是很适合使用原型模式的时候呢?
实际上,一般来说与数据库交互,或者和一些外部系统进行交互以及进行很多精密的计算之后,得到的数据算是比较复杂的数据,除此外 在内存或者CPU当中直接进行计算获得的数据本身是不会花费太多时间的,对于上面说的比较难以获得的数据使用原型模式是比较值得的
我们举一个例子来说明这样的情况
假如有一个数据库存储了10万条的搜索关键词的数据,我们需要在项目启动的时候,将这份数据加载到内存当中,并且方便查找,我们还需要建立一个key-value的形式,在Java中我们可以选择使用map这种形式,当然我们还有另一个系统b,用于定期的往数据库更新日志,比如如下的更新.
为了保证数据的实时性,我们需要定时的从数据库中拿取数据,并更新到我们维持这个map当中,这样 我们只需要在系统a当中,记录一个更新时间,然后从数据库中查找出大于这个更新时间的数据,然后依次更新到我们的map里面 那这样我们的原型模式就可以加上使用了.
我们创建两个版本,并利用copyonwrite的设计思路,在新的版本上面我们进行更新,然后在更新完成之后用新的版本替换我们旧的版本,这样既保证了数据的可用性又避免了有过渡状态的存在
public class Demo {
private HashMap<String, SearchWord> currentKeywords=new HashMap<>(); public void refresh() { HashMap<String, SearchWord> newKeywords = new LinkedHashMap<>(); // 从数据库中取出所有的数据,放入到newKeywords中 List<SearchWord> toBeUpdatedSearchWords = getSearchWords(); for (SearchWord searchWord : toBeUpdatedSearchWords) { newKeywords.put(searchWord.getKeyword(), searchWord); } currentKeywords = newKeywords; } private List<SearchWord> getSearchWords() { // TODO: 从数据库中取出所有的数据 return null; } } |
上面的实现非常糙,因为我们每次都要去创建一个新的map,而且要进行10万次的插入,所以说不太好.为此我们使用了Java的克隆方法来创建一个新的map,并且在这个新的map基础之上进行更新,更新完成之后替换过去
public class Demo {
private HashMap<String, SearchWord> currentKeywords=new HashMap<>(); private long lastUpdateTime = -1; public void refresh() { // 原型模式就这么简单,拷贝已有对象的数据,更新少量差值 HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone(); // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中 List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime); long maxNewUpdatedTime = lastUpdateTime; for (SearchWord searchWord : toBeUpdatedSearchWords) { if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) { maxNewUpdatedTime = searchWord.getLastUpdateTime(); } if (newKeywords.containsKey(searchWord.getKeyword())) { SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword()); oldSearchWord.setCount(searchWord.getCount()); oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime()); } else { newKeywords.put(searchWord.getKeyword(), searchWord); } } lastUpdateTime = maxNewUpdatedTime; currentKeywords = newKeywords; } private List<SearchWord> getSearchWords(long lastUpdateTime) { // TODO: 从数据库中取出更新时间>lastUpdateTime的数据 return null; } } |
那样看起来就已经很完美了,因为直接拷贝一个数据比从集合里,一个个取出来计算哈希值要快得多,但是在Java当中是行不通的,因为Java当中有深拷贝和前拷贝两种概念
何为深拷贝和浅拷贝,相比我就不用说了,Java中的浅拷贝,就是只拷贝数据的地址,并不会真正的去复制对象中的数据,所以我们必须要将原有代码上的浅拷贝替换为深拷贝
常见的深拷贝的方式有两种,
一是递归的拷贝这个对象直到这个对象里面没有引用数据类型,只有基本数据类型,这样就能做到深拷贝
二是将这个对象序列化以及反序列化
这两种的代码实现我们分别列在下面,而且不管采用哪一种,虽然增加了时间和内存消耗,但是能够有效的避免出现过度版本的问题
public class Demo {
private HashMap<String, SearchWord> currentKeywords=new HashMap<>(); private long lastUpdateTime = -1; public void refresh() { // Deep copy HashMap<String, SearchWord> newKeywords = new HashMap<>(); for (HashMap.Entry<String, SearchWord> e : currentKeywords.entrySet()) { SearchWord searchWord = e.getValue(); SearchWord newSearchWord = new SearchWord( searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastUpdateTime()); newKeywords.put(e.getKey(), newSearchWord); } // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中 List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime); long maxNewUpdatedTime = lastUpdateTime; for (SearchWord searchWord : toBeUpdatedSearchWords) { if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) { maxNewUpdatedTime = searchWord.getLastUpdateTime(); } if (newKeywords.containsKey(searchWord.getKeyword())) { SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword()); oldSearchWord.setCount(searchWord.getCount()); oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime()); } else { newKeywords.put(searchWord.getKeyword(), searchWord); } } lastUpdateTime = maxNewUpdatedTime; currentKeywords = newKeywords; } private List<SearchWord> getSearchWords(long lastUpdateTime) { // TODO: 从数据库中取出更新时间>lastUpdateTime的数据 return null; } } public Object deepCopy(Object object) { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(object); ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return oi.readObject(); } |
当然,有一种更加取巧的方法,就是我们进行一次浅拷贝,然后对于需要更新的对象,在利于深拷贝的方式,替换一个新的对象,这样能减少了空间和时间,又能保证数据一致性和可用性
public class Demo {
private HashMap<String, SearchWord> currentKeywords=new HashMap<>(); private long lastUpdateTime = -1; public void refresh() { // Shallow copy HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone(); // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中 List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime); long maxNewUpdatedTime = lastUpdateTime; for (SearchWord searchWord : toBeUpdatedSearchWords) { if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) { maxNewUpdatedTime = searchWord.getLastUpdateTime(); } if (newKeywords.containsKey(searchWord.getKeyword())) { newKeywords.remove(searchWord.getKeyword()); } newKeywords.put(searchWord.getKeyword(), searchWord); } lastUpdateTime = maxNewUpdatedTime; currentKeywords = newKeywords; } private List<SearchWord> getSearchWords(long lastUpdateTime) { // TODO: 从数据库中取出更新时间>lastUpdateTime的数据 return null; } } |