设计模式(面向对象)有七大原则,分别是:
1.开放-封闭原则
2.单一职责原则
3.依赖倒转原则
4.迪米特法则(也称为最小知识原则)
5.接口隔离原则
6.合成/聚合复用原则
7.里氏代换原则
开放-封闭原则具有理想主义的色彩,他是面向对象设计的终极目标。其他几条则可以看做是开放-封闭原则的实现方法。设计模式就是实现了这些原则,从而达到了代码复用,增加可维护性的目的。
一.开放-封闭原则
概念:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应该尽量在不修改原代码的情况下进行扩展。
在软件周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给代码引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。当软件需求变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。
开放封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现的频繁变化的那些部分作出抽象,然而,对于应用程序中的每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
注意事项:
1.通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。
2.参数类型、引用对象尽量使用接口或者抽象类,而不是实现类
3.抽象层尽量保持稳定,一旦确定不允许修改。
二.单一职责原则
概念:就一个类而言,应该仅有一个引起它变化的原因。
当我们在做编程的时候,很自然的回个一个类加上各种各样的功能。这样意味着,无论任何需求要来,你都需要更改这个类,这样其实是很糟糕的,维护麻烦,复用不可能,也缺乏灵活性。如果一个类承担的职责过多,就等于把这些职责耦合起来,一个职责变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭到很多意想不到的破坏。
三.依赖倒转原则
概念:依赖倒转原则是程序要依赖于抽象接口,不要依赖于具体实现。简单的来说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块的耦合。
有时候为了代码复用,一般会把常用的代码写成函数或类库。这样开发新项目的时候直接用就行了。比如做项目的时候大多要访问数据库,所以我们把访问数据库的代码写成了函数。每次做项目去调用这些函数。那么问题来了,我们要做新项目的时候,发现业务逻辑高层模块都是一样的,但客户却希望使用不同的数据库或存储方式,这时就出现了麻烦。我们希望能再次利用这些高层模块,但是高层模块都是与低层的访问数据库绑定在一起,没办法复用这些高层的模块。所以不管是高层模块和底层模块都应该依赖于抽象,具体一点就是接口或者抽象类,只要接口是稳定的,那么任何一个更改都不用担心。
注意事项:
1.高层模块不应该依赖于低层模块。两个都应该依赖抽象。
2.抽象不应该依赖结节。细节应依赖于抽象。
四.迪米特法则(也称为最小知识原则)
概念:一个软件实体应当尽可能的少与其他实体发生相互作用。每一个软件单位对其他软件单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。迪米特法则不希望类之间建立直接的联系。如果有真的需要建立联系的,也希望能通过他的友元类来转达。因此,应用迪米特法则有可能造成一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互关系,这在一定程度上增加了系统的复杂度。
五.接口隔离原则
概念:客户端不应该依赖他不需要的接口,类间的依赖关系应建立在最小的接口上。
接口隔离原则的核心定义,不出现臃肿的接口,但是“小”是有限度的,首先就是不能违反单一职责原则。
六.合成/聚合复用原则
概念:合成/聚合复用原则经常又叫做合成复用原则,就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过这些对象的委派达到复用已有功能的目的。他的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。
七.里氏代换原则
概念:里氏代换原则是面向对象设计的基本原则之一。即任何基类可以出现的地方,子类一定可以出现。里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受影响时,基类才能被真正复用,而衍生类也能够在积累的基础上增加新的行为,里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。在基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
当满足继承的时候,父类肯定存在非私有的成员,子类肯定是得到了父类的这些非私有成员(假设,父类的成员全部是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的额概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就可以在子类对象中调用这些非私有成员。所以,子类对象可以替换父类对象的位置。
在里氏带环原则下,当需求有变化时,只需继承,而别的东西不会改变。由于里氏代换原则才使得开放封闭称为可能。这样使得子类在父类无需修改就可以扩展。
Java8Stream自定义复杂排序
@Test public void testSet(){ List<Map<String,Object>> list=new ArrayList<Map<String, Object>>(); Map<String,Object> map=new HashMap<String, Object>(); map.put("name","name1"); map.put("age",12); list.add(map); Map<String,Object> map2=new HashMap<String, Object>(); map2.put("name","name2"); map2.put("age",13); list.add(map2); System.out.println("排序前:"+list.toString()); list=list.stream() //第一个排序条件 //假设自定义对象为Obj,则可直接写为Obj::getName .sorted(Comparator.comparing(Application::getName) //第二个排序条件(可一直延伸) .thenComparing(Application::getAge).reversed()).collect(Collectors.toList()); System.out.println("排序后:"+list.toString()); } private static String getName(Map<String,Object> map){ return (String) map.get("name"); } private static int getAge(Map<String,Object> map){ return (int) map.get("age"); }
MybatisPlus分页优化
在采用MyBatisPlus进行开发时,避免不了会引用其提供的分页插件,在引用的时候通过查看代码会发现,他的默认取值逻辑是先查出数据库中所有的数据,然后根据分页参数再取出其中的一页数据;
在一些数据量较大的地方这么拿数据明显不是最优解;
依据我们日常的习惯,肯定是会把分页直接放到sql中,直接查询出某一页的数据进行优化;
MyBatisPlus中提供了一个配置:
import com.baomidou.mybatisplus.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.szss.admin.dao.*")//如代码中有设置范围,则此处无需设置 public class MybatisPlusConfig { /** * mybatis-plus分页插件<br> * 文档:http://mp.baomidou.com<br> */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); return paginationInterceptor; } }
如开放debugSql打印,可以看出前后的sql变化;
这个时候扫描路径下的所有分页都已完成了执行逻辑
长链接转短链接
应用场景:之前手机上收到的垃圾短信都是一大串的连接;现在收到的基本上都是很短的连接 * 大部分长连接转短连接都是采用新浪微博平台的开放接口;下方实际也是采用微博提供的接口
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>生成短链接</title> </head> <!-- <script src="jquery-2.1.1.min.js"></script> --> <body> <input type="text" name="" id="url"> <button id="get_short">生成</button> <a id="myDiv">点我</a> </body> <script type="text/javascript"> // 绑定按钮事件 btn_getshort = document.getElementById('get_short'); btn_getshort.onclick = function (){ var source = '3271760578'; var url = document.getElementById('url').value; var all = 'http://api.t.sina.com.cn/short_url/shorten.json?source=' + source +'&url_long='+ url; document.getElementById("myDiv").href = all; } </script> </html>
Spring切面处理日志
业务场景:在web服务中添加日志,要求在刚进入方法和结束方法的时候打印开始和结束日志;进入方法好说,直接在第一行打印即可,但是return的时候可能会有很多分支,所以我们必须要在更上一层进行处理
实现步骤:
1.创建TestRequestBodyAdvice并实现RequestBodyAdvice接口,类上添加注解@ControllerAdvice
2.创建TestResponseBodyAdvice并实现ResponseBodyAdvice接口,类上添加注解@ControllerAdvice
3.修改其中的默认返回值;例如null改为对应值,false改为true(这个不要盲目的改)
4.引入logger类
5.在关键位置写入要打印的日志
具体代码如下:
@ControllerAdvice public class TestRequestBodyAdvice implements RequestBodyAdvice { private Logger logger= LoggerFactory.getLogger(TestRequestBodyAdvice.class); public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return true; } public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException { return httpInputMessage; } public Object afterBodyRead(Object object, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { logger.debug("即将进入{}方法",methodParameter.getMethod().getName()); return object; } public Object handleEmptyBody(Object object, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return object; } } @ControllerAdvice public class TestResponseBodyAdvice implements ResponseBodyAdvice { private Logger logger= LoggerFactory.getLogger(TestResponseBodyAdvice.class); public boolean supports(MethodParameter methodParameter, Class aClass) { if (aClass.isAssignableFrom(MappingJackson2CborHttpMessageConverter.class)){ return true; } return false; } public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { logger.debug("即将进入{}方法",methodParameter.getMethod().getName()); return object; } }
* 以上两个类并非只是打印日志的作用,他可以在所有请求的进入和返回时刻进行处理,例如进行简单的参数校验,或者查询为空时在此处理给前端一个默认返回值等 * 以上代码采用了适配器模式,通过supports函数来判断是否进入下面的函数进行逻辑处理
Java8特性Stream中map与forEach的区别
//Map接口的定义 <R> Stream<R> map(Function<? super T, ? extends R> mapper); //forEach接口的定义 void forEach(Consumer<? super T> action);
通过接口定义可以看出,两者最明显的区别就是map有返回值,forEach没有返回值
所以在具体调用时,map调用时可以return一个对象到外部,如下:
Oracle over函数的简单使用
OVER() 在使用时一般会配合其他函数一起使用
1.row_number() OVER (PARTITION BY COL1 ORDER BY COL2)
表示根据COL1分组,在分组内部根据 COL2排序,而此函数计算的值就表示每组内部排序后的顺序编号
2.rank() OVER (PARTITION BY COL1 ORDER BY COL2)
跳跃排序,有两个第二名时接下来就是第四名(同样是在各个分组内)
3.dense_rank() OVER (PARTITION BY COL1 ORDER BY COL2)
连续排序,有两个第二名时仍然跟着第三名。相比之下row_number是没有重复值的
4.lag(arg1,arg2,arg3)
arg1是从其他行返回的表达式;arg2是希望检索的当前行分区的偏移量;arg3是在arg2表示的数目超出了分组的范围时返回的值.
5.FIRST_VALUE(arg) OVER (PARTITION BY COL1 ORDER BY COL2)
返回组中数据窗口的第一个值,可以不进行order by
6.LAST_VALUE(arg) OVER (PARTITION BY COL1 ORDER BY COL2)
返回组中数据窗口的最后一个值,可以不进行order by
现在拿row_number举例:
获取MyBatis执行SQL
工具类实现代码片段
@Autowired private SqlSessionFactory sqlSessionFactory; public String showSql(Class<?> cls,String methodName,Object param) { String absoluteMetgodName=getAbsoluteMetgodName(cls,methodName); MappedStatement mappedStatement=sqlSessionFactory.getConfiguration().getMappedStatement(absoluteMetgodName); Configuration configuration=mappedStatement.getConfiguration(); BoundSql boundSql=mappedStatement.getBoundSql(param); Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\\s]+", " "); //CollectionUtils如无对应pom引用可换其他方法,仅判空用 if (CollectionUtils.isNotEmpty(parameterMappings)&& Objects.nonNull(parameterObject)) { TypeHandlerRegistry typeHandlerRegistry=configuration.getTypeHandlerRegistry(); MetaObject metaObject=configuration.newMetaObject(parameterObject); //object取值判断逻辑参考mybatis源码ParameterHandler.setParameters() Object[] values=parameterMappings.stream().map(it->{ String property=it.getProperty(); Object result; if (boundSql.hasAdditionalParameter(property)){ result=boundSql.getAdditionalParameter(property); }else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){ result=parameterObject; }else{ result=metaObject.getValue(property); } //Object转String并拼接进Sql的逻辑可根据自己业务修改或补充 return getParameterValue(result); }).toArray(); sql=String.format(sql.replace("?","%s"),values); } return sql; } private String getAbsoluteMetgodName(Class<?> cls,String mothodName){ return cls.getCanonicalName()+"."+mothodName; } private String getParameterValue(Object obj) { String value = null; if (obj instanceof String) { value = "'" + obj.toString() + "'"; } else if (obj instanceof Date) { SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); value="to_date('"+sdf.format(obj)+"','yyyy-mm-dd hh24:mi:ss')"; } else { if (obj != null) { value = obj.toString(); } else { value = "\'\'"; } } return value; }
调用代码片段
xxx.class为对应mapper的文件名
采用java8Stream对集合进行处理
//测试数据 List<Map<Integer,Object>> list=new ArrayList<>(); list.add((Map<Integer, Object>) new HashMap<>().put(222,"222")); list.add((Map<Integer, Object>) new HashMap<>().put(111,"111")); //筛选数据 List<Map<Integer,Object>> a=list.stream().filter(info -> info.get("key").equals("123")).collect(Collectors.toList()); //排序 针对String排序系统给出了更好的解决方案,如list中为自定义对象可通过以下方法进行排序 list=list.stream().sorted(Comparator.comparing(Map::keySet)).collect(Collectors.toList());//升序 list=list.stream().sorted(Comparator.comparing(Map::keySet).reversed()).collect(Collectors.toList());//降序 //遍历集合进行操作,{}内可写入大量代码块 list.forEach((Map<Integer, Object> info)->{info.put(333,"333");}); //筛选出来的数据条数 System.out.println(list.stream().filter(info->info.equals("123")).count());
* 当前只列举出了本人近期使用频率较高的几种语法,如无特殊的业务逻辑,以上几种的组合使用已经可以在一定程度上减少大量的开发代码 * 后续如遇到其他使用频率较高的写法会持续更新进来