聊一聊Java中常见的设计模式

193 阅读11分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

简介

本文主要讲解一些比较常见的,设计起来相对简单,也是大家用的比较多的一些设计模式。希望用最简单的方式去和大家分享,尽可能结合个人实际开发设计上的运用以图解的形式来进行讲解,希望有助于各位开发者更快的去理解。

1.单例模式

image.png

单例模式也就是说一个实例在整个应用在运行中,其实例对象只会在内存中实例化一次,以达到节约内存的目的。单例模式他的构造方法是私有的,这就成为了它的核心。如上图所示我们就需要知道有哪几种方式能够实现单例,也就是说有什么方法可以只实例化一次对象,最终通过getInstance()对外提个出去。那么我们就来分析以下几种方式:

  • 饿汉模式:有饥饿感所以不管我们的实例需不需要使用都会去创建一次,也就是在类初始化完成后就会去实例化一次对象,之后需要使用该对象直接引用即可,所以是线程安全的:
// 最简单的就是直接在静态变量instance初始化时就实例化对象
private static final Singleton INSTANCE = new Singleton();

public static Singleton getInstance() {
    return INSTANCE;
}
  • 懒汉模式:之所以懒,那就不喜欢去做无用功,所以懒汉模式就是在需要的时候才会去实例化对象。在这里我们就得考虑到是否为多线程环境了,如果是单线程环境就不会有多个同时要实例化对象的可能;如果是多线程环境,那就可能出现多线程并发要创建对象,那我们就得采取一些措施来保证对象只会实例化一次,也就是必须保证getInstance()方法是线程安全的:

直接在getInstance()方法加锁(不推荐):

// 此写法简单,但是由于锁粒度较大,高并发情况下极易造成锁升级导致性能下降,所以不推荐此写法
public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

加锁双重校验(推荐): 首先能够在高并发情况下减小锁粒度,两个线程同时第一次校验instance为空,在锁竞争后其中一个线程将对象实例化后,内存中实际已经实例化过一次对象了,另一个线程竞争获得锁后会再次进入重新实例化一次对象,所以这里做了二次验证来保证对象只会实例化一次。

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

静态内部类(强烈推荐):

// 静态内部类只会在使用的时候初始化一次,线程安全
public class Singleton {

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

    private static class SingletonInstance {
        private static Singleton INSTANCE = new Singleton();
    }
}

枚举类实例化(强烈推荐):

// 枚举类实例化只会在使用枚举类的时候初始化一次枚举类构造函数,线程安全
public class Singleton {

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonEnum.INSTANCE.getInst();
    }

    private enum SingletonEnum {
        INSTANCE;
        private Singleton inst;

        SingletonEnum() {
            inst = new Singleton();
        }

        public Singleton getInst() {
            return inst;
        }
    }
}

2.简单工厂

简单工厂这个模式从字面上看我们也知道他就是比较简单,他就是简单到可以通过一个工厂类的方法,根据指定参数类型创建出相应的实例。这些实例类都会继承一个抽象父类,而工厂方法只要返回抽象父类的引用即可。

image.png

如上图所示,我们可以看到CarFactory就是一个汽车工厂类,根据type指定类型去创建对应的汽车实例对象,调用者可以根据个人所需去实现抽象父类对应的抽象方法。

public class CarFactoryDemo {

    public static void main(String[] args) {
        CarFactory factory = new CarFactory();
        factory.generateCar("bmw").run();
        factory.generateCar("audi").run();
        factory.generateCar("benz").run();
    }
}
// 定义汽车工厂类
class CarFactory {
    public AbstractCar generateCar(String type) {
        if (StringUtils.equalsIgnoreCase("bmw", type)) {
            return new CarBmw();
        }
        if (StringUtils.equalsIgnoreCase("benz", type)) {
            return new CarBenz();
        }
        if (StringUtils.equalsIgnoreCase("audi", type)) {
            return new CarAudi();
        }
        return null;
    }
}
// 定义抽象父类
abstract class AbstractCar {
    public abstract void run();
}

// 抽象汽车类的具体实现
class CarBmw extends AbstractCar {

    @Override
    public void run() {
        System.out.println("Bmw 跑起来了");
    }
}

class CarBenz extends AbstractCar {

    @Override
    public void run() {
        System.out.println("Benz 跑起来了");
    }
}

class CarAudi extends AbstractCar {

    @Override
    public void run() {
        System.out.println("Audi 跑起来了");
    }
}

运行结果

image.png

3.抽象工厂模式

抽象工厂模式是指在简单工厂的基础上,对于简单工厂类再进行一次抽象化,相当于是一个能够创建具体工厂类的超级工厂。调用者使用时无需知道具体使用的工厂是哪个,只需专注自己的业务流程几个,通过抽象工厂类的引用对象获取实际生产出来的实体引用去执行任务。

image.png

如上图所示,主要分四类角色,抽象汽车工厂类、实际汽车工厂类、抽象汽车类、汽车实体类,由这四个角色的依赖继承关系就形成了一个抽象工厂模式。CarFactoryProduction即可作为汽车抽象工厂的生产者,这个类可由开发者按个人所需去定义用哪种方式创建汽车工厂类,使用者无需关注汽车工厂如何而来,更无需知道具体的工厂实现。

