title: java常见设计模式
tags: java
cover: /img/image-20210722094649829.png
单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
总之,选择单例模式就是为了避免不一致状态,避免政出多头。
特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
实现方式
饿汉式(静态常量)
可用
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
private final static Object class= new Object();
并配置GET方法
饿汉式(静态代码块)
可用
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的
private static Object class;
static {
object= new object;
}
提供get方法
懒汉式(线程不安全)
不可用
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
private static Object class;
public static Object getInstance(){
if (class==null) class=new Object();
return class
}
懒汉式(线程安全)
不推荐用
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
private static Object class;
public static synchronized Object getInstance(){
if (class==null) class=new Object();
return class
}
懒汉式(线程安全,使用同步代码块)
不可用
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
private static Object class;
public static Object getInstance(){
if (class==null)
synchronized(class.Class){
class=new Object();
}
return class
}
双重检查
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
private static Object class;
public static Object getInstance(){
if (class==null) {
synchronized(class.Class){
if (class==null) class=new Object();
}
}
return class
}
静态内部类
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要object类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在object类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
private static Class Objectclass{
private static final Object class=new Singleton();
}
public static Object getInstance(){
return Objectclass.class
}
枚举
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
public enum Object{
class;
public void whateverMethod(){
}
}
优点
系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
缺点
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
使用场景
• 需要频繁的进行创建和销毁的对象;
• 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
• 工具类对象;
• 频繁访问数据库或文件的对象。
工厂模式
特点
工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
普通工厂模式
我们使用一个发送邮件和短信的例子
//创建二者共有的接口
public interface Sender{
public void Send();
}
//创建实现类
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("send Mail");
}
}
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("send sms");
}
}
//创建工厂类
public class SendFactory{
public Sender producce(String type){
if ("mail".equals(type)) return new MailSender;
else if ("sms".equals(type)) return newSmsSender
else System.out.println("error");
return null;
}
}
多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法
//类就不再重复编写
public calss SendFactory{
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmaSender();
}
}
静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可
public calss SendFactory{
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmaSender();
}
}
抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYHHEqdG-1635238019237)(D:\blog\langBlog\public\img\image-20210908201342435.png)]
//定义一个接口
public interface Provider {
public Sender produce();
}
//两个实现类
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("send Mail");
}
}
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("send sms");
}
}
//两个工厂类
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
这样我们就可以在想增加一个功能时:只需要增加一个类是实现Sender接口,同时做一个工厂类,实现Provider接口就可以了 拓展性比较好
观察者模式
特点:
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PNrfDTQf-1635238019241)(D:\blog\langBlog\public\img\image-20210908202235947.png)]
public interface subject{
//订阅操作
void attach(Observer observer);
//取消订阅
void detach(Observer observer);
//通知变动
void notifyChanged();
}
//定义观察者观察人对象
public interface Observer{
void update();
}
//创建Subject 以及Obsever 的实现类
public static class RealSubject implements Subject {
//定义一个消费者队列
private List<Observer> observerList = new ArrayList<>();
@Override
public void attach(Observer observer) {
observerList.add(observer);
}
@Override
public void detach(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyChanged() {
//遍历消费者队列 对每一个消费者发送消息
for (Observer observer : observerList) {
observer.update();
}
}
//创建Observer的实现类
public static class RealObject implements Observer {
@Override
public void update() {
System.out.println("接收到了通知");
}
}
策略模式
策略模式本质是:分离算法,选择实现
优点
- 开闭原则;
- 避免使用多重条件转移语句;
- 提高了算法的保密性和安全性:可使用策略模式以避免暴露复杂的,与算法相关的数据结构。
策略模式体现了面向对象程序设计中非常重要的两个原则:
- 封装变化的概念。
- 编程中使用接口,而不是使用的是具体的实现类(面向接口编程)
举例:我们就以Java中的TreeSet为例,TreeSet仅仅知道它只是接收一个Comparator这种接口类型,但是具体是哪种实现类,TreeSet并不关心,实现类在真正的传入TreeSet之前,TreeSet本身是不知道的,所以我们可以自己去实现Comparator接口,然后在实现类里面去封装好我们自己的规则(这里的规则你可以当做是算法),比如说我们要实现对一个集合的元素排序,但是到底是要升序排序还是降序排序,这个完全由我们来去控制,我们可以把这种变化的内容封装到自己的实现类中,真正运行的时候才知道具体的实现
定义使用策略模式实现一个简单的加减乘除功能
//定义抽象策略角色,通常情况下使用接口或者抽象类去实现
public interface Strategy {
//实现2个数可以计算
public int calc(int num1,int num2);
}
//定义具体策略角色
public class AddStrategy implements Strategy {
@Override
public int calc(int num1, int num2) { //实现接口中的方法,完成两个数的和
return num1+num2;
}
}
public class SubStrategy implements Strategy {
@Override
public int calc(int num1, int num2) { //实现接口中的方法,完成两个数的差
return num1-num2;
}
//定义环境角色,负责和具体的策略类交互,内部持有一个策略类的引用,给客户端调用
public class Environment {
//持有对策略类的引用
private Strategy strategy;
//有参的构造方法,通过构造器来注入
public Environment(Strategy strategy) {
this.strategy = strategy;
}
public int calulate(int a,int b){
return strategy.calc(a,b);
}
}
代理模式
- 当我们想要隐藏某个类时,可以为其提供代理;
- 当一个类需要对不同的调用这提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,页可以在一个代理类中继续宁权限半段来进行不同权限的功能调用);
- 当我们要扩展某个功能时,可以使用代理模式,在代理模式中进行见到那扩展(只针对见到那扩展,可在引用委托类的语句之前与之后进行)
使用场景
代理模式为其他对象提供了一种代理以控制对这个对象的访问,在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间启动中介的作用。客户端都是和代理打交道的,通过代理来实现与目标对象之间的交流。
1 .抽象角色(接口):声明真实对象和代理对象的共同接口。
2 .代理角色:代理对象角色内部含有对真是对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象,同事代理对象可以在执行真实对象操作时,附加其他操作,相当于对真实对象进行封装
3 . 真实角色:代理角色多代表的真实对象,是我们最终要引用的对象。
代理种类
静态代理
- 优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展;
- 缺点:代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护;
//抽象角色代码
public interface ISinger(){
void sing();
}
//真实角色代码
public class Singer implements ISinger(){
@0verride
public void sing(){
System.out.println("sing a song");
}
}
//代理角色代码
public class SingerProxy implements ISinger{
private ISinger Isinger;
public SingerProxy(ISinger iSinger){
this.Isinger=iSinger;
}
@Override
public void sing(){
System.out.println("向观众问好");
iSinger.sing();
System.out.println("谢谢大家");
}
}
//测试类代码
Singer singer = new Singer();
//创建一个代理角色,构造方法中需要真实角色;
ISinger singerProxy = new SingerProxy(singer);
//代理角色执行方法(代理角色内部调用真实角色对应的方法)
singerProxy.sing();
jdk动态代理
-
优点:代理对象不需要实现接口, 利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
-
缺点:目标对象一定要实现接口,否则不能用动态代理
java 动态代理所使用到的类位于java.lang.reflect包下,一般主要涉及到一下两个类;
Interface InvocationHandler:该接口中仅定义了一个方法public Object invoke(Object obj,Method method,Object[] args),在实际使用时,第一个参数obj一般是指的代理类,method是被代理的方法,如上述代码中的sing(),args为该方法的参数数组;
Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含一下内容:
1)、newProxyInstance可以这样理解,生成一个代理对象,可以代理真实对象,完成真实对象的操作以及自己的额外操作;
2)、所谓Dynamic Proxy是这样一种calss:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface,你当然可以把改class实例当作这些interface中的任何一个来用,当然这个Dynamic proxy其实就是一个proxy,它不会替你做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作;
//抽象角色代码
public interface ISinger(){
void sing();
}
//定义一个class,实现抽象代码,并重写相关的方法;
public class Singer implements ISinger{
@Override
public void sing() {
System.out.println("sing a song");
}
}
//此处并没有动态代理的代码而是在使用时实现
public class Mainclass{
public static void main(String[] args){
//创建一个真实角色
Singer singer=new Singer();
//调用Proxy.newProxyInstance 方法构建一个InvocationHandler对象
//target.getClass().getClassLoader():获取类加载器,用来生成代理对象
//target.getClass().getInterfaces()获取接口元信息
ISinger iSinger =(ISinger) Proxy.newProxyInstance(singer.getClass().getClass().getClassLoader(),singer.get(
//重写一个invoke方法,同时调用method.invoke(target,args);并在该方法的上下添加自己的代码逻辑
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//在真实对象的方法被调用“前”编写自己的业务逻辑
System.out.println("动态代理---向观众问好");
Object returnValue=method.invoke(singer,args);
System.out.println("动态代理---向观众问好");
return returnValue
}
);
iSinger.sing(); }
}
CGLIB动态代理
- 优点:当要实现的类没有实现某一个接口的时候,且又想再不改原代码逻辑的情况下对这个类做代理,使用CGLIB动态代理最合适;
- 缺点:相对来说更复杂一些,且不能被final修饰(被final修饰就不能被继承,就无法生成子类,就不能实现代理);
目标类不能为final,目标对象的方法如果为final / static,那么就不会被拦截,即不会执行目标对象额外的业务方法
//在CGLIB代理中不存在抽象代码
//动态代理中的真实对象
public class Singer {
public void sing(){
System.out.println("sing a song");
}
}
//工厂类代码
public class ProxyFactory implements MethodInterceptor{
private Object target;
public ProxyFactory(Object target){
thhis.target=taeget;
}
public Object getProxyInstance(){
// 工具类
Enhancer en=new Enhancer();
// 设置父类
en.setSuperclass(target.getClass);
//设置回调函数
en.setCallback(this);
//创建子类
return en.create();
}
@Override
public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws excption{
//此处在真实对象方法执行前编写自己的业务逻辑
System.out.println("CGLIB动态代理————向观众问好");
//真实对象对应方法的调用;
Object returnValue = method.invoke(target,objects);
//此处在真实对象方法执行前编写自己的业务逻辑
System.out.println("CGLIB动态代理————谢谢大家");
return returnValue;
}
}
//测试类代码
public class MainClass {
public static void main(String[] args) {
Singer singer = new Singer();
Singer proxySinger = (Singer) new ProxyFactory(singer).getProxyInstance();
proxySinger.sing();
}
}
最后说明:两种动态代理的方法属实有点没整明白看样子还得多看几遍才懂,部分代码搬运别人blog
逻辑
System.out.println(“CGLIB动态代理————向观众问好”);
//真实对象对应方法的调用;
Object returnValue = method.invoke(target,objects);
//此处在真实对象方法执行前编写自己的业务逻辑
System.out.println(“CGLIB动态代理————谢谢大家”);
return returnValue;
}
}
//测试类代码
public class MainClass {
public static void main(String[] args) {
Singer singer = new Singer();
Singer proxySinger = (Singer) new ProxyFactory(singer).getProxyInstance();
proxySinger.sing();
}
}
最后说明:两种动态代理的方法属实有点没整明白看样子还得多看几遍才懂,部分代码搬运别人blog