一 闲谈

最近在复习设计模式,很多模式让说名字,可能记不住,但是日常开发中用的挺多的,有时候形成一种习惯。这一轮学习先把模式简化下,以前学什么总讲究完备,好像少了点很重要似的,其实很多时候应用的是基础知识,没必要死扣些定义的,也浪费时间。

一直以来觉得设计模式可能没那么重要,一方面虽然没有记熟这些设计模式,但是遵守设计原则,自然而然开发的模式很大的可能就是特定的设计模式。这里面也分享个小技巧,判断代码的坏味道,即哪些代码是需要你重构的,比较需求连续变化,你对这点代码的修改一点也不抗拒说明你的代码可扩展性不错,如果很有情绪,不一定就是需求变化引起的,而是自己实在是不想改那个看起来一团乱麻的代码。

设计的最重要的原则是隔离变化,将不变的和变化的部分分离开来,变化的部分被我们限定住了,这样就减轻了我们脑袋思考的容量,大脑似乎更喜欢处理这种很守规律的事情,让大脑感觉可以掌控它就不容易烦了。分离了之后,我们就可以针对接口,这种不变的抽象编程,而不要针对具体的实例编程。不变的在顶层,变化的在底层,像不像领导说话,说的很抽象,所以不会出错,越具体做事越容易出错,抽象的越狠,通用性越强,当然也不容易出错。

举个栗子,比如我们系统里面需要定义很多灯,那么我们可以把灯最重要的方法抽出来定义成灯的接口,比如两个方法“开灯”和“关灯”,我们代码在操作灯的时候只要针对接口就信了,不用关心是声控灯,普通电灯,还是煤油灯,或者智能控制灯,我们把常变化的不同的灯和灯最常用不变化的方法抽离了。

二 创建类设计模式概述

创建类模式

创建型设计模式 简单概括就是将对象的创建和对象的使用相分离:

  1. 单例模式:用于在一个进程中创建只有一个对象的类,好处节省内存,保证全局唯一等,因为创建对象比较特殊,所以形成一种模式。

  2. 工厂模式:根据条件不同,创建相关的不同对象,有条件的创建对象。

  3. 建造者模式:属性太多,导致构造函数的参数多,如果采用多个set方法,又缺少了安全性。

  4. 原型模式:对象创建比较耗时,我们可以采用利用现有对象进行复制或拷贝的方式创建对象。可以说创建模式是从对象创建的不同角度设计不同的模式。

三 单例模式

我(类)只有一个对象,好单纯,ok,你就是单例。我们放下一切已有的观点,我们来想,对象创建在java里面是通过new关键字的,我们当然可以new多个,那就无法控制一个类只有一个对象了。

怎么办,那我们就禁止外面new的类的方式创建对象,了解java的朋友就明白了,我们可以将构造函数访问权限改成私有,作为私有方法只能通过内部的方法调用,而调用需要对象,而对象需要创建,因为私有构造函数,外部又无法创建对象,这是不是就死循环了。不是不是,类的方法还有静态方法,可以在不创建对象的时候就可以访问类的方法,至于私有构造函数只有内部可以访问,那么我们就在类的内部创建对象,返回出去不就可以了吗?好了,直接上代码:最简单版本,这种写法最简单,如果sg创建不耗时,其实用这种就好了。

public  class sg {
// 静态方法操作静态变量
private static sg instance  = new sg();private sg() {.....
}public static sg getInstance()  {return instance;
}
}

一般创建单例要注意点:1. 私有构造函数;2.考虑线程安全;3. getInstance性能是否够高;4.是否考虑延迟加载。

什么饿汉模式,双重检查,懒汉模式懒得写。考虑延迟加载的最简单的单例了:

public class sg2{ private sg2() {}private static class SingletonHolder{private static final sg2 instance = new IdGenerator();}public static sg2getInstance() {return SingletonHolder.instance;}
}

不会主动生成sg2对象,直到真正调用sg2getInstance方法,而且也是线程安全的,推荐。我记得不错的话,在Effitive java中推荐枚举型的单例:

public enum IdGenerator {INSTANCE;private AtomicLong id = new AtomicLong(0);public long getId() { return id.incrementAndGet();}
}

与其他单例比较好处,JVM保证枚举类型和其变量在JVM中唯一的,序列化后在反序列化都可以保证是同一个对象,有这种特殊需求可以用,没有的话用起来感觉有点奇怪。

四 工厂模式

举个简单例子,比如我们配置文件由于特殊原因需要支持xml格式,json格式,yaml等不同格式的配置,那么我们在解析的时候很容易想到定义个解析接口,然后分别实现xml格式解析器,json格式解析器,yaml格式解析器。

如何使用那,能想到的就是根据传入的配置文件的后缀来生成不同的解析器,然后调用解析器解析配置文件:

public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);if (parser == null) {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}String configText = "";//从ruleConfigFilePath文件中读取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}
}public class RuleConfigParserFactory {public static IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}

