接口隔离原则要求的是在一个模块应该只依赖它需要的接口,以保证接口的小纯洁。而且需要保证接口应该尽量小,即设计接口的时候应该让接口尽量细化,不要定义太臃肿的接口(比如接口中有很多不相干的逻辑的方法声明)。
首先看看接口隔离原则的定义,有两种定义第一种:Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该强行以来它不需要的接口)
第二种:The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)
而这里的接口,却不仅仅指的是通过interface关键字定义的接口,接口分为2种:
1、对象接口(Object Interface)
JAVA中声明的一个类,通过new关键字产生的一个实例,它是对一个类型的事物的描述,这也是一种接口。例如:
Phone phone = new Phone();这里的类Person就是实例phone的一个接口
2、类接口(Class Interface)
这种接口就是通过interface关键字定义的接口了
也就是说,接口隔离原则要求的是在一个模块应该只依赖它需要的接口,以保证接口的小纯洁。而且需要保证接口应该尽量小,即设计接口的时候应该让接口尽量细化,不要定义太臃肿的接口(比如接口中有很多不相干的逻辑的方法声明)。
接口隔离原则与单一职责原则有些相似,不过不同在于:单一职责原则要求的是类和接口职责单一,注重的是职责,是业务逻辑上的划分。而接口隔离原则要求的是接口的方法尽量少,尽量有用(针对一个模块)
在使用接口隔离原则的时候需要有一些规范:
1.接口尽量小
接口尽量小主要是为了保证一个接口只服务一个子模块或者业务逻辑
2.接口高内聚
接口高内聚是对内高度依赖,对外尽可能隔离。即一个接口内部的声明的方法相互之间都与某一个子模块相关,且是这个子模块必须的。
3.接口设计是有限度的
但是如果完全遵循接口隔离原则的话,会出现一个问题。即接口的设计力度会越来越小,这样就造成了接口数量剧增,系统复杂度一下子增加了,而这不是真实项目所需要的,所以在使用这个原则的时候还要在特定的项目,根据经验或者尝试判断,不过没有一个固定的标准。
上一篇文章中,我们提到OO中复用的方式有两种,组合和继承。一般情况下,应该尽可能使用组合的方式。现在以复用为基本需求,推演若干常见组合型模式
1. Decorator模式
需求:我们已经有一群对象,现在想统一为这些对象添加若干新特性。更重要的是,这些新特性可以反复叠加于某个对象,或者只选择部分特性作用于某个对象。
条件:如果这个特性的实现不依赖于具体的对象,就如同添加一个装饰,那么可以应用Decorator模式。
如果基于开闭原则出发,实际上就是要求不修改已有的对象,通过为每一个新特性实现一个Decorator类来实现扩展(使用的方式是组合)。
应该说:Decorator的使用条件是异常苛刻的,因为每一个Decorator类要求可装饰于任意的对象,这些对象可以是现在已经存在的,同时包括那些未来时的。
2. Proxy模式
Proxy模式有点类似于Decorator模式,也是为已有类的行为做些新的装饰。所不同的是,Proxy模式一般只是为一个对象作装饰,且这些装饰不会相互组合叠加。
从开闭原则出发,Proxy与Decorator模式相同,都是要求不修改已有的对象,通过组合的方式,新建一个Proxy类来扩展原有对象的功能。
Proxy模式是典型的中间层技术,以Proxy对象作为中间层,代替原始对象,从而提供一种更灵活的设计方式。
对代理模式,常见的应用情景是权限和访问控制:
1) 远程代理(Remote Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
2) 虚拟代理(Virtual Proxy)根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
3) 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
4) 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。
5) Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。
3. Decorator模式 /Proxy模式与重构
如果原始代码没有使用Decorator模式 /Proxy模式,那么大多数情况下基本是使用了继承+多态的方式实现,就这种情况而言,可以使用Replace Inheritance with Delegate重构方法。
最后想说明的是,
1. 这两个模式的应用时机
虽然这两个模式是都是装饰已有对象的行为,但是一般设计之初我们可能就已经有意的引入这两个模式。
2. 为什么不是多态,而是组合
首先,使用多态总是可以实现需求的,但正如之前文章中所论述的,应该尽可能使用组合。
第二,这两个模式的主要特点是装饰,通过装饰,将对象的主要行为由原始对象体现,将对象的一些别致行为由装饰对象或者代理对象实现。
这样的设计更满足单一职责原则。
3. 为什么说是装饰已有行为?
没有增加新的接口;改变了原有接口的效果;
Spring的AOP是上面代理模式的深入。使用Spring AOP,开发者无需实现业务逻辑对象工厂,无需实现代理工厂,这两个工厂都由Spring容器充当。Spring AOP不仅允许使用XML文件配置目标方法,ProxyHandler也允许使用依赖注入管理,Spring AOP提供了更多灵活的选择。
在下面Spring AOP的示例中,InvocationHandler采用动态配置,需要增加的方法也采用动态配置,一个目标对象可以有多个拦截器(类似于代理模式中的代理处理器)。
下面是原始的目标对象:
//目标对象的接口
public interface Person
{
//该接口声明了两个方法
void info();
void run();
}
下面是原始目标对象的实现类,实现类的代码如下:
//目标对象的实现类,实现类实现Person接口
public class PersonImpl implements Person
{
//两个成员属性
private String name;
private int age;
//name属性的 setter方法
public void setName(String name)
{
this.name = name;
}
//age属性的setter方法
public void setAge(int age)
{
this.age = age;
}
//info方法,该方法仅仅在控制台打印一行字符串
public void info()
{
System.out.println("我的名字是: " + name + " , 今年年龄为: " + age);
}
//run方法,该方法也在控制台打印一行字符串。
public void run()
{
if (age < 45)
{
System.out.println("我还年轻,奔跑迅速...");
}
else
{
System.out.println("我年老体弱,只能慢跑...");
}
}
}
该Person实例将由Spring容器负责产生和管理,name属性和age属性也采用依赖注入管理。
为了充分展示Spring AOP的功能,此处为Person对象创建三个拦截器。第一个拦截器是调用方法前的拦截器,代码如下:
//调用目标方法前的拦截器,拦截器实现MethodBeforeAdvice接口
public class MyBeforeAdvice implements MethodBeforeAdvice
{
//实现MethodBeforeAdvice接口,必须实现before方法,该方法将在目标
//方法调用之前,自动被调用。
public void before(Method m, Object[] args, Object target) throws Throwable
{
System.out.println("方法调用之前...");
System.out.println("下面是方法调用的信息:");
System.out.println("所执行的方法是:" + m);
System.out.println("调用方法的参数是:" + args);
System.out.println("目标对象是:" + target);
}
}
第二个拦截器是方法调用后的拦截器,该拦截器将在方法调用结束后自动被调用,拦截器代码如下:
//调用目标方法后的拦截器,该拦截器实现AfterReturningAdvice接口
public class MyAfterAdvice implements AfterReturningAdvice
{
//实现AfterReturningAdvice接口必须实现afterReturning方法,该方法将在目标方法
//调用结束后,自动被调用。
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)throws Throwable
{
System.out.println("方法调用结束...");
System.out.println("目标方法的返回值是 : " + returnValue);
System.out.println("目标方法是 : " + m);
System.out.println("目标方法的参数是 : " + args);
System.out.println("目标对象是 : " + target);
}
}
第三个拦截器是是Around拦截器,该拦截器既可以在目标方法之前调用,也可以在目标方法调用之后被调用。下面是Around拦截器的代码:
//Around拦截器实现MethodInterceptor接口
public class MyAroundInterceptor implements MethodInterceptor
{
//实现MethodInterceptor接口必须实现invoke方法
public Object invoke(MethodInvocation invocation) throws Throwable
{
//调用目标方法之前执行的动作
System.out.println("调用方法之前: invocation对象:[" + invocation + "]");
//调用目标方法
Object rval = invocation.proceed();
//调用目标方法之后执行的动作
System.out.println("调用结束...");
return rval;
}
}
利用Spring AOP框架,实现之前的代理模式相当简单。只需要实现对应的拦截器即可,无需创建自己的代理工厂,只需采用Spring容器作为代理工厂。下面在Spring配置文件中配置目标bean,以及拦截器。
下面是Spring配置文件的代码:
<?xml version="1.0" encoding="gb2312"?>
<!-- Spring配置文件的文件头-->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- Spring配置文件的根元素-->
<beans>
<!-- 配置目标对象-->
<bean id="personTarget" class="lee.PersonImpl">
<!-- 为目标对象注入name属性值-->
<property name="name">
<value>Wawa</value>
</property>
<!-- 为目标对象注入age属性值-->
<property name="age">
<value>51</value>
</property>
</bean>
<!-- 第一个拦截器-->
<bean id="myAdvice" class="lee.MyBeforeAdvice"/>
<!-- 第二个拦截器-->
<bean id="myAroundInterceptor" class="lee.MyAroundInterceptor"/>
<!-- 将拦截器包装成Advisor,该对象还确定代理对怎样的方法增加处理-->
<bean id="runAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- advice属性确定处理bean-->
<property name="advice">
<!-- 此处的处理bean定义采用嵌套bean,也可引用容器的另一个bean-->
<bean class="lee.MyAfterAdvice"/>
</property>
<!-- patterns确定正则表达式模式-->
<property name="patterns">
<list>
<!-- 确定正则表达式列表-->
<value>.*run.*</value>
</list>
</property>
</bean>
<!-- 使用ProxyFactoryBean 产生代理对象-->
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理对象所实现的接口-->
<property name="proxyInterfaces">
<value>lee.Person</value>
</property>
<!-- 设置目标对象-->
<property name="target">
<ref local="personTarget"/>
</property>
<!-- 代理对象所使用的拦截器-->
<property name="interceptorNames">
<list>
<value>runAdvisor</value>
<value>myAdvice</value>
<value>myAroundInterceptor</value>
</list>
</property>
</bean>
</beans>
该配置文件使用ProxyFactoryBean来生成代理对象,配置ProxyFactoryBean工厂bean时,指定了target属性,该属性值就是目标对象,该属性值为personTarget,指定代理的目标对象为personTarget。通过interceptorNames属性确定代理需要的拦截器,拦截器可以是普通的Advice,普通Advice将对目标对象的所有方法起作用,拦截器也可以是Advisor,Advisor是Advice和切面的组合,用于确定目标对象的哪些方法需要增加处理,以及怎样的处理。在上面的配置文件中,使用了三个拦截器,其中myAdvice、myAroundInterceptor都是普通Advice,它们将对目标对象的所有方法起作用。而runAdvisor则使用了正则表达式切面,匹配run方法,即该拦截器只对目标对象的run方法起作用。
下面是测试代理的主程序:
public class BeanTest
{
public static void main(String[] args)throws Exception
{