
问题域
假设我实现一个用户管理系统(UserManager),这包括:- 用户身份管理(一个IdentityManager)
- 用户存取管理 (一个AccessManager)
- 可变的用户信息(一个UserInformationManager)
注意到我的用户管理系统不希望依赖于特定的存储机制和用户信息源。
实现
- 如果我使用Pico的Constructor IoC,那么我的UserManager会是这样
public class UserManager { private IdentityManager identityManager; private AccessManager accessManager; private UserInformationManager userInformationManager; public UserManager(IdentityManager identityManager, AccessManager accessManager, UserInformationManager userInformationManager ) { this.identityManager = identityManager; this.accessManager = accessManager; this.userInformationManager = userInformationManager ; } .............. }
- 如果我使用Spring的Bean IoC,那么我的UserManager会是这个样子: public class UserManager { private IdentityManager identityManager; private AccessManager accessManager; private UserInformationManager userInformationManager; public UserManager( ) {} public void setIdentityManager(IdentityManager identityManager) { this.identityManager = identityManager; } public void setAccessManager(AccessManager accessManager) { this.accessManager = accessManager; } public void setUserInformationManager( UserInformationManager userInformationManager) { this.userInformationManager = userInformationManager ; } .............. }
我用省略号表示的语句将使用这3个接口,而不需要考虑这些接口实现类的实例是如何创建和赋入的。
上面的UserManager写好以后,过了几个月,或者给了另外一个公司。现在一个开发人员发现他需要从一个LDAP服务器验证用户身份,从一个已有的其他系统(关系数据库)中获取存取信息,并且在自己的应用程序中保存特定的用户信息。这时候,他写一个LDAPIdentityManager,一个RelationAccessManager以及一个XMLUserInformationManager。
注册
现在他的具体实现类如下:
- LDAPIdentityManager 实现 IdentityManager 接口
- RelationAccessManager实现 AccessManager 接口
- XMLUserInformationManager实现 UserInformationManager接口
不管他实现的具体子类是什么,无论如何不需要改变UserManager的实现
如果我用Pico,那么我可以
MutablePicoContainer pico = new DefaultPicoContainer();
pico.registerComponent(UserManager.class);
pico.registerComponent(LDAPIdentityManager.class);
pico.registerComponent(RelationAccessManager.class);
pico.registerComponent(XMLUserInformationManager.class);
UserManager manager = (UserManager ) pico.getComponent(UserManager.class);
如果我用Spring,那么极有可能有一个文件,例如applicationContext.xml如下:
<bean id="userManager" class="org.erptao.flex.usercore.UserManager"> <property name="identityManager"> <ref local="identityManager"/> </property> <property name="accessManager"> <ref local="accessManager"/> </property> <property name="userInformationManager"> <ref local="userInformationManager"/> </property> </bean><bean id="identityManager" class="org.erptao.hotel.user.LDAPIdentityManager"> </bean> <bean id="accessManager" class="org.erptao.hotel.user.RelationAccessManager"> </bean> <bean id="userInformationManager" class="org.erptao.hotel.user.XMLUserInformationManager"> </bean>
以及需要注册的代码:
InputStream is = getClass().getResourceAsStream("applicationContext.xml"); XmlBeanFactory bf = new XmlBeanFactory(is); UserManager manager = (UserManager ) bf.getBean("userManager");
当然我们可以进一步封装
public interface FlexContainer { public Object getComponent(Object obj) throws ComponentNotFoundException; }
现在,不同的Pico或者Spring过程主要实现这个接口,所有的其他代码都从这个接口取对象,例如
public class SpringFlexContainer implements FlexContainer{protected ApplicationContext applicationContext; public SpringContainerContext() { }
public void setServletContext(ServletContext context) { servletContext = context; setApplicationContext( WebApplicationContextUtils.getWebApplicationContext(context)); }
public Object getComponent(Object key) throws ComponentNotFoundException { try { if (applicationContext == null) throw new IllegalStateException( "Spring Application context is null"); if (key == null) throw new ComponentNotFoundException("key cannot be null"); if (key instanceof Class) { ...... } return applicationContext.getBean(key.toString()); } catch (BeansException e) { throw new ComponentNotFoundException( "cannot get component: " + e.getMessage(), e); } } .....
这个过程不但可以隐蔽我们使用何种容器,还可以封装各种和其他框架(如WEB,J2EE容器)的具体交互过程,因此你的UserManager完全脱离了他所需要运行的环境以及完成UserManager所需要其他的辅助Manager.
进一步
考虑上面的RelationAccessManager,如果它可以用不同的方式,例如它要使用AccessDAO,那么这个DAO可以用JDBAccessDAO,JDOAccessDAO或者HibernateAccessDAO,在考虑我们的UserManager,他还需要TransactionManager,等等等等
如果你使用Factory,那么你不知道要多少个Factory,(实际上你不知道),然后你的代码还需要从这么不同的Factory去创建对象,然后你要考虑什么Factory生成的什么能够赋给其他什么对象,也就是你需要解决依赖关系,结果你就想编一个Pico或者叫XXX的IoC容器。
所以我那天和weihello聊天,说IoC容器制造产品,业务代码使用产品、垃圾收集器收集垃圾,说来说去,IoC也好,垃圾收集也好,AOP也好,EJB容器也好,目的只有一个:责任分离,让程序员更多地关注核心业务。
其他好处
测试
private IdentityManager identityManager=new LDAPIdentityManager(); private AccessManager accessManager=new RelationAccessManager(); private UserInformationManager userInformationManager=new XMLUserInformationManager(); UserManager userManager = new UserManager(identityManager,accessManager,userInformationManager); ...
不依赖容器,不需要特定的运行环境
间接带来的好处
由于容器的管理,它可以
- 缓存实例
- 加入Proxy,实现Interceptor
等等