面向“接口”编程和面向“实现”编程

开发 后端 前端
如果你已经读了我的前几篇关于面向对象范式因为受到Rust and Go等语言的影响而发生变化的文章,看到了我正在研究的Rust设计模式,你会发现我对Rust语言十分的偏爱。

[[81718]]

如果你已经读了我的前几篇关于面向对象范式因为受到Rust and Go等语言的影响而发生变化的文章,看到了我正在研究的Rust设计模式,你会发现我对Rust语言十分的偏爱。

除此之外,就在上周末,我读完了经典的《设计模式:可复用面向对象软件的基础》。这些种种,引起了我对这本书中谈及的一个核心原则的思考:

面向‘接口’编程,而不是面向‘实现’。

这是什么意思?

首先我们需要理解什么是‘接口’,什么是‘实现’。简言之,一个接口就是我们要调用的一系列方法的集合,有对象将会响应这些方法调用。

一个实现就是为接口存放代码和逻辑的地方。

本质上讲,这个原则倡导的是,当我们写一个函数或一个方法时,我们应该引用相应的接口,而不是具体的实现类。

面向‘实现’编程

首先我们看看,如果不遵循这个原则会发生什么。

假设你是《华氏451度》这本书里的“Montag”这个人。大家都知道,书在华氏451度会烧着的。小说中的消防队员只要看到了书就会把它们丢到火里。我们用面向对象的视角说问题,书有一个叫做burn()的方法。

书并不是唯一会燃烧的东西。假设我们还有另外一个东西,比如木头,它也有一个方法叫做burn()。我们用Rust语言来写这段代码,看看在不是面向‘接口’编程的情况下它们是如何燃烧的。

  1. struct Book {  
  2.     title: @str,  
  3.     author: @str,  
  4. }  
  5.  
  6. struct Log {  
  7.     wood_type: @str,  
  8. }  

很直接。我们创建了两个结构体来表示一本书(Book)和一个木头(Log)。下面我们为结构体实现它们的方法:

  1. impl Log {  
  2.     fn burn(&self) {  
  3.         println(fmt!("The %s log is burning!", self.wood_type));  
  4.     }  
  5. }  
  6.  
  7. impl Book {  
  8.     fn burn(&self) {  
  9.         println(fmt!("The book %s by %s is burning!", self.title, self.author));  
  10.     }  
  11. }  

现在LogBook 都有了 burn() 方法,让我们把它们放到火上。

我们首先把木头放到火上:

  1. fn start_fire(lg: Log) {  
  2.     lg.burn();  
  3. }  
  4.  
  5. fn main() {  
  6.     let lg = Log {  
  7.         wood_type: @"Oak",  
  8.         length: 1,  
  9.     };  
  10.  
  11.     // Burn the oak log!  
  12.     start_fire(lg);  
  13. }  

非常顺利,我们得到了输出 “The Oak log is burning!”.

现在,因为我们已经写了一个 start_fire 函数,是否我们可以把书也传进去,因为它们都有 burn()。让我们试一下:

 

  1. fn main() {  
  2.     let book = Book {  
  3.         title: @"The Brothers Karamazov",  
  4.         author: @"Fyodor Dostoevsky",  
  5.     };  
  6.  
  7.     // Let's try to burn the book...  
  8.     start_fire(book);  
  9. }  

 

可行吗?不行。出现了下面的错误:

mismatched types: expected Log but found Book (expected struct Log but
found struct Book)

#p#

说的非常清楚,因为我们写出的函数需要的是一个Log结构体,而不是我们传进去的 Book 结构体。如何解决这个问题?我们可以再写一个这样的方法,把参数改成Book结构体。然而,这并不是一个好的方案。我在两个地方有了两个几乎一样的函数,如果一个修改,我们需要记得修改另外一个。

现在让我们看看面向‘接口’编程如何能解决这个问题。

面向接口编程

我们仍然使用前面的结构体,但这次我们加一个接口。在Rust语言里,接口叫做traits

  1. struct Book {  
  2.     title: @str,  
  3.     author: @str,  
  4. }  
  5.  
  6. struct Log {  
  7.     wood_type: @str,  
  8. }  
  9.  
  10. trait Burnable {  
  11.     fn burn(&self);  
  12. }  

现在,除了两个结构体外,我们又多了一个叫做Burnable的接口。它的定义里只有一个叫做burn()的方法。我们来为每个结构体实现它们的接口:

  1. impl Burnable for Log {  
  2.     fn burn(&self) {  
  3.         println(fmt!("The %s log is burning!", self.wood_type));  
  4.     }  
  5. }  
  6.  
  7. impl Burnable for Book {  
  8.     fn burn(&self) {  
  9.         println(fmt!("The book \"%s\" by %s is burning!", self.title, self.author));  
  10.     }  

看起来并没有多大的变化。这就是面向接口编程的强大之处:

  1. fn start_fire<T: Burnable>(item: T) {  
  2.     item.burn();  
  3. }  

不仅仅只能接收一个Book对象或Log对象做参数,我们可以往里面传入任何实现了 Burnable 接口的类型(我们叫它类型T)。这使得我们的主函数可以写成这样:

  1. fn main() {  
  2.     let lg = Log {  
  3.         wood_type: @"Oak",  
  4.     };  
  5.  
  6.     let book = Book {  
  7.         title: @"The Brothers Karamazov",  
  8.         author: @"Fyodor Dostoevsky",  
  9.     };  
  10.  
  11.     // Burn the oak log!  
  12.     start_fire(lg);  
  13.  
  14.     // Burn the book!  
  15.     start_fire(book);  
  16. }  

正如期望的,我们得到了下面的输出:

The Oak log is burning!

The book “The Brothers Karamazov” by Fyodor Dostoevsky is burning!

这跟我们期望的完全一致。

结论

遵循“面向‘接口’编程”原则,我们可以写出一个函数,使其能完全能复用任何实现了Burnable接口的对象。因为很多的程序员都是按小时收费的,我们写出越多可复用的代码,用于维护它们的时间就会越少,也就是更好。

因此,这是一个非常强大的编程思想。

并不是什么时候都可以面向接口编程的,但遵循这种原则会让你更容易的写出可复用的更优雅的代码。接口提供了非常优秀的抽象归纳,让我们的开发工作变得容易很多。

英文原文:Program to an Interface, Fool

译文链接:http://www.aqee.net/program-to-an-interface-fool/

责任编辑:林师授 来源: 外刊IT评论
相关推荐

2009-07-02 13:25:00

消除实现继承面向接口编程Java

2022-07-30 23:41:53

面向过程面向对象面向协议编程

2020-07-23 17:29:47

接口编程代码

2021-01-14 08:16:41

Python接口编程

2023-02-22 18:06:35

函数javascript面向对象编程

2009-06-22 11:27:59

反向控制原理面向切面编程Spring

2010-11-17 11:31:22

Scala基础面向对象Scala

2017-04-21 09:07:39

JavaScript对象编程

2012-01-17 09:34:52

JavaScript

2012-02-10 10:32:33

JavaSpring

2023-10-04 17:25:01

面向接口编程

2015-03-20 09:54:44

网络编程面向连接无连接

2023-11-07 16:00:25

面向切面编程开发

2014-05-08 14:13:00

Java面向GC

2012-02-27 09:30:22

JavaScript

2012-12-13 11:01:42

IBMdW

2009-08-24 09:46:40

面向切面编程AOP

2016-12-12 15:22:41

编程

2023-11-30 08:00:54

面向对象面向切面

2023-10-13 07:36:58

Java函数式编程
点赞
收藏

51CTO技术栈公众号