IT技术博客大学习 共学习 共进步

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

外刊IT评论 2013-07-30 13:39:02 浏览 13,761 次

image3

   如果你已经读了我的前几篇关于面向对象范式因为受到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 ]

建议继续学习

  1. 我是如何学习计算机编程的 (阅读 181,020)
  2. Paypal接口详细代码(PHP版,非API接口) (阅读 19,300)
  3. 我学编程时犯的最大两个错误 (阅读 7,861)
  4. 存储基础知识之——硬盘接口简述 (阅读 7,403)
  5. PHP连贯接口 (阅读 7,300)
  6. 使用Mitmproxy分析接口 (阅读 6,901)
  7. JavaScript Interface 接口的实现 (阅读 6,880)
  8. 干嘛不去掉“I”和“Impl”? (阅读 6,622)
  9. 提高编程技能最有效的方法 (阅读 6,402)
  10. 函数式编程很难,这正是你要学习它的原因 (阅读 5,302)