常用设计模式

129 阅读9分钟

1.单例模式

1.1饿汉式

  • 在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。
/**
 * @Author 振帅
 * @Date 2021/05/28 2:02
 */
public class Singleton {

    //1.声明本类类型的引用指向本类类型的对象 并使用private static 修饰
    private static  Singleton instence = new Singleton();
    //2.私有化构造方法
    private  Singleton(){ }
    //3.提供公有的get方法,将上述对象返回出去 并使用 static 修饰
    public static Singleton getInstance(){
        return instence;
    }

}

1.2普通懒汉式

  • 线程不安全,不可用
  • 这是懒汉式中最简单的一种写法,只有在方法第一次被访问时才会实例化,达到了懒加载的效果。但是这种写法有个致命的问题,就是多线程的安全问题。假设对象还没被实例化,然后有两个线程同时访问,那么就可能出现多次实例化的结果,所以这种写法不可采用。
/**
 * @Author 振帅
 * @Date 2021/05/28 2:12
 * 普通的懒汉式 (线程不安全,不可用)
 */
public class Singleton {

    //1.声明本类类型的引用指向本类类型的对象 并使用private static 修饰
    private static  Singleton instence = null;
    //2.私有化构造方法
    private  Singleton(){ }
    //3.提供公有的get方法,将上述对象返回出去 并使用 static 修饰
    public static Singleton getInstance(){
        if (instence == null) {
            instence = new Singleton();
        }
        return instence;
    }

}

1.3线程同步懒汉式

  • 每个线程访问都要进行同步,效率低下。
/**
 * @Author 振帅
 * @Date 2021/05/28 2:12
 * 普通的懒汉式 (可用)
 */
public class Singleton {

    //1.声明本类类型的引用指向本类类型的对象 并使用private static 修饰
    private static  Singleton instence = null;
    //2.私有化构造方法
    private  Singleton(){ }
    //3.提供公有的get方法,将上述对象返回出去 并使用 static 修饰
    public static /*synchronized*/ Singleton getInstance(){
        synchronized (Singleton.class){
            if (instence == null) {
                instence = new Singleton();
            }
            return instence;
        }
    }

}

1.4线程同步双重检查懒汉式

  • 对象还没实例化,两个线程A和B同时访问静态方法并同时运行到第一个if判断语句,这时线程A先进入同步代码块中实例化对象,结束之后线程B也进入同步代码块,如果没有第二个if判断语句,那么线程B也同样会执行实例化对象的操作了。
  • 不需要每个线程都进行同步。
/**
 * @Author 振帅
 * @Date 2021/05/28 2:12
 * 双重检查懒汉式 (推荐)
 */
public class Singleton {

    //1.声明本类类型的引用指向本类类型的对象 并使用private static 修饰
    private static  Singleton instence = null;
    //2.私有化构造方法
    private  Singleton(){ }
    //3.提供公有的get方法,将上述对象返回出去 并使用 static 修饰
    public static /*synchronized*/ Singleton getInstance(){
        if (instence == null) {
            synchronized (Singleton.class){
                if (instence == null) {
                    instence = new Singleton();
                }
            }
        }
        return instence;
    }

}

1.5静态内部类懒汉式

  • 内部类是延时加载的,也就是说只会在第一次使用时加载。不使用就不加载,所以可以很好的实现单例模式。
  • 根据内部类不会在其外部类被加载的同时被加载的事实,我们可以引申出单例模式的一种实现方式: 静态内部类
  • 这种静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成对象的实例化。
  • 同时,因为类的静态属性只会在第一次加载类的时候初始化,也就保证了SingletonInstance中的对象只会被实例化一次,并且这个过程也是线程安全的。
/**
 * @Author 振帅
 * @Date 2021/05/28 2:12
 * 静态内部类懒汉式 (推荐)
 */
public class Singleton {

    //1.私有化构造方法
    private  Singleton(){ }

    //2.静态内部类
    static class SingletonInstance {
        private static Singleton instence = new Singleton();

    }
    //3.提供公有的get方法,将上述对象返回出去 并使用 static 修饰
    public static  Singleton getInstance(){
        return SingletonInstance.instence;
    }

}

2.工厂模式

2.1普通工厂模式

  • Sender.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:29
 */
public interface Sender {
    void send();
}
  • MailSender.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:30
 */
public class MailSender implements Sender{
    @Override
    public void send() {
        System.out.println("正在发送邮件...");
    }
}
  • SmsSender.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:31
 */
public class SmsSender implements Sender {
    @Override
    public void send() {
        System.out.println("正在发送短信...");
    }
}
  • SenderFactory.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:32
 */
public class SenderFactory {

    public Sender produce(String type) {
        if ("mail".equals(type)) {
            return new MailSender();
        }
        if ("sms".equals(type)) {
            return new SmsSender();
        }
        return null;
    }
}
  • SenderFactoryTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:34
 */
public class SenderFactoryTest {

    public static void main(String[] args) {
        SenderFactory senderFactory = new SenderFactory();
        Sender sender = senderFactory.produce("mail");
        sender.send();
    }

}

主要缺点

在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,并且可能出现空指针异 常。

2.2多个工厂方法模式

  • SenderFactory.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:32
 */
public class SenderFactory {

    public Sender produceMail() {
        return new MailSender();
    }

    public Sender produceSms() {
        return new SmsSender();
    }
}
  • SenderFactoryTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:34
 */
public class SenderFactoryTest {

    public static void main(String[] args) {
        SenderFactory senderFactory = new SenderFactory();
        Sender sender = senderFactory.produceSms();
        sender.send();
    }

}

