虽然之前学过【策略模式】,知道要面向接口,而不是面向实现,那么真的有get到精髓吗?
接口的本质是什么?——代码世界里的契约。
接口里的一个个方法,就是契约的条款
OOP中和接口非常相似的就是抽象类,这经常被大家拿来比较,虽然我之前也写过了,但我还是想再说一遍
首先它们相似在哪?——没有具体实现逻辑!
那差在哪呢?我们先看例子先:
// 抽象类
abstract class Door {
abstract void open();
abstract void close();
}
abstract class Fish {
abstract void swim();
abstract void eat();
}
// 接口
interface IFlyable {
void fly();
}
interface IComparer {
int compare(Object a,Object b);
}
有点感觉了吧?抽象类重在它的类名本身,而接口更强调的是它里面的一个个方法,而名字只是辅助。
我们接着看:
interface IFlyable {
void fly();
}
abstract class Animal {}
class Bird extends Animal implements IFlyable {}
class Plane implements IFlyable {}
懂了吧???接口是很轻量级的,附着于主体类上的一个像装备一样辅助的东西。
而抽象类则直接说明了,你是谁谁谁的一员,是单继承,但接口你可以多实现呀
(C++和Python中,接口和抽象类是合二为一的概念,都是抽象类,我们这边主要是说OOP纯度高的,以Java为例)
但是,抽象类命名也有对行为的抽象啊,那为什么还要多抽出一个接口?
那接口怎么用呢?往下看。
该场景主要是为了有相互依赖调用关系的双(多)方能同时开发,开工前需要保证模块间能够顺利对接
案例背景:
【数据源模块】——>【数据库模块】【中间件模块】【日志模块】
(数据处理完之后,会根据业务需要写到三个模块内)
最早只能咋样,数据源模块的开发者对着另外三个模块的开发者吼:“你们都给我写个void writeResult(String data)
的方法,实现具体逻辑,方法名别错了!我就只管传数据!“这显然不合适啊,那咋办,四组开发者定个协议咯,而这个协议,就是接口。这可是一大步飞跃啊,约定好的方法名,从word文档到了代码里,不实现接口,编译器还不让你过,难道不香吗???
(其实这是【观察者模式】,数据源模块是”被观察者“,其他模块是”观察者“,Container是中介,以Container为核心,实现解耦的消息通知系统)
interface IWriteResult {
void writeResult(String data);
}
// 表示实现了这个接口的对象
IWriteResult[] observers;
void sendResult(String data){
for(IWriteResult observer:observers){
observer.writeResult(data);
}
}
writeResult()
的业务逻辑,至此,我们就完成各个模块的对接啦接口的贯穿从始至终,其价值体现在项目开发的过程中
这个场景就不是多人同时开发了,而是架构师设计框架性代码时,采用接口专攻抽象的主体逻辑
我们就拿数组的排序来说:
// 给用户制定标准,用户自定义一个排序标准
Array.sort(IComparer comparer);
// 接口定义
interface IComparer {
int compare(Object a,Object b);
}
// 内部实现
Array sort(IComparer comparer){
int length = this.elements.length;
int m = length;
for(int i=0;i<length;i++){
m = m - 1;
for(int j=0;j<m;j++){
// 最关键的抽象判断
if(comparer(this.elements[j],this.elements[j+1])>0)
swap(this.elements[j],this.elements[j+1]);
}
}
return this.elements;
}
这就是面向抽象编程,而接口就是实现这一方式的关键
我们用户就根据需要实现接口:(比方这里是年龄)
class PersonComparer implements IComparer {
public int compare(Person a,Person b){
return a.age > b.age;
}
}
说白了,IComparer
这个接口,就是为了摆脱细节实现,把这一part留给用户
这个场景主要用于不应该依赖其他底层模块的封装,一旦底层模块有调用上层模块的需求,可以借助接口抽象化
先来看个案例:
在父窗口parentWindow里嵌入一个子UI控件childControl,子控件有几个功能:
Reload菜单
Add菜单
Save菜单
// 双向引用
class ChildControl {
ParentWindow parentWindow;
void reloadClicked(){
this.parentWindow.Reload();
}
void addClicked(){
this.parentWindow.Add();
}
void saveClicked(){
this.parentWindow.Save();
}
}
class ParentWindow {
void Reload(){}
void Add(){}
void Save(){}
}
父窗口和子控件莫名其妙耦合了,而且子控件还成不了轮子,很伤的,咋办?接口!
interface IDataOperation {
void Reload();
void Add();
void Save();
}
子控件里我们只关心这个接口:(其实也就是把这三个操作具体实现的任务丢出去,我不管谁来整,反正让我鼠标敲下去有对应的正确反馈就好)
class ChildControl {
// 让父窗口去实现这个接口
IDataOperation dataOperation;
void reloadClicked() {
if(dataOperation != null)
dataOperation.Reload();
}
// 其他两个省略
}
// 父窗口中实现这个接口
class ParentWindow implements IDataOperation{
// 接受子控件的接口实现任务
ChildControl childControl;
void Reload(){
this.dataArr = getDataFromDB();
}
// 其他两个省略
// 交付任务
this.childControl.dataOperation = this;
}
经常用于相互引用的耦合,我中有你,你中有我的场景
原来子控件的模块,还需要考虑父窗口怎么处理,有哪些方法;现在相反,由子控件来整个接口丢出去,让父窗口来实现
这样的接口具有特殊性,为了满足业务特定需求的,实现模块间解耦
同样的应用,可以参考model层和controller层,model层会调用controller层的方法(事件通知),但它并不知道controller层的信息,一样也是为了解耦。
接口的语法是简单,但是难在用法太花太多了!!!
有人觉得很像,有人觉得是两个东西,仁者见仁吧
以C为例:
int (*calculate)(int,int) = add;
int sum = (*calculate)(100,200);
函数指针也有个特点:自己不做决定,放权给别人来做
函数指针能做的,接口也能做(就是累点);除了OOP特性的东西,函数指针也能做接口的东西,好似两个亲兄弟
接口的业务承载能力明显比函数指针强,而函数指针更强调函数结构的定义
优势一:函数指针不是OOP的产物,而接口是OOP的高级抽象
优势二:接口是个很好的“协议”
优势一:函数指针比接口还轻量
优势二:接口可能会造成空函数
优势三:接口比函数指针更加笨重,函数指针适合业务语境很少的环境
假如有个接口A和一个接口B,A中有9个方法要实现,B中只有1个,但它们的地位却看似相等?B中的那个方法而且很可能只是主体类的一个边边角角小喽啰,没必要大动干戈,在主体类中去掉B接口,用函数指针不香吗?
而可惜的是Java没有,而C#是有的,但是…Java有Lambda表达式
为了模仿函数指针,JDK1.8增加了函数式编程新特性——Lambda表达式
interface ILambda {
void out();
}
public class Test {
public static void main(String[] args) {
// 匿名内部类
ILambda i1 = new ILambda(){
@Override
public void out() {
System.out.println("i1");
}
};
i1.out();
// Lambda表达式
ILambda i2 = ()->{
System.out.println("i2");
};
i2.out();
// 简化(如果只有一行)
ILambda i3 = ()->System.out.println("i3");
i3.out();
}
}