记录一些你理解的设计模式,尽量用清晰的例子来解释。策略模式策略模式应该是最基本的设计模式,是对行为的抽象。jdk中的Comparator比较器是一种使用策略设计模式的策略。例如,有一个Student类,它有两个属性,name和age。如果需要打印学生名单并按字母排序,可以使用Comparator接口,内部使用姓名进行比较。如果某天需要按年龄排序,只需要修改Comparator,即使用新的策略,其余不变。工厂模式工厂模式的意义在于可以使用工厂来管理对象的创建和管理,而不是创建者自己。最典型的工厂模式用户是Spring。Spring内部的容器是一个工厂,所有的bean都由这个容器管理,包括它们的创建、销毁和注入。工厂模式分为简单工厂和抽象工厂。它们的区别在于抽象工厂的抽象程度更高,工厂也被抽象成一个接口,这样每次添加新的对象时,都不需要修改工厂的代码。例如,有一个用于存储数据的Repository接口。有DatabaseRepository、CacheRepository、FileRepository,分别存储数据库、缓存和文件中的数据。定义如下:saveincache");}}classFileRepositoryimplementsRepository{@Overridepublicvoidsave(Objectobj){System.out.println("saveinfile");}}简单工厂使用":repository=newFileRepository();break;}returnrepository;}publicstaticvoidmain(String[]args){RepositoryFactoryfactory=newRepositoryFactory();factory.create("db").save(newObject());工厂.create("cache").save(newObject());factory.create("file").save(newObject());}}简单工厂的缺点是每次添加一个新的Repository,RepositoryFactory必须修改的代码抽像工厂的使用publicinterfaceRepositoryFactoryProvider{Repositorycreate();}classDatabaseRepositoryFactoryimplementsRepositoryFactoryProvider{@OverridepublicRepositorycreate(){returnnewDatabaseRepository();}}classCacheRepositoryFactoryimplementsRepositoryFactoryProvider{@OverridepublicRepositorycreate(){returnnewCacheRepository();}}classFileRepositoryFactoryimplementsRepositoryFactoryProvider{@OverridepublicRepositorycreate(){returnnewFileRepository();}}抽象工厂测试:RepositoryFactoryProviderdbProvider=newDatabaseRepositoryFactory();dbProvider.create().save(newObject());RepositoryFactoryProvidercacheProvider=newCacheRepositoryFactory();cacheProvider.create().save(newObject());RepositoryFactoryProviderfileProvider=acFileRepositoryFactory(newObject());fileProvider.create().save(newObject());抽象工厂也对工厂进行了抽象,所以新增一个Repositor对于y,只需要添加一个RepositoryFactory,原代码不需要修改装饰器模式。装饰器模式的作用是可以在不改变原有类的情况下动态地为类添加新的功能。之前写过一篇通过源码分析MyBatis缓存的文章。mybatis中的查询使用装饰器设计模式。用一段简单的代码模拟mybatis中查询的实现原理:@Data@AllArgsConstructor@ToStringclassResult{//查询结果类,相当于一个domainprivateObjectobj;privateStringsql;}publicinterfaceQuery{//查询接口,有简单的查询和缓存查询Resultquery(Stringsql);}publicclassSimpleQueryimplementsQuery{//简单查询相当于直接查询数据库,这里直接返回Result,相当于数据库查询的结果@OverridepublicResultquery(Stringsql){returnnewResult(newObject(),sql);}}publicclassCacheQueryimplementsQuery{//缓存查询,如果查询相同的sql,不直接查询数据库,而是返回map中存在的ResultprivateQueryquery;privateMapcache=newHashMap<>();publicCacheQuery(Queryquery){this.query=query;}@OverridepublicResultquery(Stringsql){if(cache.containsKey(sql)){returncache.get(sql);}Resultresult=query.query(sql);cache.put(sql,result);returnresult;}}测试:简单查询ry=newSimpleQuery();System.out.println(simpleQuery.query("select*fromt_student")==simpleQuery.query("select*fromt_student"));//falseQuerycacheQuery=newCacheQuery(simpleQuery);System.out.println(cacheQuery.query("select*fromt_student")==cacheQuery.query("select*fromt_student"));//true这里CacheQuery是装饰类,SimpleQuery是装饰器。我们通过装饰器设计方式为SimpleQuery动态添加缓存功能,而不需要修改SimpleQuery的代码。当然装饰者模式也有缺点,就是类会太多。如果我们需要添加过滤查询(如果sql中有敏感词,直接返回null,不查询数据库),只需要添加一个FilterQuery装饰器:publicclassFilterQueryimplementsQuery{privateQueryquery;privateListwords=newArrayList<>();publicFilterQuery(Queryquery){this.query=query;words.add("fuck");words.add("sex");}@OverridepublicResultquery(Stringsql){for(Stringword:words){if(sql.contains(word)returnnull;}returnquery.query(sql);}}QueryfilterQuery=newFilterQuery(simpleQuery);System.out.println(filterQuery.query("select*fromt_studentwherename='fuck'"));//nullSystem.out.println(filterQuery.query("select*fromt_studentwherename='format'"));//Result(obj=java.lang.Object@1b4fb997,sql=select*fromt_studentwherename='format')代理模式代理模式就是使用代理类来代替原来的类来操作。比较常见的是在aop中,使用代理模式来完成事务处理。代理方式分为静态代理和动态代理。静态代理的原理是对目标对象进行封装,调用目标对象的方法即可。动态代理和静态代理的区别在于动态代理中的代理类是在程序运行时生成的。Spring中对接口的代理是使用jdk内置的Proxy和InvocationHandler实现的,对类的代理是使用cglib完成的。以1个UserService为例,使用jdk自带的代理方式完成计算方法调用时间要求://UserService接口publicinterfaceIUserService{voidprintAll();}//UserService实现类classUserServiceimplementsIUserService{@OverridepublicvoidprintAll(){System.out.println("printallusers");}}//InvocationHandler策略,这里打印方法调用前后的时间("start:"+System.currentTimeMillis());Objectresult=method.invoke(userService,args);System.out.println("end:"+System.currentTimeMillis());returnresult;}}测试:IUserServiceuserService=newUserService();UserInvocationHandleruih=newUserInvocationHandler(userService);IUserServiceproxy=(IUserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),newClass[]{IUserService.class},uih);proxy.printAll();//打印出start:1489665566456printallusersend:1489665566457组合模式组合模式常与策略模式结合使用,将所有的策略组合起来,遍历这些策略,找到满足条件的策略。写了一篇关于json和xml自动转换原理的SpringMVC研究文章。映射到用户的响应被抽象并封装在HandlerMethodReturnValueHandler策略接口中。在HandlerMethodReturnValueHandlerComposite类中,使用存在的HandlerMethodReturnValueHandler对返回值进行处理,在HandlerMethodReturnValueHandlerComposite内部的代码如下://策略集合privatefinalListreturnValueHandlers=newArrayList();@OverridepublicvoidhandleReturnValue(ObjectreturnValue,MethodParameterreturnType,ModelAndViewContainermavContainer,NativeWebRequestwebRequest)throwsException{//调用selectHandler方法HandlerMethodReturnValueHandlerhandler=selectHandler(returnValue,returnType);if(handler==null){thrownewIllegalArgumentException("Unknownreturnvaluetype:"+returnType.getParameterType().getName());}handler.handleReturnValue(returnValue,vqueueType,returnType)/使用找到的handler进行处理}privateHandlerMethodReturnValueHandlerselectHandler(Objectvalue,MethodParameterreturnType){booleanisAsyncValue=isAsyncReturnValue(value,returnType);//遍历存在的HandlerMethodReturnValueHandlerfor(HandlerMethodReturnValueHandlerhandler:this.returnValueHandlers){if(isAsyncValue&&!(handlerinstanceofAsyncHandlerMethodReturnValueHandler)){continue;}if(handler.supportsReturnType(returnType)){//找到匹配的handlerreturnhandler;}}returnnull;}模板模式类似于策略模式。模板模式会先定义实现的逻辑步骤,但具体的实现方法由子类来完成。与策略模式不同的是,模板模式有逻辑步骤,比如对系内学生进行排序,取出最好的学生。这里有2个步骤,分别是排序和取出第一个学生。一段伪代码:publicabstractclassAbstractStudentGetter{publicfinalStudentgetStudent(Liststudents){sort(students);//***步if(!CollectionUtils.isEmpty(students)){returnsstudents.get(0);//第二步}returnull;}abstractpublicvoidsort(Liststudents);}classAgeStudentGetterextendsAbstractStudentGetter{//获取年龄为***的学生@Overridepublicvoidsort(Liststudents){students.sort(newComparator(){@Overridepublicintcompare(Students1,Students2){returns2.getAge()-s1.getAge();}});}}classNameStudentGetterextendsAbstractStudentGetter{//根据名字的字母顺序,取出***学生@Overridepublicvoidsort(Liststudents){students.sort(newComparator(){@Overridepublicintcompare(Students1,Students2)){returns2.getName().compareTo(s1.getName());}});}}测试:MetricsObserablemetricsObserable=newMetricsObserable();metricsObserable.addObserver(newAdminA());metricsObserable.addObserver(newAdminB());metricsObserable.updateCounter("请求计数",100l);观察者设计模式观察者设计模式的主要使用场景是当一个对象发生变化后,依赖于该对象的对象会收到一个通知。一个典型的例子是rss订阅。订阅博客的rss后,当博客更新时,订阅者会收到新的订阅信息。jdk中提供了Observable和Observer来实现观察者模式://定义一个ObservablepublicclassMetricsObserableextendsObservable{privateMapcounterMap=newHashMap<>();publicvoidupdateCounter(Stringkey,Longvalue){counterMap.put(key,value);setChanged();notifyObservers(counterMap);}}//ObserverpublicclassAdminAimplementsObserver{@Overridepublicvoidupdate(Observableo,Objectarg){System.out.println("adminA:"+arg);}}publicclassAdminBimplementsObserver{@Overridepublicvoidupdate(Observableo,Objectarg){System.out.println("adminB:"+arg);}}测试:MetricsObserablemetricsObserable=newMetricsObserable();metricsObserable.addObserver(newAdminA());metricsObserable.addObserver(newAdminB());metricsObserable.updateCounter("request-count",100l);打印输出:adminB:{request-count=100}adminA:{request-count=100}在享元模式线程池中会构建几个核心线程进行处理,这些线程会去阻塞队列中取任务并执行。这些线程被共享和重用。因为线程的创建、销毁、调度都需要消耗资源,所以不需要每次都创建新的线程,而是共享一些线程。这就是享元模式的使用。类似的还有jdbc连接池,对象池等。之前面试的时候被问到:Integer.valueOf("1")==Integer.valueOf("1")//trueorfalse当时的答案是false。后来查看了Integer的源码,发现内部有一个ClassIntegerCache,用来缓存一些常用的Integer。这个缓存的范围可以在jvm启动的时候设置。其实想一想,我们也应该这样做。我们不需要每次使用对象都返回新的对象,我们可以共享这些对象,因为创建新对象会消耗内存。适配器模式适配器模式更容易理解。就像生活中插在插座上的插头一样,有2口和3口之分。如果电脑的电源插座只有3口,而我们需要一个2口的插座,那么我们就需要用一个插座外接3口的插头,而插座上有2口的插头。这个例子和我们编程是一样的。当用户系统的接口与我们系统内部的接口不一致时,我们可以使用适配器来完成接口的转换。使用继承实现类适配:publicclassSource{publicvoidmethod(){System.out.println("sourcemethod");}}interfaceTargetable{voidmethod();voidnewMethod();}classAdapterextendsSourceimplementsTargetable{@OverridepublicvoidnewMethod(){System.out.println("newmethod");}}测试:Targetabletargetable=newAdapter();targetable.method();//sourcemethodtargetable.newMethod();//newmethod上面的方法是通过接口和继承的方式实现适配器模式。当然,我们也可以使用组合(将Source作为一个属性放在Adapter中)。单例模式单例模式比较容易理解,Spring就是一个典型的例子。Spring中容器管理的对象都有对应的作用域,配置为singleton表示这个对象是单例的,即在Spring容器的生命周期中,只有一个这个类的实例。java中单例模式的写法也有很多种。比如懒汉式,饿汉式,内部类方法,枚举方法等。需要注意的是,如果使用dcl,则需要初始化过程。这篇从JMM角度分析DCL的Java内存模型,讲解了dcl的正确用法。Effectjava中推荐的单例方式是使用枚举类型。外观模式外观模式用于包装一组接口以便于使用。比如系统中有10个模块,有一个功能需要将所有模块组合起来。这时候就需要一个包装类来包装这10个接口,然后调用业务逻辑。