上述就是简单工厂模式,也没干嘛,就是定义个单独的工厂类来负责解析对象的创建;将揉在一起的代码分开,对象创建是一块,使用对象是一块;其实复杂度没办法消除,只能转移或改变形式,变的容易接受点,就是我们需要的。

好像一堆if else看起来不太好,像这个情况我们可以如下改造,这个下面的例子是我见过最多的,也最常用的代码了:

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;}
}

至于工厂方法,抽象工厂,太麻烦,创建太多类,平时用的不太多,懒得写。

五 建造者模式

正如上面所说,建造者模式解决的是构造函数的参数太多,如果简化构造函数的参数,通过set方法来设置那,可能会在set之前对象就被使用的可能不够安全,开放set方法也不安全;如果各个参数之间有一定的校验关系,校验的逻辑放在哪里那,可能都不太合适。

public class ResourcePoolConfig {private String name;private int maxTotal;private int maxIdle;private int minIdle;private ResourcePoolConfig(Builder builder) {this.name = builder.name;this.maxTotal = builder.maxTotal;this.maxIdle = builder.maxIdle;this.minIdle = builder.minIdle;}//...省略getter方法...//我们将Builder类设计成了ResourcePoolConfig的内部类。//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。public static class Builder {private static final int DEFAULT_MAX_TOTAL = 8;private static final int DEFAULT_MAX_IDLE = 8;private static final int DEFAULT_MIN_IDLE = 0;private String name;private int maxTotal = DEFAULT_MAX_TOTAL;private int maxIdle = DEFAULT_MAX_IDLE;private int minIdle = DEFAULT_MIN_IDLE;public ResourcePoolConfig build() {// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("...");}if (maxIdle > maxTotal) {throw new IllegalArgumentException("...");}if (minIdle > maxTotal || minIdle > maxIdle) {throw new IllegalArgumentException("...");}return new ResourcePoolConfig(this);}public Builder setName(String name) {if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("...");}this.name = name;return this;}public Builder setMaxTotal(int maxTotal) {if (maxTotal <= 0) {throw new IllegalArgumentException("...");}this.maxTotal = maxTotal;return this;}public Builder setMaxIdle(int maxIdle) {if (maxIdle < 0) {throw new IllegalArgumentException("...");}this.maxIdle = maxIdle;return this;}public Builder setMinIdle(int minIdle) {if (minIdle < 0) {throw new IllegalArgumentException("...");}this.minIdle = minIdle;return this;}}
}// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder().setName("dbconnectionpool").setMaxTotal(16).setMaxIdle(10).setMinIdle(12).build();

好处是规避了刚才说的几个问题,坏处是有属性的重复定义。建造者模式针对同一个类需要多个参数做定制的时候使用,与工厂模式是创建不同的类对象相比,建造者模式是创建不同参数配置的同一个类的对象。

六 原型模式

原型模式就是利用现有的对象通过复制或深度拷贝等方法来创建新的对象,其实用的挺少,王争老师的例子却是个有意思的用法。

例子是这样的,我们有个统计系统,需要统计一个搜索系统中,各个关键词的搜索次数,定期统计日志更新数据库中,同时内存中也有一份统计数据;系统要求,在更新内存中统计的数据的时候,数据要整体更新,不能更新局部,不能有不可用状态。

简单的实现思路,定期从数据库中获取数据,创建个新的内存统计数据,创建好之后再替换老的内存统计数据即可,简单实例代码:

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;}
}

我们可以看到我们需要将所有的数据都从数据库中捞出,然后加入到新的缓存中,这样的性能会比较差。两次轮询的如果比较短,大量的数据是没有变化的,我们可以复制一个老的内存数据,然后查询数据库的时候,只获取变化的数据(数据带有时间戳)。

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;}
}

上述代码也存在问题,问题就在于我们clone方法只会clone基础数据类型,对于引用数据类型其实克隆的是引用本身,对象是共享的,一旦我们修改了统计的数据,会造成老内存统计数据被污染了,有的是老版本数据,有的是新版本的数据,不符合要求说的原子性。

那我们是不是要全部进行深拷贝对象那,也不是,优秀的思路是我们开始时候仍然采用上面clone方式生成对象,如果这个对象发生改变了,那为了不破坏老的统计数据,我们移除老对象,再添加个新对象即可,方法挺好。

public class Demo {private HashMap<String, SearchWord> currentKeywords=new HashMap<>();private long lastUpdateTime = -1;public void refresh() {// Shallow copyHashMap<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;}
}

七 备注

文章中的代码来自极客时间的《设计模式之美》-王争。

八 诗词欣赏

江城子·墨云拖雨过西楼[宋] [苏轼] 墨云拖雨过西楼。水东流。晚烟收。
柳外残阳,回照动帘钩。
今夜巫山真个好,花未落,酒新篘。美人微笑转星眸。月花羞。捧金瓯。
歌扇萦风,吹散一春愁。试问江南诸伴侣,谁似我,醉扬州。

创建类设计模式简洁介绍相关推荐

