java常见设计模式


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("接收到了通知");
    }
}

策略模式

策略模式本质是:分离算法,选择实现

优点

  • 开闭原则;
  • 避免使用多重条件转移语句;
  • 提高了算法的保密性和安全性:可使用策略模式以避免暴露复杂的,与算法相关的数据结构。

策略模式体现了面向对象程序设计中非常重要的两个原则

  1. 封装变化的概念。
  2. 编程中使用接口,而不是使用的是具体的实现类(面向接口编程)

举例:我们就以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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值