DI(依赖注入)设计模式

一. DI(依赖注入)基本概念

DI—Dependency Injection,即“依赖注入”:
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。

Java Dependency Injection设计模式允许我们删除硬编码的依赖关系,并使我们的应用程序松散耦合,可扩展和可维护。我们可以在Java中实现依赖注入,以将依赖解析从编译时移至运行时。

Java依赖注入似乎很难用理论来理解,因此我将举一个简单的例子,然后我们将看到如何使用依赖注入模式来实现应用程序中的松散耦合和可扩展性。

假设我们有一个消费应用程序EmailService来发送电子邮件。通常我们会像下面这样实现。

public class EmailService {
	public void sendEmail(String message, String receiver){
		//发送电子邮件的逻辑
		System.out.println("Email sent to "+receiver+ " with Message="+message);
	}
}

EmailService类包含将电子邮件消息发送到收件人电子邮件地址的逻辑。我们的应用程序代码如下所示。

public class MyApplication {

	private EmailService email = new EmailService();
	
	public void processMessages(String msg, String rec){
		//做一些msg验证,操作逻辑等
		this.email.sendEmail(msg, rec);
	}
}

我们的客户代码将使用MyApplicationclass发送电子邮件,如下所示。

public class MyLegacyTest {
	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("锦瑟无端五十弦", "李商隐@锦瑟");
	}
}

乍一看,上述实现似乎没有错。但是以上代码逻辑有一定的局限性。

MyApplication类负责初始化电子邮件服务,然后使用它。这导致硬编码的依赖性。如果将来想切换到其他高级电子邮件服务,则需要在MyApplication类中更改代码。这使我们的应用程序难以扩展,如果在多个类中使用电子邮件服务,则将更加困难。

如果我们想扩展应用程序以提供附加的消息传递功能,例如SMS或Facebook消息,那么我们需要为此编写另一个应用程序。这将涉及应用程序类和客户端类中的代码更改。

由于我们的应用程序是直接创建电子邮件服务实例,因此测试该应用程序将非常困难。我们无法在测试类中模拟这些对象。

我们可以将MyApplication通过使用需要电子邮件服务作为参数的构造函数来从类中删除电子邮件服务实例的创建。

public class MyApplication {

	private EmailService email = null;

	public MyApplication(EmailService svc) {
		this.email = svc;
	}

	public void processMessages(String msg, String rec){
		//做一些msg验证,操作逻辑等
		this.email.sendEmail(msg, rec);
	}
}

但是在这种情况下,我们要求客户端应用程序或测试类初始化电子邮件服务,这不是一个好的设计决定。

现在让我们看看如何应用Java依赖注入模式来解决上述实现的所有问题。Java中的依赖注入至少需要满足以下条件:

  1. 服务组件应使用基类或接口进行设计。最好选择为服务定义合同的接口或抽象类。
  2. 消费者类应根据服务接口编写。
  3. 先初始化服务的注入器类,然后初始化使用者类。

二. Java依赖注入–服务组件

对于我们的情况,我们可以MessageService声明为服务实现接口。

public interface MessageService {

    void sendMessage(String msg, String rec);
}

现在,我们有实现上述接口的电子邮件和SMS服务。

public class EmailServiceImpl implements MessageService {

    @Override
    public void sendMessage(String msg, String rec) {
        //发送电子邮件的逻辑
        System.out.println("Email sent to " + rec + " with Message=" + msg);
    }
}
public class SMSServiceImpl implements MessageService {

    @Override
    public void sendMessage(String msg, String rec) {
        //发送电子邮件的逻辑
        System.out.println("SMS sent to " + rec + " with Message=" + msg);
    }
}

我们的依赖项注入Java服务已准备就绪,现在我们可以编写我们的使用者类。

三. Java依赖注入–服务使用者

我们不需要具有用于消费者类的基本接口,但是我将具有一个Consumer用于声明消费者类的接口。

public interface Consumer {

	void processMessages(String msg, String rec);
}

消费者类实现如下。

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(MessageService svc){
		this.service=svc;
	}
	
	@Override
	public void processMessages(String msg, String rec){
		//做一些msg验证,操作逻辑等
		this.service.sendMessage(msg, rec);
	}

}

请注意,我们的应用程序类仅在使用服务。它不会初始化导致更好的“关注点分离”的服务。服务接口的使用还使我们能够通过模拟MessageService轻松地测试应用程序,并在运行时而不是在编译时绑定服务。

现在我们准备编写java依赖注入程序类,该类将初始化服务以及使用者类。

四. Java依赖注入–注入器类