  1. Python、设计原则和设计模式-创建类设计模式

    Python.设计原则和设计模式 前言 程序的目标:高内聚 低耦合 有哪些设计原则 设计原则是「高内聚.低耦合」的具体落地. 单一职责原则要求在软件系统开发.设计中,一个类只负责一个功能领域的相关职责 ...

  2. python创建类统计属性_轻松创建统计数据的Python包

    python创建类统计属性 介绍 (Introduction) Sometimes you may need a distribution figure for your slide or class ...

  3. 《设计模式之禅》读书笔记之C#版-创建类模式

    备注:由于读的电子书版本是pdf的,影印都有些看不清楚.所有练习代码都单独放到了GitHub上方便以后查看. https://github.com/yuhezhangyanru/DesignPatte ...

  4. 设计模式之创建类模式PK

    创建类模式包括: 工厂方法模式 建造者模式 抽象工厂模式 单例模式 原型模式 创建类模式能够提供对象的创建和管理职责. 其中单例模式和原型模式非常容易理解, 单例模式是要保持在内存中只有一个对象,原型 ...

  5. 技术图文:02 创建型设计模式(下)

    创建型设计模式(下) 知识结构: 图1 知识结构 单例模式 – 确保对象的唯一性 Sunny 软件公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量 ...

  6. 创建型设计模式对比总结 设计模式(八)

    创建型模式是new 的一种替代方式,可以将对象的创建与具体的类型进行分离 目前已经介绍了5种创建型设计模式(如果简单工厂算一种的话,那就是6种) 分别是: 简单工厂模式.工厂方法模式.抽象工厂模式.建 ...

  7. java面向对象程序设计第三版_JAVA面向对象程序设计之创建型设计模式

    [本文详细介绍了JAVA面向对象程序设计中的创建型设计模式,欢迎读者朋友们阅读.转发和收藏!] 1 基本概念 1.1 什么是设计模式 设计模式( Design pattern )是一套被反复使用.多数 ...

  8. 从框架源码中学习创建型设计模式

    文章目录 从框架源码中解读创建型设计模式 工厂模式 案例一:RocketMQ源码-创建Producer生产者 案例二:RocketMQ源码-创建过滤器工厂 抽象工厂 案例一:Dubbo源码-创建缓存的 ...

  9. JavaScript设计模式之创建型设计模式

    此系列总结与<JavaScript设计模式>,总共分为创建型设计模式.结构型设计模式.行为型设计模式.技巧型设计模式和架构性设计模式五大类. github原文地址:YOU-SHOULD-K ...

最新文章

  1. 香港计算机本科专业,中国香港计算机本科专业包含哪些呢?
  2. Spark分析之Standalone运行过程分析
  3. pythonos pathjson_Python Json数据文件操作原理解析
  4. kubernetes1.8.4 安装指南 -- 7. kubernetes node安装
  5. 12-openldap使用AD密码
  6. 最新版本_adt-bundle-windows-x86_64-20140702 无法建立avd
  7. 无法初始化链接服务器 (null) 的 OLE DB 访问接口 Microsoft.Jet.OLEDB.4.0 的数据源对象。
  8. 怎么导出oracle库,【DG】怎么从Oracle备库导出数据
  9. java 循环删除hashmap中的键值对,解决java.util.ConcurrentModificationException报错
  10. 程序设计与算法----动态规划之最长上升子序列
  11. 词法分析之LED文件生成程序【调试中......】
  12. 台式计算机快捷键大全,最常用的电脑键盘快捷键大全
  13. 学校毕业论文格式对奇数页页眉和偶数页页眉有要求,遇到问题请教
  14. 网页游戏防外挂策略。
  15. 在计算机上格式u盘启动,请问U盘制作成启动盘后插电脑上显示0字节,打不开也无法格式化,提示磁盘写有保护怎么回事?...
  16. Unity IOS微信登录
  17. qt linux不能读写u盘文件,Qt读取U盘文件内容
  18. 线代复习小结 矩阵等价、相似、合同的区别以及向量组等价 2019/09/13
  19. 雷达导引头伺服系统的建模与仿真
  20. 硬件电路设计入门之计数器74HCT4060

热门文章

  1. springcloud适配mysql和oracle数据库
  2. oracle--2.服务
  3. C语言进阶——地址和指针
  4. STM32的串口打印土壤湿度传感器(YL-69)数据
  5. ORB-SLAM2源码特征点提取
  6. 天龙手游角色删除服务器还有显示,天龙八部手游怎么删除角色_角色删除方法详解_玩游戏网...
  7. 10.26 酷狗音乐校招前端一面经历
  8. Google 百度 图标收藏(三)
  9. 关于如何正确安装python的一些资源包和库的操作命令
  10. IEEE 标准 802.1Qbv™-2015