public class AbstractCarFactoryDemo {

    public static void main(String[] args) {
        // 使用抽象工厂做引用,调用者无需知道实际工厂是哪个
        AbstractCarFactory bmwFactory = CarFactoryProduction.getCarFactory("bmw");
        bmwFactory.productionCar().run();

        AbstractCarFactory benzFactory = CarFactoryProduction.getCarFactory("benz");
        benzFactory.productionCar().run();
    }
}

/**
 * 汽车工厂生产者
 */
class CarFactoryProduction {
    public static AbstractCarFactory getCarFactory(String type) {
        if ("bmw".equalsIgnoreCase(type)) {
            return new BmwCarFactory();
        }
        if ("benz".equalsIgnoreCase(type)) {
            return new BenzCarFactory();
        }
        return null;
    }
}

/**
 * 定义抽象汽车工厂类
 */
abstract class AbstractCarFactory {
    public abstract AbstractCars productionCar();
}

/**
 * 定义宝马汽车工厂实体类
 */
class BmwCarFactory extends AbstractCarFactory {
    @Override
    public AbstractCars productionCar() {
        return new BmwCar();
    }
}

/**
 * 定义奔驰汽车工厂实体类
 */
class BenzCarFactory extends AbstractCarFactory {
    @Override
    public AbstractCars productionCar() {
        return new BenzCar();
    }
}

/**
 * 定义汽车抽象类
 */
abstract class AbstractCars {
    public abstract void run();
}

/**
 * 定义宝马汽车实体类
 */
class BmwCar extends AbstractCars {
    @Override
    public void run() {
        System.out.println("BMW跑一跑");
    }
}

/**
 * 定义奔驰汽车实体类
 */
class BenzCar extends AbstractCars {
    @Override
    public void run() {
        System.out.println("BENZ跑一跑");
    }
}

运行结果

image.png

4.观察者模式

观察者模式其主要实现的是观察一个对象的状态变化,广播通知到所有依赖它的对象。能够让业务逻辑与具体的通知处理进行解耦,只需处理好他们直接的依赖关系即可。

1654498093992.jpg

如上图所示,大致有以下几个角色:观察者接口(NotifyObserver)、具体观察者实现类(EmailNotifyObserver、SmsNotifyObserver、WechatNotifyObserver)、被观察类(NotifySubject)。这是在一个支付系统涉及的一个余额变动通知的开发案例,主要就是将所有可接收通知的渠道进行注册,在余额变动时对各渠道逐个进行发送通知。在将来如果有更多的通知渠道,也比较方便扩展,仅需关注具体观察者接口的实现,以及何时进行渠道的注册,已有的功能不受影响。

public class ObserverDemo {
    public static void main(String[] args) {
        NotifySubject subject = new NotifySubject();
        // 具体观察者注册
        subject.registerObserver(new EmailNotifyObserver());
        subject.registerObserver(new WechatNotifyObserver());
        subject.registerObserver(new SmsNotifyObserver());
        subject.balanceChange(10);
        System.out.println("==========================");
        subject.balanceChange(-5);
    }
}

interface NotifyObserver {
    void sendNotify(String msg);
}

class EmailNotifyObserver implements NotifyObserver {
    @Override
    public void sendNotify(String msg) {
        System.out.println("发送邮件通知->" + msg);
    }
}

class SmsNotifyObserver implements NotifyObserver {
    @Override
    public void sendNotify(String msg) {
        System.out.println("发送短信通知->" + msg);
    }
}

class WechatNotifyObserver implements NotifyObserver {
    @Override
    public void sendNotify(String msg) {
        System.out.println("发送微信公众号通知->" + msg);
    }
}
// 如有需要可接入内部网站通知、支付宝通知等其他有相关业务的平台

class NotifySubject {
    private final List<NotifyObserver> observers = new ArrayList<>();
    private final AtomicInteger balance = new AtomicInteger(0);

    public void registerObserver(NotifyObserver observer) {
        observers.add(observer);
    }

    public void balanceChange(int amount) {
        notifyAllObservers("余额:" + balance.addAndGet(amount));
    }

    private void notifyAllObservers(String message) {
        for (NotifyObserver observer : observers) {
            observer.sendNotify(message);
        }
    }
}

运行结果:

1654498675320.jpg

5.代理模式

代理模式通俗的讲就是一个类代表另一个类去实现相应的功能。在我们现实生活中也有很多常见的代理模式(比如:房产中介、各种品牌代理商),简而言之就是我们无法直接去实现或者实现起来比较繁琐的事就可以通过代理进行处理。在我们最常用的SpringAOP实现也是一种代理模式,它是将具体接口方法的实现做一层代理通过代理类去调用,在代理类调用方法前/后做AOP切面处理。接下来我们来看一下一个具体的实例:

1654504240546.jpg

