您现在的位置是:首页 >技术教程 >肝一肝设计模式【八】-- 外观模式网站首页技术教程

肝一肝设计模式【八】-- 外观模式

老六说编程 2024-06-17 10:31:55
简介肝一肝设计模式【八】-- 外观模式

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门
肝一肝设计模式【七】-- 代理模式 传送门



前言

本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式、装饰器模式、代理模式,本节我们来学习一下——外观模式。


一、外观模式是什么

外观模式(Facade Pattern),它为复杂子系统提供了一个简单的接口,使得客户端可以更容易地使用该子系统,同时避免了直接访问子系统可能带来的复杂性。

外观模式的核心思想是为子系统提供一个高层次的接口,以便客户端可以通过这个接口访问子系统的功能,而无需了解子系统的内部实现细节。外观模式封装了子系统的复杂性,提供了一个简单的界面,使得客户端可以更加方便地使用子系统的功能。

二、外观模式中的角色

通常情况下,外观模式的实现需要创建一个外观类,该类将客户端的请求委派给子系统中的相应对象进行处理。外观类对客户端隐藏了子系统的复杂性,同时还可以为子系统提供额外的功能,以满足客户端的需求。

外观模式主要由三个角色:

  • 外观(Facade):外观是外观模式的核心,它封装了子系统的复杂性,并为客户端提供一个简单的接口。外观类通常包含一个或多个操作方法,这些方法将客户端的请求委派给子系统中的相应对象进行处理。外观类还可以为客户端提供一些额外的功能,以便满足客户端的需求。
  • 子系统(Subsystem):子系统是外观模式中的核心组成部分,它是指实现系统功能的一组类。子系统包含一些相互关联的类,这些类共同实现了系统的一些功能。在外观模式中,客户端通常不会直接访问子系统中的类,而是通过外观类来访问子系统。
  • 客户端(Client):客户端是使用外观模式的应用程序的组成部分。客户端通过外观类来访问子系统中的功能,从而实现系统的需求。客户端并不了解子系统的内部实现细节,它只需要调用外观类中的方法即可完成所需功能。

三、举个栗子

又到了举例子环节,比如单独一个人去医院看病,往往会因为挂号、化验、取报告、收费、取药等这些流程搞得焦头烂额,当你刚刚花了半个小时排完队交完费,转头看到取药窗口的长队时,当下的我崩溃如下图。。在这里插入图片描述
实际生活中像我经历的这种情况不在少数,而这就催生出一种服务叫陪诊服务,用户只需要专注看病这件事就行了,其他都交给陪诊员,这种形式的服务就和程序中外观模式的概念就很类似了

上代码:

我们把取报告、交费、取药这几个步骤当做子系统

public class StepA {
	public void getReport() {
		System.out.println("帮我取化验报告");
	}
}
public class StepB {
	public void pay() {
		System.out.println("帮我去交费");
	}
}
public class StepC {
	public void getMedicine() {
		System.out.println("帮我把药取回来");
	}
}

陪诊员相当于外观角色

public class AccompanyFacade {

	private StepA stepA;
    private StepB stepB;
    private StepC stepC;

    public AccompanyFacade() {
        stepA = new StepA();
        stepB = new StepB();
        stepC = new StepC();
    }
	public void work() {
		stepA.getReport();
		stepB.pay();
		stepC.getMedicine();
	}
}

客户端调用

public class FacadeClient {
    public static void main(String[] args) {
        AccompanyFacade facade= new AccompanyFacade();
        facade.work();
    }
}

在这里插入图片描述

四、在开源框架中的使用

SLF4J(Simple Logging Facade for Java)是一个常用的日志框架,它使用了外观模式来封装不同的日志实现库,提供了统一的接口供开发人员使用。SLF4J允许开发人员在代码中使用一致的日志API,而无需关注底层的日志实现细节。

SLF4J的核心组件是Logger接口,它定义了常见的日志方法,如debug()、info()、error()等。开发人员可以通过获取Logger实例并调用这些方法来记录日志。

