Java设计模式总结

本文介绍了装饰模式(Wrapper)与单例模式(懒汉式、饿汉式、双重检查锁、静态内部类和枚举实现)的概念及其实现,重点讨论了它们在编程中的应用和各自优缺点。此外,还涵盖了Template方法模式在算法骨架设计中的运用。
摘要由CSDN通过智能技术生成

目录

一、装饰模式(Wrapper)

二、单例设计模式

1、懒汉式(线程不安全)

2、饿汉式(线程安全)

3、双重检查锁实现(线程安全)

4、静态内部类实现(线程安全)

5、枚举类实现(线程安全)

三、Template设计模式

四、模板设计模式


 

 

一、装饰模式(Wrapper)

装饰模式又名包装(Wrapper)模式,

装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,

public class WrapTest{
    public static void main(String[] args) throws IOException {
        ChineseDog cd=new ChineseDog(new Dog());
        cd.ability();
    }
}
 
interface Animal {
    public void ability();
}
 
class Dog implements Animal{
    @Override
    public void ability() {
        System.out.println("吃骨头");
    }
}
 
class ChineseDog implements Animal{
    private Dog d;
    public ChineseDog(Dog d){
        this.d=d;
    }
    @Override
    public void ability() {
        d.ability();
        System.out.println("看门");
    }
}

我们可以用继承实现这样的功能,但是继承关系耦合性太强了,父类修改了子类也会跟着改变,

而使用装饰模式就没有那么强的耦合性,两个类都是独立的,被装饰类的变化和装饰类的变化无关。

 

二、单例设计模式

单例设计模式即保证类在内存中只有一个对象,主要有以下五种方法实现:

1、懒汉式(线程不安全)

public class Singleton { 
    private static Singleton uniqueInstance;
    private Singleton() {}
    public static Singleton getUniqueInstance() { 
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        } return uniqueInstance;
    }
}
  • 说明:先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。
  • 优点:延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。
  • 缺点:线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null) ,若此时还未实例化,也就是uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton(); ,就会实例化多个实例;
  • 解决方案:在getUniqueInstance()方法上添加锁:private static synchronized Singleton getUinqueInstance(),避免多个线程同时获取实例
     

2、饿汉式(线程安全)

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
}
  • 说明:先不管需不需要使用这个实例,直接先实例化好实例 (饿死鬼一样),然后当需要使用的时候,直接调方法就可以使用了。
  • 优点:提前实例化好了一个实例,避免了线程不安全问题的出现。
  • 缺点:直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会操作系统的资源浪费
     

3、双重检查锁实现(线程安全)

public class Singleton {
    private volatile static Singleton uniqueInstance; 
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}
  • 说明:双重检查锁相当于是改进了线程安全的懒汉式。
    • 线程安全的懒汉式的缺点是性能降低了,造成的原因是因为即使实例已经实例化,依然每次都会有锁。而现在,我们将锁的位置变了,并且多加了一个检查。 也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。
    • 为什么使用 volatile 关键字修饰了 uniqueInstance 实例变量 ?
    • uniqueInstance = new Singleton(); 这段代码执行时分为三步:
      • 1.为 uniqueInstance 分配内存空间
      • 2.初始化 uniqueInstance
      • 3.将 uniqueInstance 指向分配的内存地址
    • 正常的执行顺序当然是 1>2>3 ,但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。
    • 单线程环境时,指令重排并没有什么问题;多线程环境时,会导致有些线程可能会获取到还没初始化的实例。
    • 例如:线程A 只执行了 1 和 3 ,此时线程B来调用 getUniqueInstance(),发现 uniqueInstance 不为空,便获取 uniqueInstance 实例,但是其实此时的 uniqueInstance 还没有初始化。
    • 解决办法就是加一个 volatile 关键字修饰 uniqueInstance ,volatile 会禁止 JVM 的指令重排,就可以保证多线程环境下的安全运行。
  • 优点:延迟实例化,节约了资源;线程安全;并且相对于 线程安全的懒汉式,性能提高了。
  • 缺点:volatile 关键字,对性能也有一些影响。
     

4、静态内部类实现(线程安全)

public class Singleton { 
    private Singleton() {} 
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    } 
    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE; 
    }
}
  • 说明:
    • 首先,当外部类 Singleton 被加载时,静态内部类 SingletonHolder 并没有被加载进内存。
    • 当调用 getUniqueInstance() 方法时,会运行 return SingletonHolder.INSTANCE;  
    • 触发了 SingletonHolder.INSTANCE ,此时静态内部类 SingletonHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次。
  • 优点:延迟实例化,节约了资源;且线程安全;性能也提高了。
     

5、枚举类实现(线程安全)

public enum Singleton {
    INSTANCE; //添加自己需要的操作 public void doSomeThing() {}
}
  • 说明:默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
  • 优点:写法简单,线程安全,天然防止反射和反序列化调用。
     

防止反序列化

序列化:把java对象转换为字节序列的过程;

反序列化:通过这些字节序列在内存中新建java对象的过程;

也就是说,反序列化 将一个单例实例对象写到磁盘再读回来,从而获得了一个新的实例。

我们要防止反序列化,避免得到多个实例。

其中枚举类天然防止反序列化。

其他单例模式可以通过重写 readResolve() 方法,从而防止反序列化,使实例唯一,重写 readResolve() :

private Object readResolve() throws ObjectStreamException{ 
    return singleton;
}

 

三、Template设计模式

Template模板方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现,

其优缺点为:

  • 优点:使用模板方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
  • 缺点:如果算法骨架有修改的话,则需要修改抽象类

我们用代码来理解一下这个设计模式如何进行使用,

public class Template {
    public static void main(String[] args) {
        forTest f=new forTest();
        System.out.println(f.getTime());
    }
}
 
abstract class GetTime{
    public final long getTime(){//添加final关键字,不允许子类重写该方法
        long start=System.currentTimeMillis();//获取程序运行开始时间
        Code();
        long end=System.currentTimeMillis();//获取结束时间
        return end-start;//返回程序运行时间
    }
 
    public abstract void Code();//定义抽象方法,让子类去实现具体的算法
}
 
class forTest extends GetTime{
 
    @Override
    public void Code() {
        for (int i = 0; i < 100000; i++) {
            System.out.println("*");
        }
    }
}

下面是运行截图:

 

四、模板设计模式

模板方法模式是类的行为模式,

模板设计模式会先定义一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑,

不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。

比如在Servlet类为一个抽象类,定义了service的抽象方法,交给子类去实现,我们自定义一个类继承Servlet的子类HttpServlet类,

HttpServlet类对service方法进行了重写,可以针对客户端不同的请求方式来响应,响应的方法为doXXX,我们对doXXX进行重写,

import javax.servlet.ServletException;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;

public class ServletDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
        System.out.println("doPost");
    }
}

然后部署到tomcat上,进行访问,服务器在创建Servlet时执行的代码为,

Servlet s=new ServletDemo3();
s.service();

因为我们对service方法进行了重写, 所以会自动调用doXXX方法,这样系统就会打印客户端的请求方式了,

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值