让我们有一个MessageServiceInjector带有方法声明的接口,该接口返回Consumer该类。

public interface MessageServiceInjector {

	public Consumer getConsumer();
}

现在,对于每项服务,我们将必须创建如下所示的注入器类。

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new EmailServiceImpl());
	}
}
public class SMSServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new SMSServiceImpl());
	}
}

现在,让我们看看我们的客户端应用程序将如何通过一个简单的程序使用该应用程序。

public class MyMessageDITest {

    public static void main(String[] args) {
        String msg = "锦瑟无端五十弦,一弦一柱思华年。";
        String email = "李商隐@锦瑟.com";
        String phone = "4088888888";
        MessageServiceInjector injector = null;
        Consumer app = null;

        //发送电子邮件
        injector = new EmailServiceInjector();
        app = injector.getConsumer();
        app.processMessages(msg, email);

        //Send SMS
        injector = new SMSServiceInjector();
        app = injector.getConsumer();
        app.processMessages(msg, phone);
    }
}

如您所见,我们的应用程序类仅负责使用服务。服务类别是在注入器中创建的。同样,如果我们必须进一步扩展我们的应用程序以允许Facebook消息传递,我们将只必须编写Service类和注入器类。

因此,依赖项注入实现解决了具有硬编码依赖项的问题,并帮助我们使应用程序灵活且易于扩展。现在让我们看看通过模拟注入器和服务类来测试应用程序类有多么容易。

五. 带有模拟注入器和服务的JUnit测试用例

public class MyDIApplicationJUnitTest {

    private MessageServiceInjector injector;

    @Before
    public void setUp() {
        //使用匿名类模拟注入器
        injector = new MessageServiceInjector() {

            @Override
            public Consumer getConsumer() {
                //模拟消息服务
                return new MyDIApplication(new MessageService() {
                    @Override
                    public void sendMessage(String msg, String rec) {
                        System.out.println("模拟消息服务实现");

                    }
                });
            }
        };
    }

    @Test
    public void test() {
        Consumer consumer = injector.getConsumer();
        consumer.processMessages("锦瑟无端五十弦", "李商隐@锦瑟");
    }

    @After
    public void tear() {
        injector = null;
    }
}

正如你可以看到,我使用匿名类来模拟注入器和服务类,我可以轻松地测试我的应用程序的方法。我在上述测试类中使用的是JUnit 4,因此,如果您在测试类之上运行,请确保它在您的项目构建路径中。

我们使用构造函数将依赖项注入应用程序类中,另一种方法是使用setter方法将依赖项注入应用程序类中。对于setter方法的依赖注入,我们的应用程序类将如下实现。

public class MyDIApplication implements Consumer {

	private MessageService service;
	
	public MyDIApplication(){}

	//setter依赖注入
	public void setService(MessageService service) {
		this.service = service;
	}

	@Override
	public void processMessages(String msg, String rec){
		//做一些msg验证,操作逻辑等
		this.service.sendMessage(msg, rec);
	}
}
public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		MyDIApplication app = new MyDIApplication();
		app.setService(new EmailServiceImpl());
		return app;
	}
}

使用基于构造函数的依赖项注入还是基于setter是设计决策,取决于您的要求。例如,如果没有服务类我的应用程序根本无法工作,那么我宁愿使用基于构造函数的DI,否则我会选择基于setter方法的DI仅在真正需要时才使用它。

Java中的依赖注入是一种通过将对象绑定从编译时移到运行时来在我们的应用程序中实现控制反转(IoC)的方法。我们也可以通过工厂模式,模板方法设计模式,策略模式和服务定位器模式来实现IoC 。

Spring Dependency Injection, Google Guice和Java EE CDI框架通过使用Java Reflection API和Java注释简化了依赖项注入的过程。我们所需要做的就是注释字段,构造函数或设置方法,并在配置xml文件或类中对其进行配置。

六. Java依赖注入的优缺点

Java依赖注入的好处
在Java中使用依赖注入的一些好处是:

  1. 关注点分离
  2. 减少应用程序类中的代码,因为初始化依赖项的所有工作都由注入器组件处理
  3. 可配置的组件使应用程序易于扩展
  4. 使用模拟对象可以轻松进行单元测试

Java依赖注入的缺点
Java依赖注入也有一些缺点:

  1. 如果使用过度,则可能导致维护问题,因为在运行时知道更改的效果。
  2. Java中的依赖关系注入隐藏了服务类依赖关系,这可能导致运行时错误,而这些错误在编译时就会被捕获。

参考链接: DI设计模式示例教程

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

香辣奥利奥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值