设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
服务定位器模式
服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。 服务定位器模式被认为是一种反面模式! 服务定位器模式被一些人认为是一种反面模式。它违反了依赖倒置原则。该模式隐藏类的依赖,而不是暴露依赖(如果暴露可通过依赖注入的方式注入依赖)。当某项服务的依赖发生变化时,使用该服务的类的功能将面临被破坏的风险,最终导致系统难以维护。 ## 目的 服务定位器模式能够降低代码的耦合度,以便获得可测试、可维护和可扩展的代码。**DI 模式和服务定位器模式是 IOC 模式的一种实现**。 ## 用法 服务定位模式(Service Locator Pattern)是一种软件开发中的设计模式,可对涉及尝试获取一个服务的过程进行封装。该模式使用一个称为 "Service Locator" 的中心注册表来处理请求并返回处理特定任务所需的必要信息。 使用 ServiceLocator ,你可以为给定的 interface 注册一个服务。通过使用这个 interface,你不需要知道该服务的实现细节,就可以获取并在你应用中使用该服务。你可以在引导程序中配置和注入服务定位器对象。 在首次请求某个服务时,服务定位器会查找服务,没有已经存在的服务,会新建并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。 ## 作者:richy_ 链接:https://www.jianshu.com/p/33ea3da8a5a2 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ## JNDI ### 什么是 JNDI JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一。 > JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。 > > JNDI可访问的现有的目录及服务有: > DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。 ### 问题 未使用 JNDI 的 MySQL JDBC 驱动程序类: ```java Connection conn=null; try { Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader()); conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=xxx&password=xxx"); ...... conn.close(); } catch(Exception e) { e.printStackTrace(); } finally { if(conn!=null) { try { conn.close(); } catch(SQLException e) {} } } ``` 没有 JNDI 的做法存在的问题: 1. 数据库服务器名称 MyDBServer 、用户名和口令都可能需要改变,由此引发 JDBC URL 需要修改; 2. 数据库可能改用别的产品,如改用 DB2 或者 Oracle,引发 JDBC 驱动程序包和类名需要修改; 3. 随着实际使用终端的增加,原配置的连接池参数可能需要调整;...... ### 解决方法 在 J2EE 容器中配置 JNDI 参数,定义一个数据源,也就是 JDBC 引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。 具体操作如下(以 JBoss 为例): **1、配置数据源** 在 JBoss 的 `D:/jboss420GA/docs/examples/jca` 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 `mysql-ds.xml` 文件 Copy 到你使用的服务器下。 修改 `mysql-ds.xml` 文件的内容,使之能通过 JDBC 正确访问你的 MySQL 数据库,如下: ```xml <?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>MySqlDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/lw</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>rootpassword</password> <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources> ``` 这里,定义了一个名为 MySqlDS 的数据源,其参数包括 JDBC 的 URL,驱动类名,用户名及密码等。 **2、在程序中引用数据源:** ![在程序中引用数据源](/media/202203/2022-03-18_1334170.6793677808084353.png) ```java Connection conn=null; try { Context ctx = new InitialContext(); Object datasourceRef = ctx.lookup("java:MySqlDS"); //引用数据源 DataSource ds = (Datasource) datasourceRef; conn = ds.getConnection(); ...... c.close(); } catch(Exception e) { e.printStackTrace(); } finally { if(conn!=null) { try { conn.close(); } catch(SQLException e) {} } } ``` 直接使用 JDBC 或者通过 JNDI 引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体 JDBC 参数了。 在系统部署后,如果数据库的相关参数变更,只需要重新配置 `mysql-ds.xml` 修改其中的 JDBC 参数,只要保证数据源的名称不变,那么程序源代码就无需修改。 由此可见,JNDI 避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。 J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— **J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制**。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。 在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。 ## 结构 我们将创建 *ServiceLocator* 、 *InitialContext* 、 *Cache* 、*Service* 作为表示实体的各种对象。*Service1* 和 *Service2* 表示实体服务。 *ServiceLocatorPatternDemo* 类在这里是作为一个客户端,将使用 *ServiceLocator* 来演示服务定位器设计模式。 ![服务定位器模式的 UML 图](https://www.runoob.com/wp-content/uploads/2014/08/20201015-service-locator.svg) 以下是这种设计模式的实体。 * **服务(Service)** - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。 * **Context / 初始的 Context** - JNDI Context 带有对要查找的服务的引用。 * **服务定位器(Service Locator)** - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。 * **缓存(Cache)** - 缓存存储服务的引用,以便复用它们。 * **客户端(Client)** - Client 是通过 ServiceLocator 调用服务的对象。 ## 优点 * 把类与依赖项解耦,从而使这些依赖项可被替换或者更新。 * 类在编译时并不知道依赖项的具体实现。 * 类的隔离性和可测试性非常好。 * 类无需负责依赖项的创建、定位和管理逻辑。 * 通过将应用程序分解为松耦合的模块,达成模块间的无依赖开发、测试、版本控制和部署。 ## 注意事项 在使用 Service Locator 模式之前,请考虑以下几点: * 有很多程序中的元素需要管理。 * 在使用之前必须编写额外的代码将服务的引用添加到服务定位器。 * 类将对服务定位器有依赖关系。 * 源代码变的更加复杂和难以理解。 * 可以使用配置数据来定义运行时的关系。 * 必须提供服务的实现。因为服务定位器模式将服务消费者与服务提供者解耦,它可能需要提供额外的逻辑。这种逻辑将保证在服务消费者尝试定位服务之前,服务提供者已被安装和注册。 ## 相关模式 * 依赖注入(Dependency Injection)。这种模式解决了与 Service Locator 模式相同的问题,但它使用不同的方法。 * 控制反转(Inversion of Control)。Service Locator 模式是这种模式的特殊版本。它将应用程序的传统控制流程反转。它用被调用对象来代替控制过程的调用方。 ## 示例代码 ### Java ```java public interface Service { public String getName(); public void execute(); } public class Service1 implements Service { public void execute(){ System.out.println("Executing Service1"); } @Override public String getName() { return "Service1"; } } public class Service2 implements Service { public void execute(){ System.out.println("Executing Service2"); } @Override public String getName() { return "Service2"; } } public class InitialContext { public Object lookup(String jndiName){ if(jndiName.equalsIgnoreCase("SERVICE1")){ System.out.println("Looking up and creating a new Service1 object"); return new Service1(); }else if (jndiName.equalsIgnoreCase("SERVICE2")){ System.out.println("Looking up and creating a new Service2 object"); return new Service2(); } return null; } } public class Cache { private List<Service> services; public Cache(){ services = new ArrayList<Service>(); } public Service getService(String serviceName){ for (Service service : services) { if(service.getName().equalsIgnoreCase(serviceName)){ System.out.println("Returning cached "+serviceName+" object"); return service; } } return null; } public void addService(Service newService){ boolean exists = false; for (Service service : services) { if(service.getName().equalsIgnoreCase(newService.getName())){ exists = true; } } if(!exists){ services.add(newService); } } } public class ServiceLocator { private static Cache cache; static { cache = new Cache(); } public static Service getService(String jndiName){ Service service = cache.getService(jndiName); if(service != null){ return service; } InitialContext context = new InitialContext(); Service service1 = (Service)context.lookup(jndiName); cache.addService(service1); return service1; } } public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); } } ``` ### PHP ```php <?php namespace DesignPatterns\More\ServiceLocator; class ServiceLocator { /** * @var array */ private $services = []; /** * @var array */ private $instantiated = []; /** * @var array */ private $shared = []; /** * 相比在这里提供一个类,你也可以为接口存储一个服务。 * * @param string $class * @param object $service * @param bool $share */ public function addInstance(string $class, $service, bool $share = true) { $this->services[$class] = $service; $this->instantiated[$class] = $service; $this->shared[$class] = $share; } /** * 相比在这里提供一个类,你也可以为接口存储一个服务。 * * @param string $class * @param array $params * @param bool $share */ public function addClass(string $class, array $params, bool $share = true) { $this->services[$class] = $params; $this->shared[$class] = $share; } public function has(string $interface): bool { return isset($this->services[$interface]) || isset($this->instantiated[$interface]); } /** * @param string $class * * @return object */ public function get(string $class) { if (isset($this->instantiated[$class]) && $this->shared[$class]) { return $this->instantiated[$class]; } $args = $this->services[$class]; switch (count($args)) { case 0: $object = new $class(); break; case 1: $object = new $class($args[0]); break; case 2: $object = new $class($args[0], $args[1]); break; case 3: $object = new $class($args[0], $args[1], $args[2]); break; default: throw new \OutOfRangeException('Too many arguments given'); } if ($this->shared[$class]) { $this->instantiated[$class] = $object; } return $object; } } ```
追风者
2022年3月18日 14:05
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码