SLF4J的外观模式实现的关键在于它提供了适配器(Adapter)和桥接(Bridge)两个重要的概念:

  • 适配器(Adapter):SLF4J提供了适配器模块,例如slf4j-jdk14、slf4j-log4j12等,用于适配不同的日志实现库,将它们转换为SLF4J接口的调用。这样,开发人员可以使用SLF4J的接口,而实际的日志实现则由相应的适配器来负责。

  • 桥接(Bridge):SLF4J还提供了桥接模块,例如jul-to-slf4j、log4j-to-slf4j等,用于将其他日志库的日志输出重定向到SLF4J接口。这样,即使在使用其他日志库的应用中,开发人员仍然可以通过SLF4J接口来统一管理日志。

通过适配器和桥接的机制,SLF4J实现了外观模式,封装了不同的日志实现库,提供了统一的日志接口供开发人员使用。这样,开发人员可以轻松切换和管理日志库,而无需修改大量的代码。

SLF4J本身只提供了日志接口和相关的适配器和桥接模块,实际的日志输出仍需要依赖具体的日志实现库,例如Logback、Log4j等。开发人员需要在项目中同时引入SLF4J和所选的日志实现库。

以下是SLF4J的部分源代码,展示了它的外观模式实现:

Logger接口:

public interface Logger {
    void debug(String message);
    void info(String message);
    void error(String message);
    // ...
}

LoggerFactory类:

public final class LoggerFactory {
    private LoggerFactory() {}

    public static Logger getLogger(Class<?> clazz) {
        // 获取适配器实例
        ILoggerFactory loggerFactory = getILoggerFactory();
        return loggerFactory.getLogger(clazz.getName());
    }

    private static ILoggerFactory getILoggerFactory() {
        // 返回适配器实例
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    }
}

StaticLoggerBinder类:

public class StaticLoggerBinder {
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    private final ILoggerFactory loggerFactory = new MyLoggerFactory();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }
}

适配器实现:

public class Log4jLoggerFactory implements ILoggerFactory {
    public Logger getLogger(String name) {
        return new Log4jLogger(org.apache.log4j.Logger.getLogger(name));
    }
}

在上述代码中,Logger接口定义了常见的日志方法,LoggerFactory类是获取Logger实例的工厂类。LoggerFactory通过调用getILoggerFactory()方法获取适配器实例,然后返回相应的Logger实例。

StaticLoggerBinder类是一个单例类,提供了getLoggerFactory()方法来获取适配器实例。在示例中,适配器实现为Log4jLoggerFactory,它实现了ILoggerFactory接口,并通过Log4j库来实现日志功能。

这样,当开发人员通过LoggerFactory.getLogger()获取Logger实例时,SLF4J会根据适配器的实现来选择相应的日志实现库,然后返回封装好的Logger实例。开发人员可以直接使用Logger接口的方法来记录日志,而无需关心具体的日志实现细节。


写在最后

外观模式的优点:

  • 简化接口:外观模式通过提供一个统一的接口,将底层子系统的复杂性封装起来。这样,使用者只需要与外观对象交互,无需了解底层子系统的具体实现细节,简化了接口和调用过程。

  • 提供易用性:外观模式可以为使用者提供一个简单而易用的接口,降低了学习和使用系统的难度。使用者无需深入了解系统的内部结构和复杂性,可以更快地上手和开发。

  • 解耦和降低依赖:外观模式将系统的子系统和使用者之间进行解耦,通过外观对象进行交互。这样,子系统的变化不会影响到使用者,使用者只需要关注外观对象的接口,减少了依赖关系。

  • 提供灵活性和可维护性:通过外观模式,可以隔离系统的变化和演化。当底层子系统发生变化时,只需要调整外观对象的实现,而不需要修改使用者的代码。这提供了灵活性和可维护性,降低了系统的耦合度。

外观模式的缺点:

  • 违背开闭原则:外观模式的一个潜在缺点是当系统的需求变化时,可能需要修改外观对象的接口或实现。这可能导致外观对象的修改,违背了开闭原则。但通常情况下,外观模式的修改范围相对较小,不会对系统的其他部分产生太大影响。

  • 可能引入性能问题:在某些情况下,外观模式可能会引入性能问题。由于外观对象需要处理复杂的子系统调用,可能会导致一定的性能开销。但在大多数情况下,这种开销是可以接受的,并且可以通过优化和缓存等技术进行改进。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。