设计模式:适配器模式
在上一篇文章中,我们介绍了外观模式如何只用一个简单的外观类来简化任何庞大而复杂的系统工作。
在这篇文章中,我们将继续讨论设计模式---适配器模式。 当你的代码依赖于一些外部的API或者其它的代码改变很频繁时,可以使用这个特定的模式来解决问题。这种模式属于“结构模式”的范畴,因为它教导我们的代码和类如何构造来更容易地管理和扩展。
再次重申,设计模式对于传统的类并没有什么新东西,相反,它展示给我们一种更好地构造我们的类,控制它们的行为和管理它们的创建的方式。
注意,下面的例子,都是用PHP语言来说明的
问题
class Twitter {
public function __construct() {
// Your Code here //
}
public function send($msg) {
// Posting to Twitter //
echo $msg;
}
}
$twitter = new Twitter();
$twitter->send('Posting on Twitter');
在上面的代码中,你可以看到,我们正在利用Twitter类简单地发送消息。在这里,我们直接创造Twitter API的对象,并在Twitter类上发布tweet。你会在很多地方使用这些代码。
前段时间,Twitter把这个send API方法名改为了 sendTweet。这清楚的表明,对于像我们这样使用这个 send 方法的开发者来说是个大问题。具体来说,我们需要改变所有的send 方法名 为 sendTweet。 想象一下我们需要修改的大量的代码,以及需要对每个功能重新测试一遍需要花费的时间。
解决办法
解决这个问题的一个方法是使用适配器模式。
根据维基百科上:
在软件工程中,适配器模式是一种软件设计模式,它允许通过另一个接口使用一个现有的类的接口。它经常被用来使现有的类与其它类一起合作,而无需修改其源代码。
这种情况下,我们应该创建一个包装接口来让它变成可能。我们将不改变任何外部类库的代码,因为我们控制不了它们,而且这些外部类库也会随时改变。
让我们深入到现在的代码,它显示了运行中的适配器模式:
// Concrete Implementation of Twitter Class
class Twitter {
public function __construct() {
// Your Code here //
}
public function send($msg) {
// Posting to Twitter //
echo $msg;
}
}
// Simple Interface for each Adapter we create
interface socialAdapter {
public function send($msg);
}
class twitterAdapter implements socialAdapter {
private $twitter;
public function __construct(Twitter $twitter) {
$this->twitter = $twitter;
}
public function send($msg) {
$this->twitter->send($msg);
}
}
查看上面的代码,你应该可以知道我们没有改变主的 Twitter 类,而是为我们的 social adapter 创建了一个接口,以及一个给Twitter的适配器。
随后,我们将使用这个适配器类,而不是直接使用Twitter类。当创建一个适配器类时,我们将一个主的Twitter类作为参数传递进去,所以这个适配器类有一个主类的引用,它可以调用主的Twitter类的方法。
让我们看看我们如何能够直接利用这个方法:
// Client Code
$twitter = new twitterAdapter(new Twitter());
$twitter->send('Posting to Twitter');
现在想象一下Twitter把send 方法改为了 sendTweet方法。然后我们仅仅需要在twitterAdapter中改变相应的名称即可。看一下下面的代码,仅仅一个改变:
class twitterAdapter implements socialAdapter {
private $twitter;
public function __construct(Twitter $twitter) {
$this->twitter = $twitter;
}
public function send($msg) {
$this->twitter->sendTweet($msg);
}
}
添加一个新的适配器
在这一点上,我们已经看到我们如何使用适配器设计模式克服了上述情况。现在,它很容易添加一个新类依赖于现有的适配器上。比方说,Facebook有一个状态更新的API。
同样的,我们应用一个跟Twitter适配器模式一样的适配器,而不是直接使用Facebook类。
// Concrete Implementation of Twitter Class
class Facebook {
public function __construct() {
// Your Code here //
}
public function updateStatus($msg) {
// Posting to Facebook //
echo $msg;
}
}
// Facebook Adapter
class facebookAdapter implements socialAdapter {
private $facebook;
public function __construct(Facebook $facebook) {
$this->facebook = $facebook;
}
public function send($msg) {
$this->facebook->updateStatus($msg);
}
}
// Client Code
$facebook = new facebookAdapter(new Facebook());
$facebook->send('Posting to Facebook');
如你所看到的,应用了同样的原则。你定义了一个可用于第三方类的方法,如果一个依赖改变了它的API,你只需要改变这个依赖类,而不用暴露它的外部的接口。
总结
一个伟大的应用程序正在不断挂接到其它库和API,所以我建议我们实行适配器的方法,这样当一个第三方API或库改变了它的代码,我们就不会遇到任何麻烦。
我已经尽了最大努力提供一个基本的,但有用的例子来证明适配器设计模式,但如果您有其他意见或问题,请不要犹豫,将它们添加下面的饲料中。
更新(个人添加)
在作者发布这篇文章后,有一个回复指出了,前面的适配器中,不应该在创建时,传递特定的第三方类(如 new Twitter(),new Facebook())。也就是不要在适配器中,加入如下的构造方法,这样不是很优雅。
public function __construct(Facebook $facebook) {
因为这样,我们的适配器就依赖于特定的外部类了。回复者还贴出了自己的代码:
interface SocialNetworkAdapter {
public function send($message);
public function read($postId); // just an example
}
class TwitterAdapter implements SocialNetworkAdapter { ... }
class FacebookAdapter implements SocialNetworkAdapter { ... }
class LinkedinAdapter implements SocialNetworkAdapter { ... }
class SocialNetworksHolder {
public function add(SocialNetworkAdapter $adapter) {}
public function sendToAll() {}
}
我们直接通过SocialNetworksHolder 来处理所有的发送请求。