面向“接口”编程和面向“实现”编程
如果你已经读了我的前几篇关于面向对象范式因为受到Rust and Go等语言的影响而发生变化的文章,看到了我正在研究的Rust设计模式,你会发现我对Rust语言十分的偏爱。
除此之外,就在上周末,我读完了经典的《设计模式:可复用面向对象软件的基础》。这些种种,引起了我对这本书中谈及的一个核心原则的思考:
面向‘接口’编程,而不是面向‘实现’。
这是什么意思?
首先我们需要理解什么是‘接口’,什么是‘实现’。简言之,一个接口就是我们要调用的一系列方法的集合,有对象将会响应这些方法调用。
一个实现就是为接口存放代码和逻辑的地方。
本质上讲,这个原则倡导的是,当我们写一个函数或一个方法时,我们应该引用相应的接口,而不是具体的实现类。
面向‘实现’编程
首先我们看看,如果不遵循这个原则会发生什么。
假设你是《华氏451度》这本书里的“Montag”这个人。大家都知道,书在华氏451度会烧着的。小说中的消防队员只要看到了书就会把它们丢到火里。我们用面向对象的视角说问题,书有一个叫做burn()的方法。
书并不是唯一会燃烧的东西。假设我们还有另外一个东西,比如木头,它也有一个方法叫做burn()。我们用Rust语言来写这段代码,看看在不是面向‘接口’编程的情况下它们是如何燃烧的。
struct Book {
title: @str,
author: @str,
}
struct Log {
wood_type: @str,
}
很直接。我们创建了两个结构体来表示一本书(Book)和一个木头(Log)。下面我们为结构体实现它们的方法:
impl Log {
fn burn(&self) {
println(fmt!("The %s log is burning!", self.wood_type));
}
}
impl Book {
fn burn(&self) {
println(fmt!("The book %s by %s is burning!", self.title, self.author));
}
}
现在Log 和 Book 都有了 burn() 方法,让我们把它们放到火上。
我们首先把木头放到火上:
fn start_fire(lg: Log) {
lg.burn();
}
fn main() {
let lg = Log {
wood_type: @"Oak",
length: 1,
};
// Burn the oak log!
start_fire(lg);
}
非常顺利,我们得到了输出 “The Oak log is burning!”.
现在,因为我们已经写了一个 start_fire 函数,是否我们可以把书也传进去,因为它们都有 burn()。让我们试一下:
fn main() {
let book = Book {
title: @"The Brothers Karamazov",
author: @"Fyodor Dostoevsky",
};
// Let's try to burn the book...
start_fire(book);
}
可行吗?不行。出现了下面的错误:
mismatched types: expected Log but found Book (expected struct Log but
found struct Book)
说的非常清楚,因为我们写出的函数需要的是一个Log结构体,而不是我们传进去的 Book 结构体。如何解决这个问题?我们可以再写一个这样的方法,把参数改成Book结构体。然而,这并不是一个好的方案。我在两个地方有了两个几乎一样的函数,如果一个修改,我们需要记得修改另外一个。
现在让我们看看面向‘接口’编程如何能解决这个问题。
面向接口编程
我们仍然使用前面的结构体,但这次我们加一个接口。在Rust语言里,接口叫做traits:
struct Book {
title: @str,
author: @str,
}
struct Log {
wood_type: @str,
}
trait Burnable {
fn burn(&self);
}
现在,除了两个结构体外,我们又多了一个叫做Burnable的接口。它的定义里只有一个叫做burn()的方法。我们来为每个结构体实现它们的接口:
impl Burnable for Log {
fn burn(&self) {
println(fmt!("The %s log is burning!", self.wood_type));
}
}
impl Burnable for Book {
fn burn(&self) {
println(fmt!("The book "%s" by %s is burning!", self.title, self.author));
}
}
看起来并没有多大的变化。这就是面向接口编程的强大之处:
fn start_fire<T: Burnable>(item: T) {
item.burn();
}
不仅仅只能接收一个Book对象或Log对象做参数,我们可以往里面传入任何实现了 Burnable 接口的类型(我们叫它类型T)。这使得我们的主函数可以写成这样:
fn main() {
let lg = Log {
wood_type: @"Oak",
};
let book = Book {
title: @"The Brothers Karamazov",
author: @"Fyodor Dostoevsky",
};
// Burn the oak log!
start_fire(lg);
// Burn the book!
start_fire(book);
}
正如期望的,我们得到了下面的输出:
The Oak log is burning!
The book “The Brothers Karamazov” by Fyodor Dostoevsky is burning!
这跟我们期望的完全一致。
结论
遵循“面向‘接口’编程”原则,我们可以写出一个函数,使其能完全能复用任何实现了Burnable接口的对象。因为很多的程序员都是按小时收费的,我们写出越多可复用的代码,用于维护它们的时间就会越少,也就是更好。
因此,这是一个非常强大的编程思想。
并不是什么时候都可以面向接口编程的,但遵循这种原则会让你更容易的写出可复用的更优雅的代码。接口提供了非常优秀的抽象归纳,让我们的开发工作变得容易很多。
:)
本文由外刊IT评论网(www.aqee.net)原创发表,文章地址:面向“接口”编程和面向“实现”编程,[英文原文:Program to an Interface, Fool ]
建议继续学习:
- Paypal接口详细代码(PHP版,非API接口) (阅读:18377)
- 我学编程时犯的最大两个错误 (阅读:6935)
- PHP连贯接口 (阅读:6384)
- 存储基础知识之——硬盘接口简述 (阅读:6156)
- JavaScript Interface 接口的实现 (阅读:5835)
- 干嘛不去掉“I”和“Impl”? (阅读:5691)
- 提高编程技能最有效的方法 (阅读:5579)
- 使用Mitmproxy分析接口 (阅读:5384)
- 我是如何学习计算机编程的 (阅读:4800)
- 函数式编程很难,这正是你要学习它的原因 (阅读:4505)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Aqee 来源: 外刊IT评论
- 标签: 接口 编程
- 发布时间:2013-07-30 13:39:02
- [55] Oracle MTS模式下 进程地址与会话信
- [54] IOS安全–浅谈关于IOS加固的几种方法
- [53] 如何拿下简短的域名
- [52] android 开发入门
- [52] Go Reflect 性能
- [51] 图书馆的世界纪录
- [49] 读书笔记-壹百度:百度十年千倍的29条法则
- [47] 【社会化设计】自我(self)部分――欢迎区
- [39] 程序员技术练级攻略
- [32] 视觉调整-设计师 vs. 逻辑