如上图所示,定义一个第三方支付服务接口(ThirdPartyPayService),具体支付渠道实现类(AlipayService、WxpayService),由于各个支付渠道所接收的参数肯定是不同的,所以就创建了各自对应的代理类(AlipayProxy、WxpayProxy)做相应的参数转换,以及具体的收发日志信息,最终由客户端类(ProxyDemo)统一传输参数通过代理类转换参数后执行具体实现。

public class ProxyDemo {
    public static void main(String[] args) {
        // 此处无论是支付宝/微信支付,接口进来一般都是一样的参数,可以通过代理模式对参数做一轮转换,同时可以记录一些日志信息
        AlipayProxy alipayProxy = new AlipayProxy();
        alipayProxy.pay(10);
        System.out.println("===============================");
        WxpayProxy wxpayProxy = new WxpayProxy();
        wxpayProxy.pay(20);
    }
}

/**
 * 第三方支付服务接口
 */
interface ThirdPartyPayService {
    void pay(int amount);
}

class AlipayService implements ThirdPartyPayService {

    @Override
    public void pay(int amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

/**
 * 支付宝支付代理类
 */
class AlipayProxy {
    private ThirdPartyPayService service = new AlipayService();

    public void pay(int amount) {
        System.out.println("支付宝支付请求参数转换处理");
        // 这里一般会转换成相应支付渠道请求参数对象,此处不做细化
        service.pay(amount);
    }
}

class WxpayService implements ThirdPartyPayService {

    @Override
    public void pay(int amount) {
        System.out.println("微信支付:" + amount);
    }
}

/**
 * 微信支付代理类
 */
class WxpayProxy {
    private ThirdPartyPayService service = new WxpayService();

    public void pay(int amount) {
        System.out.println("微信支付请求参数转换处理");
        // 这里一般会转换成相应支付渠道请求参数对象,此处不做细化
        service.pay(amount);
    }
}

运行结果:

1654503536046.jpg

6.适配器模式

适配器模式即是将两个不兼容的接口做适配,让互不兼容的接口能够进行交互工作。在某些互不兼容的业务当中做适配能够起到很好的作用。

image.png

如上图所示,简单绘制了一下当时做支付对账时使用适配器的方案。适配器模式主要分四种角色:目标抽象类(BillingHandle)、适配者类(AlipayBillingHandle、WxPayBillingHandle)、适配器类(BillingHandleAdapter)、客户端类(BillingHandleService)。由此可见该场景即是账单处理服务无法直接做完整的账单处理,需要根据渠道做适配,从而才能完成相应渠道的账单处理。从而我们也可以很清楚的看到适配器模式它的优点就是方便扩展,如有渠道支付渠道需要处理账单,即可直接通过实现BillingHandle类,紧接着在适配器类增加该渠道的适配即可。通常情况下我们会把不同渠道的支付账单下载下来,再统一存入一个业务表,再进行后续的真实账单处理。

public class AdapterDemo {
    public static void main(String[] args) {
        BillingHandleService service = new BillingHandleService();
        service.downloadBilling("alipay");
        service.downloadBilling("wxpay");
        service.downloadBilling("unionpay");
    }
}

/**
 * 目标抽象接口
 */
interface BillingHandle {
    void downloadBilling();
}

/**
 * 支付宝账单适配者
 */
class AlipayBillingHandle implements BillingHandle {
    @Override
    public void downloadBilling() {
        System.out.println("下载阿里云对账单");
    }
}

/**
 * 微信账单适配者
 */
class WxPayBillingHandle implements BillingHandle {
    @Override
    public void downloadBilling() {
        System.out.println("下载微信对账单");
    }
}

/**
 * 适配器类
 */
class BillingHandleAdapter {
    private BillingHandle billingHandle;
    BillingHandleAdapter(BillingHandle billingHandle) {
        this.billingHandle = billingHandle;
    }

    public void downloadBilling() {
        if (billingHandle == null) {
            return; // error
        }
        billingHandle.downloadBilling();
    }
}

/**
 * 下载账单客户端类
 */
class BillingHandleService {
    public void downloadBilling(String channel) {
        BillingHandleAdapter adapter;
        if ("alipay".equalsIgnoreCase(channel)) {
            adapter = new BillingHandleAdapter(new AlipayBillingHandle());
        }
        else if("wxpay".equalsIgnoreCase(channel)) {
            adapter = new BillingHandleAdapter(new WxPayBillingHandle());
        }
        else {
            System.out.println("未知渠道");
            return; // error
        }
        adapter.downloadBilling();
    }
}

运行结果:

image.png

总结

设计模式是一种辅助开发设计的开发方式,对于开发人员来说,是一个很好的设计思想。设计模式主要遵循六大原则(开闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、迪米特法则、单一职责原则),能够帮开发人员提高代码的可复用率、降低代码的冗余,使代码更加简介可靠。但是与之带来的问题就是设计周期较长,且相对复杂的设计模式可能会导致经验相对薄弱的同学难以读懂,所以不是说设计模式用的多就一定是好的,还是得各个开发人员根据真实的业务场景去合理分析后选择是否使用设计模式以及应该使用哪种设计模式。

设计模式虽好,但请不要贪用哦!