主要缺点

在多个工厂方法模式中,为了能够正确创建对象,先需要创建工厂类的对象才能调用工厂类中的生 产方法。

2.3静态工厂方法模式

  • SenderFactory.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:32
 */
public class SenderFactory {

    public static Sender produceMail() {
        return new MailSender();
    }

    public static Sender produceSms() {
        return new SmsSender();
    }

}
  • SenderFactoryTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:34
 */
public class SenderFactoryTest {

    public static void main(String[] args) {
        Sender sender = SenderFactory.produceSms();
        sender.send();
    }

}
  • 实际意义

工厂方法模式适合:凡是出现了大量的产品需要创建且具有共同的接口时,可以通过工厂方法模式 进行创建。

  • 主要缺点

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序生产新的产 品,就必须对工厂类的代码进行修改,这就违背了开闭原则

2.4抽象工厂模式

  • Provider.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:55
 * 自定义抽象方法描述产品的生产行为
 */
public interface Provider {
    Sender produce();
}
  • SmsSenderFactory.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:57
 */
public class SmsSenderFactory implements Provider{
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}
  • MailSenderFactory.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:57
 */
public class MailSenderFactory implements Provider{
    @Override
    public Sender produce() {
        return new MailSender();
    }
}
  • SenderFactoryTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 20:34
 */
public class SenderFactoryTest {

    public static void main(String[] args) {
        MailSenderFactory mailSenderFactory = new MailSenderFactory();
        Sender sender = mailSenderFactory.produce();
        sender.send();
    }

}

与工厂方法模式对比

  • 工厂方法:一个工厂两个方法
  • 抽象工厂:两个工厂各自一个方法

3.装饰器模式

基本概念

装饰器模式就是给一个对象动态的增加一些新功能,要求装饰对象和被装饰对象实现同一个接口, 装饰对象持有被装饰对象的实例。

  • Sourceable.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:12
 * 提供方法的接口
 */
public interface Sourceable {

    void method();
    
}
  • Source.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:13
 * 原来的实现
 */
public class Source implements Sourceable {
    @Override
    public void method() {
        System.out.println("原来的方法");
    }
}
  • Decorator.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:15
 * 装饰器实现
 */
public class Decorator implements Sourceable{

    Sourceable sourceable;

    public Decorator(Sourceable sourceable) {
        this.sourceable = sourceable;
    }

    @Override
    public void method() {
        sourceable.method();//保证原来的方法不变
        System.out.println("装饰方法");
    }
}
  • SourceTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:13
 */
public class SourceTest {
    public static void main(String[] args) {
        //装饰前的方法调用
        Sourceable sourceable = new Source();
        sourceable.method();
        //装饰后的方法调用
        Sourceable sourceable1 = new Decorator(sourceable);
        sourceable1.method();
    }
}

实际意义

  • 可以实现一个类功能的扩展。
  • 可以动态的增加功能,而且还能动态撤销(继承不行)。
  • 缺点:产生过多相似的对象,不易排错。

4.代理模式

基本概念

代理模式就是找一个代理类替原对象进行一些操作。

  • Proxy.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:34
 */
public class Proxy implements Sourceable{

    Source source;

    public Proxy(){
        this.source = new Source();
    }

    @Override
    public void method() {
        source.method();//调用原来的方法
        System.out.println("proxy method");
    }
}
  • SourceTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:13
 */
public class SourceTest {
    public static void main(String[] args) {
        //装饰前的方法调用
        Sourceable sourceable = new Source();
        sourceable.method();
        //装饰后的方法调用
        Sourceable sourceable1 = new Decorator(sourceable);
        sourceable1.method();
        //代理模式
        Sourceable proxy = new Proxy();
        proxy.method();
    }
}

实际意义

  • 如果在使用的时候需要对原有的方法进行改进,可以采用一个代理类调用原有方法,并且对产生的 结果进行控制,这种方式就是代理模式。
  • 使用代理模式,可以将功能划分的更加清晰,有助于后期维护。

代理模式和装饰器模式的比较

  • 装饰器模式通常的做法是将原始对象作为一个参数传给装饰者的构造器,而代理模式通常在一个代 理类中创建一个被代理类的对象。
  • 装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。

5.模板方法模式

基本概念

模板方法模式主要指一个抽象类中封装了一个固定流程,流程中的具体步骤可以由不同子类进行不 同的实现,通过抽象类让固定的流程产生不同的结果。

  • AbstractCalculator.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:52
 * 抽象计算器
 */
public abstract class AbstractCalculator {

    /**
     * 计算传入的表达式 1+1
     * @param exp 表达式
     * @param op 分割符号
     * @return 结果
     */
    public int splitExepression(String exp, String op){
        String[] split = exp.split(op);
        return calculate(Integer.parseInt(split[0]),Integer.parseInt(split[1]));
    };

    /**
     * 计算方法
     * @param a
     * @param b
     * @return
     */
    public abstract int calculate(int a, int b);

}
  • Plus.java
/**
 * @Author 振帅
 * @Date 2021/05/31 21:57
 * 加法计算器
 */
public class Plus extends AbstractCalculator {
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
}
  • AbstractCalculatorTest.java
/**
 * @Author 振帅
 * @Date 2021/05/31 22:00
 */
public class AbstractCalculatorTest {

    public static void main(String[] args) {
        AbstractCalculator calculator = new Plus();
        int result = calculator.splitExepression("2+2", "\\+");
        System.out.println(result);
    }
}

实际意义

将多个子类共有且逻辑基本相同的内容提取出来实现代码复用。 不同的子类实现不同的效果形成多态,有助于后期维护。