定义接口
接口的例子
接口这个词在日常生活中经常听到,比如:USB接口,电源插座接口等,下边通过了解USB接口和电源插座接口来理解接口的意义。
1)USB接口
引用百度百科,USB是通用串行总线(英语:Universal Serial Bus,缩写:USB)是一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,并扩展至摄影器材、数字电视(机顶盒)、游戏机等其它相关领域。最新一代是USB 3.1,传输速度为10Gbit/s,三段式电压5V/12V/20V,最大供电100W ,新型Type C插型不再分正反。
老牌手机大部分是下边这种Micro USB接口方式,符合USB2.0规范,支持12Mbit/s的数据传输速率。
新型手机大部分是下边这种Type-C接口方式,是一种全新的USB接口形式,正式解决了 “USB永远插不准” 的世界性难题,正反面随便插。
你会发现连接电源一端或连接电脑一端的接口是固定的,下图是它的接口定义:
了解了USB接口,我们思考接口是什么?
接口是一个标准,比如:Type-C USB接口,手机厂商支持Type-C接口,按照Type-C的规范生产手机,手机充电线的厂家按照Type-C 规范生产充电线,大家遵循统一的标准和规范生产各自的产品,这样遵循Type-C 接口规范的手机和充电线才能一起使用。
2)电源插座接口
我们家里用的插座接口也遵循一定的标准,家用和类似用途的插头插座,由国家质检总局和中国国家标准化管理委员会于2008年9月24日发布,并于2009年8月1日强制执行。对排插的外观、尺寸、性能等方面均给出了明确的规范。2017年4月14日中国插座行业国家新标准《GB 2099.7-2015家用和类似用途插头插座 第2-7部分:延长线插座的特殊要求》、《GB 2099.3-2015家用和类似用途插头插座第2 -5部分:转换器的特殊要求》正式实施生效(以下简称新国标),本次的插座行业国家标准新旧替代已是第3次,之前的《GB 2099.3-1997》《GB 2099.9-2008》被替换下来,相对于之前2次标准替换,这次新国标更加严谨,对安全性进行全面彻底升级。
一图是新国标和旧国标的产品样式:
只有遵循国标生成的插座、插头才可以一起使用。
通用的接口概念
接口就是标准,接口就是规范,它是不同实体之间协作的标准,各行各业遵循接口标准进行生产有利于行业的协作发展,促进国家经济的发展。
软件接口
在计算机编程、计算机软件中接口是什么呢?下边举几个例子。
1)支付接口
我们在电商网站上买东西,在支付时会让我们选择各种支付方式,最常见的微信支付、支付宝支付、银联支付等。
软件最重要的是可重用性,电商网站所擅长的领域是电子商务,微信支付和支付宝它所擅长的领域是支付,电商网站要想让顾客使用微信进行支付就需要按照微信支付接口的要求接入,如果网站要使用支付宝支付,网站就需要按照支付宝的接口要求接入。这里微信提供了微信支付的接口标准,支付宝提供了支付宝支付的接口标准,谁使用哪种支付方式请按照谁的的接口标准进行接入。
不管是微信还是支付宝他们最终都要和银行的系统对接,所以微信支付和支付宝都必须按照银行的接口标准与银行的系统进行对接。
2)身份证射频卡接口
身份证是我们每个公民的身份,我们去乘坐火车、取票、住宾馆等都需要刷身份证,刷身份证的目的是读出身份证中的信息,要知道进站刷卡、取票刷卡、宾馆刷卡都是不同的软件,他们是如何读取出身份证中的信息呢?
身份证内置了非接触 IC 卡芯片,在芯片中有我们的身份信息,采用符合ISO14443 Type B通讯协议射频识别(RFID)技术,不同的身份证读取软件只要按照协议标准即可读取出身份中的身份信息。
计算机软件接口是指软件对外提供的服务接口,是指不同程序之间的通信接口,有了接口更能提高软件的可重用性,更能促进软件产业朝着标准化的方向发展。
Java中的接口
理解完接口的概念,回归到具体的程序代码中,我们思考接口在Java代码中如何体现呢?也就是说在面向对象编程中,如何与一个对象通信?通过调用对象的方法与对象进行通信。比如:一个天气预报的类,提供天气查询方法,外界调用它的天气查询方法从而得到当前的天气信息;攀博课堂视频分享类,提供视频分享的方法,外界调用它的视频分享的方法得到视频的分享地址。
在Java类中方法就是接口,那么方法的实现逻辑属于接口吗?换句话说,外界要调用一个类的方法它会关心这个方法具体的实现逻辑吗?自然不会,否则也失去了软件重用的意义。比如攀博课堂的资源分享类,外界调用它的视频分享方法其目的就是获取视频分享的地址,至于内部是如何生成这个地址的外界是不关心的;再比如电商网站调用支付宝的支付接口,电商网站最终的目的是通过支付宝为客户完成在线支付,至于支付宝内部与银行系统对接的具体逻辑电商网站不关心。
总结一下,java类中的方法声明就是接口,方法的实现逻辑外界不关心,外界关心的是如何调用此接口并通过接口能获取什么。
所以,Java中接口仅仅是方法的声明,方法的声明包括方法签名(方法名、参数列表)和返回值类型,如下:
返回值类型 方法名(参数列表)
例如攀博课堂提供视频分享的接口,外界调用此接口获取视频分享的地址,如下图:
定义接口
定义一个接口和定义一个类差不多,把class关键字换成interface关键字,并且方法只有声明没有实现逻辑。
下边创建一个接口:
1)创建攀博课堂视频分享接口
新增加一个interface接口类型PbSharingInterface:
点击完成,PbSharingInterface的代码如下:
package com.pbteach.javase.oop.testinterface1;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
}
2)在接口中添加视频分享的方法
package com.pbteach.javase.oop.testinterface1;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return 视频地址
*/
String shareVideo(String knowledge,String version);
}
视频分享接口包括了方法名、参数列表、及返回值类型,简单理解就是只是方法声明没有方法实现,类似抽象类中抽象方法的定义。接口中的方法无需权限修饰符,默认为public权限。
一个接口类中可以包括多个接口方法,如下:
package com.pbteach.javase.oop.testinterface1;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return 视频地址
*/
String shareVideo(String knowledge,String version);
/**
* 文档分享方法
* @param resourcesId 资源id
* @return 文档地址
*/
String shareDocument(String resourcesId);
}
实现接口
确定接口
下边是前面课程定义的攀博课堂资源分享接口,如下:
package com.pbteach.javase.oop.testinterface1;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return 视频地址
*/
String shareVideo(String knowledge,String version);
/**
* 文档分享方法
* @param resourcesId 资源id
* @return 文档地址
*/
String shareDocument(String resourcesId);
}
接口实现
外界通过接口的定义即可知道该接口所包含的功能,但外界如何调用此接口中的方法呢?
调用接口最终就是调用对象的方法,我们需要定义一个类来实现接口中的方法,这个类叫接口实现类,这样外界就可以调用接口实现类的对象的方法,在接口实现类中要将接口中所声明的方法全部实现,下图是接口与接口实现类关系图:
定义一个类PbSaringClass实现PbSharingInterface接口需要使用implements关键字,并且需要实现接口中的所有方法,PbSharingInterface是接口,PbSharingClass 是接口实现类,代码如下:
package com.pbteach.javase.oop.testinterface1;
/**
* 攀博课堂资源分享接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbSharingClass implements PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return 视频地址
*/
public String shareVideo(String knowledge,String version) {
System.out.println("视频分享接口被调用");
//根据knowledge、版本查询视频地址
//组装完整的视频地址并返回
return "http://www.pbteach.com/具体的视频地址";
}
/**
* 文档分享方法
* @param resourcesId 资源id
* @return 文档地址
*/
public String shareDocument(String resourcesId) {
System.out.println("文档分享接口被调用");
return "http://www.pbteach.com/具体的文档地址";
}
}
调用接口
调用接口就是调用对象的方法,创建接口实现类的对象,调用它的方法。
在PbSharingClass中定义main方法,如下:
public static void main(String[] args) {
//创建接口实现类的对象
PbSharingClass pbSharingClass = new PbSharingClass();
//调用对象的方法即接口的方法
String shareVideo = pbSharingClass.shareVideo("javase_oop_interface_define", "v1.0");
System.out.println(shareVideo);
}
接口也作为一个类型存在,接口也支持向上转型,见下边的例子:
public static void main(String[] args) {
// PbSharingClass pbSharingClass = new PbSharingClass();
//通常将对象向上转型为接口类型
PbSharingInterface pbSharingClass = new PbSharingClass();
//调用对象的方法即接口的方法
String shareVideo = pbSharingClass.shareVideo("javase_oop_interface_define", "v1.0");
System.out.println(shareVideo);
}
输出:
视频分享接口被调用
http://www.pbteach.com/具体的视频地址
接口中的其它成员
静态变量、静态方法
一个接口除了方法定义还可以定义变量及静态方法,如下例:
package com.pbteach.javase.oop.testinterface2;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
//攀博课堂网址
String siteUrl = "http://www.pbteach.com";
//攀博课堂Top10视频列表
static String[] getTopVideos() {
String[] topVides = new String[] {
"Java面向对象编程-基础篇",
"Java面向对象编程-高级篇",
"JavaWeb开发实战篇",
"Spring框架原理与实战",
"SpringBoot快速开发模型",
"SpringCloud分布式开发实战",
"SpringCloudAlibaba分布式开发实战",
"Java在线项目实战之攀博课堂大型分布式在线教育项目",
"Java在线项目实战之攀博课堂移动支付项目",
"Java在线项目实战之攀博课堂大数据分析项目"
};
return topVides;
}
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return 视频地址
*/
String shareVideo(String knowledge,String version);
/**
* 文档分享方法
* @param resourcesId 资源id
* @return 文档地址
*/
String shareDocument(String resourcesId);
}
成员变量在接口中自动为static(静态)、final(最终),所以接口中定义的成员变量、成员方法都是静态的,只通过接口类型即可访问,类似于类中定义的静态变量和静态方法,如下:
//访问接口中的静态变量
System.out.println(PbSharingInterface.siteUrl);
//访问接口中的静态方法
String[] topVideos = PbSharingInterface.getTopVideos();
完整代码如下:
package com.pbteach.javase.oop.testinterface2;
/**
* 攀博课堂资源分享接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbSharingClass implements PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return
*/
public String shareVideo(String knowledge,String version) {
//根据knowledge、版本查询视频地址
//组装完整的视频地址并返回
return siteUrl+"/具体的视频地址";
}
public String shareDocument(String resourcesId) {
return siteUrl+"/具体的文档地址";
}
public void test() {
}
public static void main(String[] args) {
// PbSharingClass pbSharingClass = new PbSharingClass();
//通常将对象向上转型为接口类型,将对象赋值给接口变量
PbSharingInterface pbSharingClass = new PbSharingClass();
String shareVideo = pbSharingClass.shareVideo("javase_oop_interface_define", "v1.0");
System.out.println(shareVideo);
//访问接口中的静态变量
System.out.println(PbSharingInterface.siteUrl);
//访问接口中的静态方法
String[] topVideos = PbSharingInterface.getTopVideos();
for (int i = 0; i < topVideos.length; i++) {
System.out.println(topVideos[i]);
}
}
}
默认方法
如果一个接口已经运行了一段时间 ,这表示它已经存在实现类,而且不止一个,此时如果发现该接口少定义了一个方法,该怎么办?
方案1:在接口中定义这个缺少的方法。
问题:如果在接口中定义该方法,那么它的所有实现类都要实现此方法, 那些实现类已经投入使用,再去修改可能会影响其它程序,此方案不太可取。
方案2:在接口中定义这个缺少的方法,并且提供默认实现,这样接口实现类可以不用理睬,也可以重写方法。
方案2不会给现有程序造成直接影响,此方案相比方案1要好。
下边的代码定义默认方法String shareOutline(String courseId)用于分享课程大纲。
注意:接口中的默认方法是指拥有方法实现的方法,需要使用default关键字修饰。
package com.pbteach.javase.oop.testinterface2;
/**
* 攀博课堂资源分享接口类型
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface PbSharingInterface {
//攀博课堂网址
String siteUrl = "http://www.pbteach.com";
//攀博课堂Top10视频列表
static String[] getTopVideos() {
String[] topVideos = new String[]{
"Java面向对象编程-基础篇",
"Java面向对象编程-高级篇",
"JavaWeb开发实战篇",
"Spring框架原理与实战",
"SpringBoot快速开发模型",
"SpringCloud分布式开发实战",
"SpringCloudAlibaba分布式开发实战",
"Java在线项目实战之攀博课堂大型分布式在线教育项目",
"Java在线项目实战之攀博课堂移动支付项目",
"Java在线项目实战之攀博课堂大数据分析项目"
};
return topVideos;
}
/**
* 课程大纲分享
* @param courseId 课程id
* @return
*/
default String shareOutline(String courseId) {
return "默认大纲";
}
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return
*/
String shareVideo(String knowledge,String version);
/**
* 文档分享方法
* @param resourcesId 资源id
* @return
*/
String shareDocument(String resourcesId);
}
可直接调用接口的默认方法,见下代码:
//通常将对象向上转型为接口类型,将对象赋值给接口变量
PbSharingInterface pbSharingClass = new PbSharingClass();
//调用默认方法
String shareOutline = pbSharingClass.shareOutline("101");
System.out.println(shareOutline);
当然,接口实现类是可以重写默认方法的,重写后则调用的是接口实现类的方法,见下边的代码:
//重写默认大纲
public String shareOutline(String courseId) {
return "课程:"+courseId+"的大纲";
}
完整代码如下:
package com.pbteach.javase.oop.testinterfaces2;
/**
* 攀博课堂资源分享接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbSharingClass implements PbSharingInterface {
/**
* 视频分享方法
* @param knowledge 知识点
* @param version 版本
* @return
*/
public String shareVideo(String knowledge,String version) {
//根据knowledge、版本查询视频地址
//组装完整的视频地址并返回
return PbSharingInterface.siteUrl+"/具体的视频地址";
}
public String shareDocument(String resourcesId) {
return PbSharingInterface.siteUrl+"/具体的文档地址";
}
//重写默认大纲
public String shareOutline(String courseId) {
return "课程:"+courseId+"的大纲";
}
public static void main(String[] args) {
// PbSharingClass pbSharingClass = new PbSharingClass();
//通常将对象向上转型为接口类型,将对象赋值给接口变量
PbSharingInterface pbSharingClass = new PbSharingClass();
String shareVideo = pbSharingClass.shareVideo("javase_oop_interface_define", "v1.0");
System.out.println(shareVideo);
//访问接口中的静态变量
System.out.println(PbSharingInterface.siteUrl);
//访问接口中的静态方法
String[] topVideos = PbSharingInterface.getTopVideos();
for (int i = 0; i < topVideos.length; i++) {
System.out.println(topVideos[i]);
}
//调用默认方法
String shareOutline = pbSharingClass.shareOutline("101");
System.out.println(shareOutline);
}
}
接口与抽象类的区别
接口和抽象类一样是一种抽象技术,它们的区别和相同点如下:
不同点:
1、抽象类中可以有成员变量,接口中只能有静态(static)且不可变(final)的变量。
2、抽象类中可以有构造方法,接口中没有构造方法,因为接口中只有static、final的变量,在定义时就完成了初始化且不能再变。
3、子类继承抽象类只允许单继承,实现类可以实现多个接口(稍后介绍),可以认为是多继承。
4、抽象类存在于继承关系中,在实际生产中存在is-a关系时可以使用抽象类;接口不限制用于is-a关系。
相同点:
1、接口与抽象类都不允许创建对象。
2、接口与抽象类都允许有抽象方法及完整的实现方法。
建议:
抽象时不仅仅是对方法抽象,还有一部分成员变量需要提取到父类中,且子类和父类的关系是is-a的关系,此时用抽象类。
抽象时只是对功能方法进行抽象,这些方法是暴露给外界调用的,此时建议使用接口,一个接口是一个功能的抽象。
接口应用更广泛
接口与抽象类相比,接口应用更为广泛更灵活:
1、首先接口实现相比继承关系更简单,接口实现类只需要实现接口方法即可,而子类继承父类不仅要考虑重写父类的方法还要考虑继承父类的成员变量。
2、接口不限制为is-a关系,继承只限制为is-a关系。
3、其次一个接口实现类可以有多个接口,而是子类只能有一个父类,接口更灵活。
4、一个类与外界交互,外累只需要知道类的接口即可,无需知道它的实现细节,接口更符合封装的思想。
所以建议在对类设计时,如果此类与外界有交互可抽象一层接口层专门负责对外交互。
接口扩展
接口继承
和类一样,接口之间也可以继承,接口A继承了接口B即拥有接口B中的接口。接口之间的继承与子类继承父类意义上不同,接口继承的目的是扩展,子类继承父类的目的第一为了寻根, 第二个为了方法重写。
下边以攀博课堂支付系统为例进行说明:
攀博课堂支付系统定义基础的支付接口,包括请求支付和查询支付结果两个接口方法,如下:
package com.pbteach.javase.oop.testinterface3;
/**
* 攀博课堂支付基础接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface Pay {
/**
* 请求支付
* @param account
* @param money
* @return 请求结果
*/
String requestPay(String account,float money);
/**
* 查询支付结果
* @param orderId
* @return 支付结果
*/
String getResult(String orderId);
}
攀博课堂支持微信支付和支付宝支付,特定义微信支付接口类型和支付宝支付接口类型,除了具有Pay基础支付接口中的方法还具有自己特有的接口方法。
根据微信和支付宝接入要求,首先需要申请授权码方可调用微信和支付宝的接口,所以在微信支付和支付宝支付的接口类型中添加申请授权码方法,代码如下:
微信支付接口类型:
package com.pbteach.javase.oop.testinterface3;
/**
* 攀博课堂微信支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface WeixinPay extends Pay {
String weixinUrl = "https://";
/**
* 申请微信支付授权码
* @param account
* @param money
* @return 授权码
*/
String applyWeixinCode(String account,float money);
}
interface WeixinPay extends Pay表示WeixinPay 接口继承Pay接口,WeixinPay 接口拥有父接口的所有方法及自己所特有的方法。
我们可以验证WeixinPay 接口中的方法,定义微信支付接口实现类,实现类中的实现方法即是WeixinPay接口中的所有方法,见下边的代码,在实现类中需要实现三个接口方法:
package com.pbteach.javase.oop.testinterface3;
/**
* 攀博课堂微信支付接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbWeixinPay implements WeixinPay {
public String requestPay(String account, float money) {
return null;
}
public String getResult(String orderId) {
return null;
}
public String applyWeixinCode(String account, float money) {
return null;
}
}
下边定义支付宝支付接口类型如下:
package com.pbteach.javase.oop.testinterface3;
/**
* 攀博课堂支付宝支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface AliPay extends Pay {
String aliPayUrl = "https://";
/**
* 申请支付宝授权码
* @param account
* @param money
* @return 授权码
*/
String applyAliCode(String account,float money);
}
方法重写
当子接口出现与父接口同名的方法且参数一致时,编译 器认为是子接口重写了父接口的方法,子接口中重写方法的返回值类型必须可向上转型为父接口方法的返回值类型,否则编译不通过,例如:
当接口实现类实现了子接口则需要实现子接口口的重写方法即可。
建议:
当一个子接口需要将某个方法返回值类型缩小范围时才会使用重写方法,如果子接口与父接口中的方法完全一样这是没有必要的。
多重继承
在Java中类之间的继承只允许单继承,接口可以多重继承,下边的代码验证了接口的多重继承:
public interface PbPayInterface extends WeixinPay,AliPay {
//这里可以添加自己特有的接口方法
}
PbPayInterface继承了WeixinPay和AliPay,PbPayInterface将拥有WeixinPay和AliPay及自己特有的接口方法。
定义实现类PbPay,验证PbPayInterface接口中的方法:
点击“Add unimplemented methods”自动生成接口方法,见下图,@Overriede表示实现了接口中的方法,它是一个注解(后边学习)可以去掉。
完整PbPay的代码如下:
package com.pbteach.javase.oop.testinterface3;
public class PbPay implements PbPayInterface {
public String applyWeixinCode(String account, float money) {
return null;
}
public String requestPay(String account, float money) {
return null;
}
public String getResult(String orderId) {
return null;
}
public String applyAliCode(String account, float money) {
return null;
}
}
多个实现
在Java中接口可以继承多个接口,一个类也可以实现多个接口类型,下边的代码验证了一个类实现多个接口:
implements后边可以跟多个接口,中间用逗号分隔,在实现类中需要将所实现的接口方法全部实现。
package com.pbteach.javase.oop.interfaces.extend;
public class PbPayWeixinAndAli implements WeixinPay,AliPay {
public String applyWeixinCode(String account, float money) {
return null;
}
public String requestPay(String account, float money) {
return null;
}
public String getResult(String orderId) {
return null;
}
public String applyAliCode(String account, float money) {
return null;
}
}
理解面向接口编程
准备环境
本节使用攀博课堂支付接口案例讲解,详情参考 “接口扩展”章节。
案例代码如下:
1、基础支付接口
package com.pbteach.javase.oop.testinterface4;
/**
* 攀博课堂支付基础接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface Pay {
/**
* 请求支付
* @param account
* @param money
* @return 请求结果
*/
String requestPay(String account,float money);
/**
* 查询支付结果
* @param orderId
* @return 支付结果
*/
String getResult(String orderId);
}
2、微信支付接口
package com.pbteach.javase.oop.testinterface4;
/**
* 攀博课堂微信支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface WeixinPay extends Pay {
String weixinUrl = "https://";
/**
* 申请微信支付授权码
* @param account
* @param money
* @return 授权码
*/
String applyWeixinCode(String account,float money);
}
3、微信支付实现
package com.pbteach.javase.oop.testinterface4;
/**
* 攀博课堂微信支付接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbWeixinPay implements WeixinPay {
public String requestPay(String account, float money) {
return null;
}
public String getResult(String orderId) {
return null;
}
public String applyWeixinCode(String account, float money) {
return null;
}
}
什么是面向接口编程
在面向对象编程中建议大家采用面向接口编程,什么是面向接口编程?面向接口编程又有什么好处呢?
下边攀博课堂在线教育系统的部分演示代码,请找出问题:
支付类与支付接口还采用上一节课的内容。
package com.pbteach.javase.oop.testinterface4;
/**
* 攀博课堂在线教育系统启动类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbMain {
//课程管理服务
//学生管理服务
//支付服务
PbWeixinPay pbWeixinPay = new PbWeixinPay();
//...
}
上边例子中PbMain关联PbWeixinPay,在实际生产中类之间的关联关系较常见,但是关联会使用类产生耦合,耦合是指类与类之间存在一定的联系,这种联系可能是依赖、关联、聚合、组合、继承等关系,在类的设计中尽量避免类与类之间耦合,如果存在耦合要想办法解耦合,如果耦合过于紧密系统就非常难以维护。
上边代码中对PbWeixinPay的初始化方法是在定义成员变量的同时执行new PbWeixinPay(),这看上去似乎没有问题,但从长远考虑存在问题。
后期随着系统的升级避免不了增加其它的支付方式,比如支付宝支付、银联支付,也就是说用户可以选择微信支付、支付宝支付、银联支付的其中一种进行支付,如果在PbMain中直接使用PbWeixinPay作为成员变量类型后期增加支付方式后需要修改PbMain的代码使之支持其它支付类型。
如何解决?采用面向接口编程即可解决。
因为接口实现类可以向上转型至接口类型,将PbWeixinPay类型更改为Pay基础支付接口,并提供pay的getter和setter方法,代码如下:
package com.pbteach.javase.oop.testinterface4;
/**
* 攀博课堂在线教育系统启动类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbMain {
//课程管理服务
//学生管理服务
//支付服务
// PbWeixinPay pbWeixinPay = new PbWeixinPay();
private Pay pay;
public Pay getPay() {
return pay;
}
public void setPay(Pay pay) {
this.pay = pay;
}
//...
}
当用户选择微信支付向pay赋值PbWexinPay对象,选择支付宝支付向其赋值PbAliPay对象,代码如下:
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = new PbWeixinPay();
//向pay属性赋值
pbMain.setPay(pay);
}
在进行类的关系设计时,如果类避免不了与其它类存在关系,我们需要抽取类的对外服务接口,类与类之间通过接口进行关联,这样就将类与类之间解耦合。
面向接口编程是在进行系统设计时首先要设计类、系统等对外服务的接口,类与类之间、系统与系统之间都是使用接口进行交互设计,无需考虑接口的实现。面向接口设计出来的系统更健壮,更易于维护,但是注意不要在系统中滥用接口,否则会起到反作用。
面向接口编程能力不是一项可立竿见影的技能,是编程者首先具有面向接口编程的意识并长期培养的一种能力,面向接口设计能力是架构师的必备技能。
工厂模式
准备环境
本节使用攀博课堂支付接口案例讲解,详情参考 “理解面向接口编程”章节。
案例代码如下:
1、基础支付接口
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂支付基础接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface Pay {
/**
* 请求支付
* @param account
* @param money
* @return 请求结果
*/
String requestPay(String account,float money);
/**
* 查询支付结果
* @param orderId
* @return 支付结果
*/
String getResult(String orderId);
}
2、微信支付与支付宝接口
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂微信支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface WeixinPay extends Pay {
String weixinUrl = "https://";
/**
* 申请微信支付授权码
* @param account
* @param money
* @return 授权码
*/
String applyWeixinCode(String account,float money);
}
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂支付宝支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface AliPay extends Pay {
String aliPayUrl = "https://";
/**
* 申请支付宝授权码
* @param account
* @param money
* @return 授权码
*/
String applyAliCode(String account,float money);
}
3、微信支付实现
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂微信支付接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbWeixinPay implements WeixinPay {
public String requestPay(String account, float money) {
return null;
}
public String getResult(String orderId) {
return null;
}
public String applyWeixinCode(String account, float money) {
return null;
}
}
4、攀博课堂系统
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂在线教育系统启动类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbMain {
//课程管理服务
//学生管理服务
//支付服务
// PbWeixinPay pbWeixinPay = new PbWeixinPay();
private Pay pay;
public Pay getPay() {
return pay;
}
public void setPay(Pay pay) {
this.pay = pay;
}
//...
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = new PbWeixinPay();
//向pay属性赋值
pbMain.setPay(pay);
}
}
抛出问题
在攀博课堂系统中有很多地方要使用支付接口,比如:课程购买、会员赞助等,下边是其中一处使用支付接口的代码,我们思考一个问题:下边代码虽然使用Pay接口,但还是与PbWeixinPay类有耦合,代码如下:
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = new PbWeixinPay();
//向pay属性赋值
pbMain.setPay(pay);
}
因为PbWeixinPay与微信支付接口有交互,微信支付系统属于外部系统,当微信支付接口出现变更那么PbWeixinPay类也要变更,通常会增加新的接口实现,代码如下,PbWeixinPayNew类属于PbWeixinPay的2.0版本。
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = new PbWeixinPayNew();
//向pay属性赋值
pbMain.setPay(pay);
}
支付接口作为攀博课堂的公共服务接口会在很多使用支付的地方引用了PbWeixinPay类,所以一旦修改了PbWeixinPay对象的创建方式就要修改所有引用PbWeixinPay类的地方,系统的可维护性就比较差,如下图所示:
如何解决接口应用类与接口实现类之间的耦合问题呢?
我们这将构造Pay接口实现类对象的代码单独放在一个工厂,由工厂去生产,原类引用PbWeixinPay类改为引用工厂类,这样当PbWeixinPay类出现变更我们只需要更改工厂即可,伪代码如下:
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = 工厂类.getPay();
//向pay属性赋值
pbMain.setPay(pay);
}
Pay pay = 工厂类.getPay();表示从工厂中获取支付对象赋值给pay变量。
工厂模式
面对前边支付类与应用类耦合的问题我们提出使用工厂来解决,其实这并不是我们的奇思妙想,在业界早有前人提出了很多解决生产实践问题的解决方法,这些解决方法都是通过生产验证的,并且总结出的最佳的解决方案,这些在软件行业被成为设计模式,工厂模式是其中一种设计模式。
工厂模式包括简单工厂模式、工厂方法模式、抽象工厂模式,本节的重点不是学习工厂模式,其课程目标是让大家学习面向接口编程的方法,本次使用一种简单工厂模式来解决问题,下边是实现过程。
微信支付服务 类:
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂微信支付接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbWeixinPay implements WeixinPay {
public String requestPay(String account, float money) {
//过程略...
return null;
}
public String getResult(String orderId) {
//过程略...
return null;
}
public String applyWeixinCode(String account, float money) {
//过程略...
return null;
}
}
支付宝支付服务类:
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂支付宝支付接口实现类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbAliPay implements AliPay {
public String requestPay(String account, float money) {
//过程略...
return null;
}
public String getResult(String orderId) {
//过程略...
return null;
}
public String applyAliCode(String account, float money) {
//过程略...
return null;
}
}
支付服务工厂类:
package com.pbteach.javase.oop.testinterface5;
/**
* 攀博课堂支付服务工厂类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbPayFactory {
//微信支付类型
public static final String weixinPayType = "weixin";
//支付宝支付类型
public static final String aliPayType = "ali";
//根据支付类型获取支付服务对象
public static Pay getPay(String payType) {
Pay pay = null;
switch (payType) {
case PbPayFactory.weixinPayType:
pay = new PbWeixinPay();
break;
case PbPayFactory.aliPayType:
pay = new PbAliPay();
break;
default:
break;
}
return pay;
}
}
使用工厂类获取支付服务对象:
public static void main(String[] args) {
PbMain pbMain = new PbMain();
//创建PbWeixinPay对象
Pay pay = PbPayFactory.getPay(PbPayFactory.weixinPayType);
//向pay属性赋值
pbMain.setPay(pay);
}
适配器模式
什么是适配器
引用百度百科:适配器是一个接口转换器,它可以是一个独立的硬件接口设备,允许硬件或电子接口与其它硬件或电子接口相连,也可以是信息接口。比如:电源适配器、三角架基座转接部件、USB与串口的转接设备等。
比如电源适配器,将220v交流电压变为电子设备需要的电压模式:
生活中的例子还有很多,比如,你买了个港版的电脑要在国内使用就需要一个插座的转换器:
适配器也叫转换器,它的作用是将源接口转换为目标接口。
适配器模式
在软件开发中适配器模式正是借鉴了适配器的特性,它可以将一个接口转换成一个目标接口,使之满足系统的需要。
下边以攀博课堂支付系统为例说明适配器模式的使用方法:
早期攀博课堂支持银联支付方式,银联支付类如下:
package com.pbteach.javase.oop.testinterface6;
/**
* 旧的银联支付接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public class Unionpay {
/**
* 支付接口
* @param account 账号id
* @param money 金额
* @return
*/
public String pay(String account, float money) {
//过程略...
return null;
}
/**
* 获取支付结果接口
* @param account 账号id
* @param orderId 订单id
* @return
*/
public String payResult(String account,String orderId) {
//过程略...
return null;
}
}
在移动互联网的今天,攀博课堂支付系统引入微信支付、支付宝支付,并且使用标准的支付接口,如下:
package com.pbteach.javase.oop.testinterface6;
/**
* 攀博课堂支付基础接口
* @author 攀博课堂(www.pbteach.com)
*
*/
public interface Pay {
/**
* 请求支付
* @param account
* @param money
* @return 请求结果
*/
String requestPay(String account,float money);
/**
* 查询支付结果
* @param orderId
* @return 支付结果
*/
String getResult(String orderId);
}
如何让早期的银联支付类支持现有接口标准Pay呢?
按照新的支付接口重写一个银联的支付类可以吗?不合适,因为还有旧代码在用这个类,我们是想这个银联类新、旧接口都支持,就好比前边我们举的港版电脑的例子,不能因为在国内使用就把电脑硬件给改造了,如果哪天去香港了怎么办?
此问题用适配器模式即可解决,下图是适配器模式的UML类图:
适配器模式有三个角色:
Adaptee(适配者类):被适配对象,它拥有源接口,上图中旧的银联支付类为适配者类。
Target(目标接口):适配的目标接口,上图中Pay支付接口为目标接口。
Adapter(适配器):适配器角色,适配器是适配器模式的核心,它的作用是对Adaptee适配,经过适配后支持目标接口。
Adapter该如何实现呢?根据上图的指示,Adapter继承Adaptee并且实现Target目标接口,Adapter继承Adaptee即拥有了Adaptee的方法,Adapter实现Target目标即支持目标接口。
适配器代码如下:
package com.pbteach.javase.oop.testinterface6;
/**
* 银联支付适配器
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbUnionpayAdapter extends Unionpay implements Pay {
@Override
public String requestPay(String account, float money) {
//调用适配者的源接口方法
return super.pay(account, money);
}
@Override
public String getResult(String orderId) {
//根据订单id找到用户账户
String account = "u101";
//调用适配者的源接口方法
return super.payResult(account, orderId);
}
}
适配器相当于一个包装器,包装了旧的银联支付类,使之支持新的支付接口。
在支付服务工厂中使用适配器构建银联支付对象,代码如下:
package com.pbteach.javase.oop.testinterface6;
/**
* 攀博课堂支付服务工厂类
* @author 攀博课堂(www.pbteach.com)
*
*/
public class PbPayFactory {
//微信支付类型
public static final String weixinPayType = "weixin";
//支付宝支付类型
public static final String aliPayType = "ali";
//银联支付类型
public static final String unionPayType = "union";
//根据支付类型获取支付服务对象
public static Pay getPay(String payType) {
Pay pay = null;
switch (payType) {
case PbPayFactory.weixinPayType:
pay = new PbWeixinPay();
break;
case PbPayFactory.aliPayType:
pay = new PbAliPay();
break;
case PbPayFactory.unionPayType:
pay = new PbUnionpayAdapter();
break;
default:
break;
}
return pay;
}
}
适配器模式还有其它的实现方案,比如对象适配器、接口适配器等,本节内容的目标是通过接口的应用,其它适配器模式的实现方法可以自学,也可参考攀博课堂的设计模式专题课程。