几个设计模式总结
一、单例模式
一般有八种写法,这里简单记录一下思路,首先单例需要构造器是private,然后一般有个static方法获得这个类的实例。
饿汉式:
所谓饿汉式其实应该是相对于懒汉式,懒汉式就是当你用到这个类的时候,才初始化实例并给你,所以这个饿汉式就是在类加载的时候就初始化了这个对象,所以有两种方法,一种是
private final static Singleton INSTANCE = new Singleton(); public static Singleton getInstance(){ return INSTANCE; }
还有就是利用static块,
private static Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance() { return instance; }
懒汉就是要用到的时候,才去初始化或者说new这个对象。
这就有线程安全的问题了,所以懒汉这里有
线程不安全的情况;
线程安全getInstance同步方法的情况;
采用同步块,只判断一次instance==null所以还是线程不安全的情况;
采用同步块但双重检查的情况;
然后还有两种分别是静态内部类和枚举的写法。
静态内部类:
public class Singleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
枚举:
public enum Singleton { INSTANCE; public void whateverMethod() { } }
单例模式的写法可以参考:https://www.cnblogs.com/zhaoyan001/p/6365064.html——《单例模式的八种写法比较 》
二、工厂模式
有三种,简单工厂模式,工厂模式,抽象工厂模式。
简单工厂模式:
1. 产品有个接口。
2. 有一个工厂类,提供这个接口的产品。
常见模式就是,工厂类一个switch块,有个方法根据你传进去的String值来返回相应的产品实例。
public interface Product { } public class easyProductFactory { public static Product getProduct(String name) { switch(name) { case "xxx": return new ProductImplA(); ....... } } }
但这种版本的话,如果你传进去的字符串出错了,就得不到正确的产品,所以有个改良版的,其实就是把switch改成直接调用一个getXXXProduct的方法
public class EasyProductFactory { public Product getAProduct() { return new ProductAImpl(); } } //用的时候就easyProductFactory.getAProduct();
还有个改版就:静态方法版的:
public class EasyProductFactory { public static Product getProductA(){ return new productAImpl(); } } //用的时候不用new工厂类了,直接EasyProductFactory.getProductA();就好
然后是工厂方法模式或者说是工厂模式
1. 有个产品接口
2. 有个工厂接口
工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
所以其实就从原来的单一的一个生产产品的固定类,变成了一个或者多个实现了Factory接口的工厂是实现类那样咯。
抽象工厂方法
1. 有大于等于一类个产品接口
2. 有个工厂接口
3. 工厂接口中提供不止一种产品
就为什么要有这个抽象工厂呢,就之前的情况呢,一个Product接口是可以抽象出所有的产品的共性的,但如果不能的话,或者说我们需要多个产品的话,就要用这个模式了。
比如说水果接口和玩具接口,这就是两个完全不同的产品了,你无法只用一个Product来抽象吧,那么这个时候的大概形式就是:
public interface Fruit { } public interface Toy { } public interface ProductFactory { Fruit getFruit(); Tou getToy(); } //然后再分别有水果的实现类,玩具的实现类还有工厂的实现类,工厂的实现类需要提供两种产品。
工厂模式的例子或者说是说明可以参考这两篇文章:
https://www.cnblogs.com/zailushang1996/p/8601808.html——《java 三种工厂模式 》
https://blog.csdn.net/u012156116/article/details/80857255——《简单工厂模式、工厂模式以及抽象工厂模式(具体)》这个可以只看它对抽象工厂模式的介绍
三、模板方法模式
一般就是有个抽象类,里面有很多可以重用的方法,你不想实现那么多次,就把它都放在抽象的父类中,然后留下几个protected的abstract方法或者是普通的方法给子类去实现。
比如一个JDBC的实现,如果每个请求都完整走一遍的话,你会发现有很多的代码都是重复写的。
我们先来看看使用JDBC操作数据库需要经过哪些步骤:
1. 获取数据库连接
2. 通过数据库连接得到Statement对象
3. 使用Statement对象进行增删改查
4. 处理异常
5. 关闭连接释放资源
我们再来区分一下这些步骤中,哪些是可变部分,哪些是不可变部分:
1. 获取数据库连接(不可变)
2. 通过数据库连接得到Statement对象(不可变)
3. 使用Statement对象进行增删改查(可变)
4. 处理异常(不可变)
5. 关闭连接释放资源(不可变)
我们可以看到,在5个步骤中,4个是不可变的,只有一个步骤是可变的
所以我们其实可以用一个模板abstract类来封装这些不变的方法,然后JDBC具体的类就继承这个模板类,重写CRUD方法就好了。
典型的模板方法设计模式还有:
HttpServlet中的doGet、doPost方法;
Lock的底层实现AQS也就是抽象队列同步器。
四、观察者模式
定义:
在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
说人话就是:
其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
这个模式的一个结构图:
可以看到,该模式包含四个角色
- 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
- 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
- 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
- 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。
可以类比一个微信公众服务号,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息。
具体的例子和代码见博客:https://www.cnblogs.com/luohanguo/p/7825656.html——《JAVA设计模式之观察者模式 》
五、装饰器模式和代理模式
先主要讲一下装饰器模式的思路吧。
装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。
一个很简单的例子就是,咖啡,牛奶咖啡,牛奶咖啡加冰块。
一般的样子就是:
- 有个接口,比如饮料接口Drink
- 然后有个接口的实现类,比如Coffee
- 然后有个抽象类也去实现Drink接口,里面维护着一个被装饰的类的对象,像Coffee coffee。
- 然后是真正实现装饰的类,就上面那个抽象类的子类——这些类在普通的Coffee上装饰了些东西上去
看完会发现,卧槽这东西和代理模式怎么这么像——代理模式,和被代理类实现同一个接口,然后就可以代替被代理类去接受用户的请求,而代理类里面是包装着真正类的,在调用真正类的某个请求方法的过程中,可以另外做点其他的事情。
那么这两者有什么区别呢?其实我觉得大体上是差不多的了,要说区别就是思维还有用法,比如这里我们用两种设计模式去做同一件事情——在一个方法调用之前调用before(),调用只有调用after()。
这是个很常见的前置增强和后置增强,所以代理模式显然就是在代理类中类似这样的代码:
@Override public void sayHello(String name) { before(); greetingImpl.sayHello(name); after(); }
而如果是装饰者模式的话,就是这样的设计结构:
然后用起来是这个样子的:
public static void main(String[] args) { Greeting greeting = new GreetingAfter(new GreetingBefore(new GreetingImpl())); greeting.sayHello("Jack"); }
有没有像我们的IO模型的样子,就这样理解吧hh
关于装饰器模式的介绍可以看——https://blog.csdn.net/smy_0114/article/details/80653127——《设计模式之-装饰器模式》
关于装饰器模式和代理模式的差别,还有上面我的例子的详细代码可以看——https://blog.csdn.net/andong154564667/article/details/80258061——《代理模式和装饰器模式区别》