【设计模式】案例-创建型
简单工厂
常规实现:单独的工厂类,负责创建,通常方法createXXX,也可其他命名
存在两种实现方式
1、常规实现
1 public class RuleConfigParserFactory { 2 public static IRuleConfigParser createParser(String configFormat) { 3 IRuleConfigParser parser = null; 4 if ("json".equalsIgnoreCase(configFormat)) { 5 parser = new JsonRuleConfigParser(); 6 } else if ("xml".equalsIgnoreCase(configFormat)) { 7 parser = new XmlRuleConfigParser(); 8 } else if ("yaml".equalsIgnoreCase(configFormat)) { 9 parser = new YamlRuleConfigParser(); 10 } else if ("properties".equalsIgnoreCase(configFormat)) { 11 parser = new PropertiesRuleConfigParser(); 12 } 13 return parser; 14 } 15 }
2、优化实现:类似单例+简单工厂,提前创建好对象缓存
public class RuleConfigParserFactory { private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>(); static { cachedParsers.put("json", new JsonRuleConfigParser()); cachedParsers.put("xml", new XmlRuleConfigParser()); cachedParsers.put("yaml", new YamlRuleConfigParser()); cachedParsers.put("properties", new PropertiesRuleConfigParser()); } public static IRuleConfigParser createParser(String configFormat) { if (configFormat == null || configFormat.isEmpty()) { return null;//返回null还是IllegalArgumentException全凭你自己说了算 } IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase()); return parser; } }
工厂方法
常规实现:
1 public interface IRuleConfigParserFactory { 2 IRuleConfigParser createParser(); 3 } 4 5 public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory { 6 @Override 7 public IRuleConfigParser createParser() { 8 return new JsonRuleConfigParser(); 9 } 10 } 11 12 public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory { 13 @Override 14 public IRuleConfigParser createParser() { 15 return new XmlRuleConfigParser(); 16 } 17 } 18 // 工厂方法使用,变更需要修改load(),导致设计更复杂 19 public class RuleConfigSource { 20 public RuleConfig load(String ruleConfigFilePath) { 21 String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); 22 23 IRuleConfigParserFactory parserFactory = null; 24 if ("json".equalsIgnoreCase(ruleConfigFileExtension)) { 25 parserFactory = new JsonRuleConfigParserFactory(); 26 } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) { 27 parserFactory = new XmlRuleConfigParserFactory();
优化实现:增加工厂的工厂
1 public class RuleConfigSource { 2 public RuleConfig load(String ruleConfigFilePath) { 3 String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); 4 5 IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension); 6 if (parserFactory == null) { 7 throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath); 8 } 9 IRuleConfigParser parser = parserFactory.createParser(); 10 11 String configText = ""; 12 //从ruleConfigFilePath文件中读取配置文本到configText中 13 RuleConfig ruleConfig = parser.parse(configText); 14 return ruleConfig; 15 } 16 17 private String getFileExtension(String filePath) { 18 //...解析文件名获取扩展名,比如rule.json,返回json 19 return "json"; 20 } 21 } 22 23 //因为工厂类只包含方法,不包含成员变量,完全可以复用, 24 //不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。 25 public class RuleConfigParserFactoryMap { //工厂的工厂 26 private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>(); 27 28 static { 29 cachedFactories.put("json", new JsonRuleConfigParserFactory()); 30 cachedFactories.put("xml", new XmlRuleConfigParserFactory()); 31 cachedFactories.put("yaml", new YamlRuleConfigParserFactory()); 32 cachedFactories.put("properties", new PropertiesRuleConfigParserFactory()); 33 } 34 35 public static IRuleConfigParserFactory getParserFactory(String type) { 36 if (type == null || type.isEmpty()) { 37 return null; 38 } 39 IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase()); 40 return parserFactory; 41 } 42 }
原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。
基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
实际上,创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。
如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从RPC、网络、数据库、文件系统等非常慢速的IO中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。
需求:
假设数据库中存储了大约10万条“搜索关键词”信息,每条信息包含关键词、关键词被搜索的次数、信息最近被更新的时间等。系统A在启动的时候会加载这份数据到内存中,用于处理某些其他的业务需求。为了方便快速地查找某个关键词对应的信息,我们给关键词建立一个散列表索引。
不过,我们还有另外一个系统B,专门用来分析搜索日志,定期(比如间隔10分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对v2版本的数据进行更新,得到v3版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。
public class Demo { private ConcurrentHashMap<String, SearchWord> currentKeywords = new ConcurrentHashMap<>(); private long lastUpdateTime = -1; public void refresh() { // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中 List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime); long maxNewUpdatedTime = lastUpdateTime; for (SearchWord searchWord : toBeUpdatedSearchWords) { if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) { maxNewUpdatedTime = searchWord.getLastUpdateTime(); } if (currentKeywords.containsKey(searchWord.getKeyword())) { currentKeywords.replace(searchWord.getKeyword(), searchWord); } else { currentKeywords.put(searchWord.getKeyword(), searchWord); } } lastUpdateTime = maxNewUpdatedTime; } private List<SearchWord> getSearchWords(long lastUpdateTime) { // TODO: 从数据库中取出更新时间>lastUpdateTime的数据 return null; } }
新的特殊要求:任何时刻,系统A中的所有数据都必须是同一个版本的,要么都是版本a,要么都是版本b,不能有的是版本a,有的是版本b。那刚刚的更新方式就不能满足这个要求了。除此之外,我们还要求:在更新内存数据的时候,系统A不能处于不可用状态,也就是不能停机更新数据。
常规解决方案(非最优解):当我们要更新内存中的数据的时候,重新创建另一个版本数据(假设是版本b数据),等新的版本数据建好之后,再一次性地将服务版本从版本a切换到版本b。这样既保证了数据一直可用,又避免了中间状态的存在。
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; }
需区分浅拷贝和深拷贝差别,避免导致意想不到的问题发生
在上面的代码实现中,newKeywords构建的成本比较高。我们需要将这10万条数据从数据库中读出,然后计算哈希值,构建newKeywords。这个过程显然是比较耗时。
为了提高效率,原型模式就派上用场了。
1、先采用浅拷贝的方式创建newKeywords。
2、对于需要更新的SearchWord对象,我们再使用深度拷贝的方式创建一份新的对象,替换newKeywords中的老对象。毕竟需要更新的数据是很少的。
这种方式即利用了浅拷贝节省时间、空间的优点,又能保证currentKeywords中的中数据都是老版本的数据。具体的代码实现如下所示。这也是标题中讲到的,在我们这个应用场景下,最快速clone散列表的方式。
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; } }
浙公网安备 33010602011771号