设计模式

基础

1,使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性。实现可维护、可扩展,就必须尽量复用代码,并且降低代码的耦合度。

2,依赖注入:是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有四种,分别是:构造注入,设值注入(Setter注入),接口注入,基于注解注入。构造注入是指通过构造函数,在新建对象时传入所依赖类型的对象;设值注入是指通过Setter方法,让外部容器调用传入所依赖类型的对象;而接口注入是指通过在接口中声明的业务方法来传入具体类的对象;基于注解注入,在私有变量前加@Autowired等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。

这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。对象在运行时而不是在编译时被赋予它们的依赖关系。对对象的『依赖』是注入进来的,而和它的构造方式解耦了。构造它这个『控制』操作也交给了第三方,也就是控制反转,显现了和依赖的解耦。

优势是可复用注入的对象,该对对象可以注入到多个地方,减小了创建多个相同对象的开销,并且保证了依赖对象都是相同的,以后修改注入的对象也只需要修改一处即可。。

3,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段 。开闭原则,里氏替换原则,依赖倒置原则与依赖注入关系:定义一个抽象类/接口A,类/接口中定义抽象方法m,各个子类/实现类B,C根据自身需求重写抽象方法m。

2,七大原则:

 

工厂模式

简单工厂模式:定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。由于工厂类封装了对象的创建过程,所以客户端应该不关心对象的创建。适用于需要创建的对象较少;客户端不关心对象的创建过程。

工厂方法模式不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂,每个对象都有一个与之对应的工厂,将生产任务交给不同的派生类工厂,这样不用通过指定类型来创建对象了。让子类工厂决定将哪一个类实例化,让一个类的实例化延迟到其子类。客户端不需要知道它所创建的对象的类,不需要知道每个产品具体叫什么名,只知道创建它的工厂名就完成了创建过程。使得创建对象和使用对象是分离的,并且客户端总是引用抽象工厂和抽象产品,以便工厂方法能随时切换不同的子类返回,却不影响调用方代码。

抽象工厂模式:工厂是抽象的,产品是抽象的。这个抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品。AbstractFactory(抽象工厂)声明了一组用于创建不同产品的方法。ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建对象的方法,生成一组具体对象。AbstractProduct(抽象产品):它为每种对象声明接口,在其中声明了对象所具有的业务方法。ConcreteProduct(具体产品):它定义具体工厂生产的具体对象。

抽象工厂模式是为了让创建工厂和一组产品与使用相分离,并可以随时切换到另一个工厂以及另一组产品;抽象工厂模式实现的关键点是定义工厂接口和产品接口,但如何实现工厂与产品本身需要留给具体的子类实现,客户端只和抽象工厂与抽象产品打交道。

装饰器模式

动态地给一个对象添加一些额外的职责。就增加功能来说,相比生成子类更为灵活。n个功能共产生Cn1+Cn2+...+Cnn种组合,需要2n1个类实现。Decorator模式的目的就是把一个一个的附加功能,用Decorator的方式给一层一层地累加到原始数据源上,最终通过组合获得我们想要的功能。

把核心功能和附加功能给分开了。如果我们要新增核心功能,就增加Component的子类。如果我们要增加附加功能,就增加Decorator的子类。两部分都可以独立地扩展,而具体如何附加功能,由调用方自由组合,从而极大地增强了灵活性。

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例的构造方法必须是private,这样就防止了调用方自己创建实例,但是在类的内部,是可以用一个静态字段来引用唯一创建的实例的,供一个静态方法,直接返回实例。

延迟加载:即在调用方第一次调用getInstance()时才初始化全局唯一实例,在多线程中是错误的,在竞争条件下同时发现实例为空,会创建出多个实例。必须对整个方法进行加锁:public synchronized static Singleton getInstance(),但加锁会严重影响并发性能。

双重检查:如果多个线程同时了通过了第一次检查,并且其中一个线程首先通过了第二次检查并实例化了对象,那么剩余通过了第一次检查的线程就不会再去实例化对象。除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,解决了性能消耗的问题。

INSTANCE = new Singleton();分配内存空间->初始化对象->将对象指向刚分配的内存空间。但编译器为了性能的原因,可能会将第二步和第三步进行重排序:分配内存空间->将对象指向刚分配的内存空间->初始化对象。当进行完第二步后,另一个线程尝试获取对象现象对象非空,然后获取返回,此时对象还未完成初始化,导致后续产生错误。为此需要在INSTANCE前加入关键字volatile,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

 

如果没有特殊的需求,使用Singleton模式的时候,最好不要延迟加载,这样会使代码更简单。

对于Web程序大部分服务类都应该被视作Singleton,如果全部按Singleton的写法写,会非常麻烦,所以,通常是通过约定让框架(例如Spring)来实例化这些类,保证只有一个实例,调用方自觉通过框架获取实例而不是new操作符。

模板模式

定义一个操作中的算法的骨架,对于某些暂时确定不下来的步骤,就留给子类去实现好了,这样不同的子类就可以定义出不同的步骤,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

为了防止子类重写父类的骨架方法,可以在父类中对骨架方法使用final。对于需要子类实现的抽象方法,一般声明为protected,使得这些方法对外部客户端不可见。Java标准库也有很多模板方法的应用,AbstractQueuedSynchronizer都定义了很多通用操作,子类只需要实现某些必要方法。

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。

通知者不能直接引用被通知者,不然耦合度太高,新加被通知者就要修改通知者代码。它引用一个Observer接口对象的集合,任何想要得到通知的对象,只要实现该接口,并且把自己注册到通知者即可,每当要发通知时,通知者遍历集合给被通知者发送消息。

广义的观察者模式包括所有消息系统。所谓消息系统,就是把观察者和被观察者完全分离,通过消息系统本身来通知:消息发送方称为Producer,消息接收方称为Consumer,Producer发送消息的时候,必须选择发送到哪个Topic。Consumer可以订阅自己感兴趣的Topic,从而只获得特定类型的消息。

v2-b6ed65f370a766620718ad4227d5d4e5_r

异步通知:使得所有观察者可以并发同时处理。多线程方式,对每个观察者开一个线程发送通知,缺点开销大,使用线程池,重用线程,限制线程数,使用集合保存submit()返回的Future对象,然后遍历该集合,使用带超时参数的get,如果超时还未成功返回,直接取消执行,防止卡死。

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。Adapter的步骤如下:实现目标接口A;内部持有一个待转换接口B的引用;在目标接口A的实现方法内部,调用待转换接口B的方法。

FutureTask

代理模式

为其他对象提供一种代理以控制对这个对象的访问。在代理类中实现权限检查等额外功能。对被代理类无改变,职责清晰:一个类只负责一件事;易于测试:一次只测一个功能。

虚代理:Virtual Proxy:它让调用者先持有一个代理对象,但真正的对象尚未创建。如果没有必要,这个真正的对象是不会被创建的,直到客户端需要真的必须调用时,才创建真正的对象。JDBC的连接池返回的JDBC连接(Connection对象)就可以是一个虚代理,即获取连接时根本没有任何实际的数据库连接,直到第一次执行JDBC查询或更新操作时,才真正创建实际的JDBC连接。

保护代理:Protection Proxy,它用代理对象控制对原始对象的访问,常用于鉴权。

智能引用:Smart Reference,它也是一种代理对象,如果有很多客户端对它进行访问,通过内部的计数器可以在外部调用者都不使用后自动释放它。