在Java中我们通常把接口作为系统与外界交互的窗口,接下来我们来考虑以下问题:
- 如何设计接口?
- 当两个系统之间接口不匹配时,如何处理?
- 但系统A无法便捷的引用系统B的接口的实现类实例时,如何处理?
- ……
为了解决以上问题,需要引入与接口相关的设计模式,接下来介绍定制服务模式、适配器模式、默认适配器模式、代理模式、标识类型模式和常量接口模式。
- 定制服务模式
在如今的商业领域,很流行定制服务。例如电信公司会制定各种各样的服务套餐,满足各种客户的需求。下表是电信公司为个人用户定制的两款宽带服务套餐:
极速精英套餐 | 金融专网套餐 |
---|---|
宽带上网服务(限速2Mbps) | 电信金融专网服务(限速1Mbps) |
在线杀毒服务 | 在线杀毒服务 |
50MB邮箱服务 | 28MB网络硬盘服务 |
5MB邮箱服务 | |
价格:140元无限包月 | 价格:150元无限包月 |
当一个系统能对外提供多种类型的服务时,一种方式是设计粗粒度的接口,把所有的服务放在一个接口中声明,这个接口臃肿庞大,所有的使用者都访问同一个接口;还有一种方式是设计精粒度的接口,对服务精心分类,针对使用者的需求提供特定的接口。
显然第二种精粒度的接口方式会让系统更加容易维护,精粒度的接口可以减轻软件提供商软件维护成本。假如某个精粒度的接口不得不发生变更,那么也只会影响到一小部分访问该接口使用者。此外,精粒度的接口更有利于接口的重用,通过对接口的继承,可以方便的生成针对特定使用者的复合接口。
在上述例子中,可以抽象出5个精粒度的接口,代表5种服务,这5种服务分别是:
- 宽带上网服务 BroadbandService
- 网络硬盘服务 NetworkDiskService
- 在线杀毒服务 VirusKillingService
- 邮箱服务 MailboxService
- 金融专网服务 FinancialNetworkService
上表中的极速精英套餐SuperSpeedCombo和金融专网套餐FinanceCombo属于两种定制的服务接口,它们可以通过继承以上5个精粒度的接口而形成,这样的接口也称为复合接口。
服务接口定制好以后,接下来的问题是如何实现这些接口。为了提高代码的可重用性,类的粒度也应该尽可能小,所以首先为精粒度的接口提供实现类。
以下列出其中的一个服务实现类:
public class BroadbandServiceImpl implements BroadbandService{
private int speed;//网速
public BroadbandServiceImpl(int speed){
this.speed = speed;
}
//连接网络
public void connect(String username,String password){...}
//断开网络
public void disconnect(){...}
}
同上,将精粒度的接口一一创建实现类,得到精粒度的类。
那么对于SuperSpeedCombo 和 FinanceCombo 复合接口,如何实现它们呢?以 SuperSpeedCombo接口的实现类 SuperSpeedComboImpl为例,可以采用组合手段,复用 BroadbandService接口、VirusKillingService接口和MailboxService接口的实现类的程序代码。
那么什么是组合关系呢?在这再复习一下,所谓的组合和继承都是提高代码可重用性的手段,继承最大的弱点就是破坏封装,子类和父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性,而组合关系不会破坏封装,整体类与局部类之间松耦合,彼此相互独立。当然组合关系也有缺点:创建整体类的对象时需要创建所有局部类的对象,而继承关系在创建子类的对象时无须创建父类的对象。
比如要在SuperSpeedComboImpl采用组合手段加入宽带上网服务BroadbandService:
public class SuperSpeedComboImpl implements SuperSpeedCombo{
private BroadbandServiceImpl BroadbandService;
public SuperSpeedComboImpl(BroadbandServiceImpl BroadbandService){
this.BroadbandService = BroadbandService;
}
}
此外,对于极速精英套餐和金融专网套餐,都有付费方式和价格这些属性,可以把这些属性放到同一个Payment中,这符合构建精粒度的对象模型的原则,下面是Payment的源程序:
public class Payment{
public static final String TYPE_PER_YEAR="按年付费";
public static final String TYPE_PER_MONTH="按月付费";
private String type;//付费方式
private double price;//价格
public Payment(String type, double price) {
this.type = type;
this.price = price;
}
//省略type属性和price属性的get/set方法
...
}
SuperSpeedComboImpl类的源程序如下:
public class SuperSpeedComboImpl implements SuperSpeedCombo{
private BroadbandServiceImpl BroadbandService;
private VirusKillingService virusKillingService;
private MailboxService mailboxService;
private Payment payment;
public SuperSpeedComboImpl(BroadbandServiceImpl broadbandService, VirusKillingService virusKillingService,
MailboxService mailboxService, Payment payment) {
super();
BroadbandService = broadbandService;
this.virusKillingService = virusKillingService;
this.mailboxService = mailboxService;
this.payment = payment;
}
public BroadbandServiceImpl getBroadbandService() {
return BroadbandService;
}
public void setBroadbandService(BroadbandServiceImpl broadbandService) {
BroadbandService = broadbandService;
}
public VirusKillingService getVirusKillingService() {
return virusKillingService;
}
public void setVirusKillingService(VirusKillingService virusKillingService) {
this.virusKillingService = virusKillingService;
}
public MailboxService getMailboxService() {
return mailboxService;
}
public void setMailboxService(MailboxService mailboxService) {
this.mailboxService = mailboxService;
}
public Payment getPayment() {
return payment;
}
public void setPayment(Payment payment) {
this.payment = payment;
}
}
下面创建一个极速精英套餐服务的一个实例:
//创建付费信息,按年付费,价格1555
Payment payment = new Payment(Payment.TYPE_PER_MONTH,);
//创建宽带上网服务,网速2Mbps
BroadbandService broadbandService = new BroadbandServiceImpl();
//创建邮箱服务,50MB容量
MailboxService mailboxService = new MialboxServiceImpl();
//创建在线杀毒服务
VirusKillingService virusKillingService = new VirusKillingServiceImpl();
//创建极速精英套餐服务
SuperSpeedCombo superSpeedCombo =
new SuperSpeedComboImpl(broadbandService,mailboxService,virusKillingService,payment);
- 适配器模式
松耦合的系统之间通过接口来交互,当两个系统之间的接口不匹配时,就需要用适配器来把一个系统的接口转换为与另一个系统匹配的接口。可见,适配器的作用是进行接口转换。
在面向对象领域,也采用适配器模式来进行接口的转换。适配器模式有三种实现方式:
- 类的适配器模式(继承实现方式)
- 对象的适配器模式(组合实现方式)
- 接口的适配器模式(抽象类实现方式)
首先我们来看类的适配器模式,以下分别为两个接口:
package com.adapter;
public interface SourceIFC {//源接口
public int add(int a,int b);
}
package com.adapter;
public interface TargetIFC {//目标接口
public int addOne(int a);
}
显然这两个接口是不匹配的,就像220V的电源和笔记本电脑需要的15V的电源,中间需要一个电源适配器,这两个接口之间也需要一个适配器才能匹配。适配器会将其中的一个系统的接口转换为与另一个系统匹配的接口。
其中一个系统的接口实现如下:
package com.adapter;
public class SourceImpl implements SourceIFC{
@Override
public int add(int a, int b) {
return a + b;
}
}
下面就要考虑如何实现TargetIFC接口,才能使之与SourceIFC接口匹配呢? 代码如下:
package com.adapter;
public class TargetImpl extends SourceImpl implements TargetIFC{
@Override
public int addOne(int a) {
return add(a,);//调用父类SourceImpl的add(int a,int b)方法
}
}
上面的TargetImpl就是适配器,它实现了TargetIFC接口,并且继承SourceImpl类,从而能重用SourceImpl类的add()方法。
我们来思考一下,SourceIFC的实现类SourceImpl是否有必要存在呢?我们的目的是为了转换接口使之匹配,对客户来说只需要调用TargetIFC接口,而将TargetIFC接口转换为SourceIFC接口的工作对客户来说是透明的。而上面的转换方法需要先实现SourceIFC接口,再让适配器去继承SourceIFC实现类,而继承关系只能把一个源接口转换为一个目标接口。那么有没有别的更好的方法来实现接口的转换呢?
下面来介绍对象的适配器模式,对象的适配器模式是通过组合方式实现的。同样TargetImpl为适配器,它只实现了TargetIFC接口,并不需要继承SourceIFC的实现类,而是在TargetImpl适配器类的内部对SourceImpl类进行包装,从而生成新的接口。以下是TargetImpl的源程序:
package com.adapter;
public class TargetImpl implements TargetIFC{
private SourceIFC source = new SourceImpl();
@Override
public int addOne(int a) {
return source.add(a, );
}
}
上面这个方法只是我个人的思考,在教科书中在TargetIFC接口中声明了addOne(int a)方法和add(int a,int b)方法,然后在对象的适配器模式中TargetImpl类中代码为下:
package com.adapter;
public class TargetImpl implements TargetIFC{
private SourceIFC source ;
public TargetImpl(SourceIFC source){
this.source = source;
}
public int add(int a,int b){return source.add(a, b);}
@Override
public int addOne(int a) {
return source.add(a, );
}
}
如果按照书上来写适配器的内容的话,在实例化适配器的之前,还要实例化一个SourceImpl类,然后将SourceImpl实例作为构造参数实例化适配器。
关于组合关系与继承关系的优劣,总的来说,组合关系比继承关系更有利于系统的维护和扩展,而且组合关系能够将多个源接口转换为一个目标接口,在上文介绍定制服务模式的SuperSpeedComboImpl适配器就是一个例子,而继承关系只能把一个源接口转换为一个目标接口,因此应该优先考虑用组合关系来实现适配器。
继续来看第三种适配器模式:接口的适配器模式,接口的适配器模式通过抽象类来实现,十分简单。比如在java.awt.event包下,MouseListener接口的定义如下:
public interface MouseListener extends EventListener {
public void mouseClicked(MouseEvent e);
public void mousePressed(MouseEvent e);
public void mouseReleased(MouseEvent e);
public void mouseEntered(MouseEvent e);
public void mouseExited(MouseEvent e);
}
如果用户想要处理按下鼠标键的事件,就要创建MouseListener接口的实现类,然后必须实现所有方法,在mousePressed方法下编写处理按下鼠标键的事件,其他方法则为空方法不加处理。
尽管仅仅想要实现mousePressed()方法,但是不得不为其他的方法提供空的方法体。那么怎么来解决这个问题呢?我们可以再定义一个抽象类实现MouseListener接口:
package com.adapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public abstract class MyMouseListener implements MouseListener {
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
}
然后如果你想处理鼠标按下这一个事件,就可以这样写:
package com.adapter;
import java.awt.event.MouseEvent;
public class MyMouseListenerAdapter extends MyMouseListener{
@Override
public void mousePressed(MouseEvent e) {
}
}
实际上JDK已经为MouseListener提供了一个默认适配器MouseAdapter,不同的是MouseAdapter并不是抽象类。但是一般情况下我们都会去继承MouseAdapter,所以个人认为将这个适配器定义为抽象类或许更好。