如何在Java中利用IOC控制反转
这篇文章给大家介绍如何在Java中利用IOC控制反转,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
Java的特点有哪些
Java的特点有哪些1.Java语言作为静态面向对象编程语言的代表,实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。2.Java具有简单性、面向对象、分布式、安全性、平台独立与可移植性、动态性等特点。3.使用Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。
IOC范式揭秘
控制反转是一种带有某些特征的模式。下面,给出了由Martin Fowler给出的一个IOC经典范例,该范例实现的功能是从控制台中收集用户数据。
public static void main(String[] args) { while (true) { BufferedReader userInputReader = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Please enter some text: "); try { System.out.println(userInputReader.readLine()); } catch (IOException e) { e.printStackTrace(); } }}
这个用例中,在main方法中进行流程控制:在无限循环调用中,读取用户输入,并将读取的内容输出到控制台上。完全又main方法控制何时去读取用户输入,何时去输出。
考虑下,上述程序的一个新版本,该版本中需要通过图形界面中的文本框来收件用户输入,另外还有个按钮,该按钮上绑定有一个action监听器。这样的话,用户每次点击按钮,输入的文本由监听器收集并打印到面板。
在这个版本的程序中,它实际上是由事件监听器模型(在这种情况下,这是框架)的控制下,调用开发者编写的用于读取和打印用户输入的代码。简单地说,框架将调用开发人员的代码,而不是其他方式。该框架实际上是一个可扩展的结构,它为开发人员提供了一组注入自定义代码段的切入点。
这种情况下,控制已经被有效的反转了。
从更通用的角度来看,由框架定义的每个可调用扩展点(以接口实现,实现继承(也称为子类)的形式)是IoC的一种明确定义的形式。
看下,下述这个简单的Servlet例子:
public class MyServlet extends HttpServlet { protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } }
此处,HttpServlet类(属于框架)是完全控制程序的元素,而不是MyServlet这个子类。在由servlet容器创建之后,当收到servlet的GET和POST的HTTP请求,doGet()和doPost()方法中的代码会分别自动调用。
与典型的继承方式相比,即子类是控制的元素,而不是基类,该例中,控件已经被反转了。
事实上,servlet的方法是模板方法模式的实现,稍后我们再深入讨论。
使用那些通过提供可扩展API,秉承开闭原则的框架时,使用框架的开发人员的角色,最终被归结为定义自己的一组自定义类,即开发人员要么通过实现框架提供的一个或多个接口方式,要么通过继承现有基类的方式。反过来,类的实例却是直接框架进行实例化,并且这些事例是被框架调用的。
此处引用Fowler的话:该框架调用开发人员,而不是开发人员调用该框架。因此,IoC通常被称为好莱坞原则:不要打电话给我们,我们会打电话给你。
IOC的实现方式
该问题上,显而易见的是,实现控制反转是有几种不同方法的。我们不妨来总结一下,那些常见的实现方式。
注入依赖实现IOC
如前所述,注入依赖是IOC的一种实现方式,而且是最常见的一种面向对象设计方式。但是,思考一下:注入依赖究竟是如何达到控制反转效果的呢?
为了回答这个问题,我们给出如下一个原始的例子:
public interface UserQueue { void add(User user); void remove(User user); User get(); } public abstract class AbstractUserQueue implements UserQueue { protected LinkedList<User> queue = new LinkedList<>(); @Override public void add(User user) { queue.addFirst(user); } @Override public void remove(User user) { queue.remove(user); } @Override public abstract User get(); } public class UserFifoQueue extends AbstractUserQueue { public User get() { return queue.getLast(); } } public class UserLifoQueue extends AbstractUserQueue { public User get() { return queue.getFirst(); } }
UserQueue 接口定义了公共的API,用于在一个队列中去存放User对象(为了简单明了,此处忽略User的具体实现)。AbstractUserQueue则是为后续的继承类,提供了一些公用的方法实现。最后的UserFifoQueue 和 UserLifoQueue,则是分别实现了FIFO 和 LIFO 队列。
这是,实现子类多态性的一种有效方式。但是这具体用什么来买我们好处呢?实际上,好处还是蛮多的。
通过创建一个依赖于UserQueue抽象类型(也称为DI术语中的服务)的客户端类,可以在运行时注入不同的实现,无需会重构使用客户端类的代码:
public class UserProcessor { private UserQueue userQueue; public UserProcessor(UserQueue userQueue) { this.userQueue = userQueue; } public void process() { // process queued users here } }
UserProcessor展示了,注入依赖确实是IOC的一种方式。
我们可以通过一些硬编码方式 如 new 操作,直接在构造函数中实例化在UserProcessor中获取对队列的依赖关系。但是,这是典型的代码硬编程,它引入了客户端类与其依赖关系之间的强耦合,并大大降低了可测性。耳边警钟声声想起啦!不是吗?是的,这样设计真的很挫。
该类在构造函数中声明对抽象类 UserQueue 的依赖。也就是说,依赖关系不再通过 在构造函数中使用 new 操作, 相反,通过外部注入的方式,要么使用依赖注入框架(如CDI和谷歌的Guice),要么使用factory或builders模式。
简而言之,使用DI,客户端类的依赖关系的控制,不再位于这些类中;而是在注入器中进行:
public static void main(String[] args) { UserFifoQueue fifoQueue = new UserFifoQueue(); fifoQueue.add(new User("user1")); fifoQueue.add(new User("user2")); fifoQueue.add(new User("user3")); UserProcessor userProcessor = new UserProcessor(fifoQueue); userProcessor.process();}
上述方式达到了预期效果,而且对UserLifoQueue的注入也简单明了。显而易见,DI确实是实现IOC的一种方式(该例中,DI是实现IOC的一个中间层)。
观察者模式实现IOC
直接通过观察者模式实现IOC,也是一种常见的直观方式。广义上讲,通过观察者实现IOC,与前文提到的通过GUI界面中的action监听器方式类似。但是在使用action监听器情况下,只有在特定的用户事件发生时(点击鼠标,键盘或窗口事件等),才会发生调用。观察者模式通常用于在模型视图的上下文中,跟踪模型对象的状态的变迁。
在一个典型的实现中,一到多个观察者绑定到可观察对象(也称为模式术语中的主题),例如通过调用addObserver方法进行绑定。一旦定义了被观察者和观察者之间的绑定,则被观察者状态的变迁都会触发调用观察者的操作。
为了深入了解这个概念,给出如下例子:
@FunctionalInterfacepublic interface SubjectObserver { void update(); }
值发生改变时,会触发调用上述这个很简单的观察者。真实情况下,通常会提供功能更丰富的API,如需要保存变化的实例,或者新旧值,但是这些都不需要观察action(行为)模式,所以这里举例尽量简单。
下面,给出一个被观察者类:
public class User { private String name; private List<SubjectObserver> observers = new ArrayList<>(); public User(String name) { this.name = name; } public void setName(String name) { this.name = name; notifyObservers(); } public String getName() { return name; } public void addObserver(SubjectObserver observer) { observers.add(observer); } public void deleteObserver(SubjectObserver observer) { observers.remove(observer); } private void notifyObservers(){ observers.stream().forEach(observer -> observer.update()); }}
User类中,当通过setter方法变更其状态事,都会触发调用绑定到它的观察者。
使用主题观察者和主题,以下是实例给出了观察方式:
public static void main(String[] args) { User user = new User("John"); user.addObserver(() -> System.out.println( "Observable subject " + user + " has changed its state.")); user.setName("Jack");}
每当User对象的状态通过setter方法进行修改时,观察者将被通知并向控制台打印出一条消息。到目前为止,给出了观察者模式的一个简单用例。不过,通过这个看似简单的用例,我们了解到在这种情况下控制是如何实现反转的。
观察者模式下,主题就是起到”框架层“的作用,它完全主导何时何地去触发谁的调用。观察者的主动权被外放,因为观察者无法主导自己何时被调用(只要它们已经被注册到某个主题中的话)。这意味着,实际上我们可以发现控制被反转的”事发地“ – - – 当观察者绑定到主题时:
user.addObserver(() -> System.out.println( "Observable subject " + user + " has changed its state."));
上述用例,简要说明了为什么,观察者模式(或GUI驱动环境中的action监听器)是实现IoC的一种非常简单的方式。正是以这种分散式设计软件组件的形式,使得控制得以发生反转。
通过模板方法模式实现IoC
模板方法模式实现的思想是在一个基类中通过几个抽象方法(也称算法步骤)来定义一个通用的算法,然后让子类提供具体的实现,这样保证算法结构不变。
我们可以应用这个思想,定义一个通用的算法来处理领域实体:
public abstract class EntityProcessor { public final void processEntity() { getEntityData(); createEntity(); validateEntity(); persistEntity(); } protected abstract void getEntityData(); protected abstract void createEntity(); protected abstract void validateEntity(); protected abstract void persistEntity(); }
processEntity() 方法是个模板方法,它定义了处理实体的算法,而抽象方法代表了算法的步骤,它们必须在子类中实现。通过多次继承 EntityProcessor 并实现不同的抽象方法,可以实现若干算法版本。
虽然这说清楚了模板方法模式背后的动机,但人们可能想知道为什么这是 IoC 的模式。
典型的继承中,子类调用基类中定义的方法。而这种模式下,相对真实的情况是:子类实现的方法(算法步骤)被基类的模板方法调用。因此,控制实际是在基类中进行的,而不是在子类中。
这也是 IoC 的典型例子,通过分层结构实现。这种情况下,模板方法只是可调的扩展点的一个漂亮的名字,被开发者用来管理自己的一系列实现。
关于如何在Java中利用IOC控制反转就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341