适配器设计模式综合指南
设计模式是面向对象编程中的重要工具,它提供了可重用的解决方案来解决常见的软件设计问题。结构设计模式关注于类和对象之间的结构。
今天我们将讨论结构设计模式之一,即适配器设计模式。
定义
适配器模式是一种软件设计模式,它允许将现有类的接口转换为另一个接口。
适配器模式通过使用一个中间类来实现两个不兼容的接口,从而使两个接口兼容。该中间类实现了两个接口,并将来自一个接口的调用转换为另一个接口的调用。
问题
假设您正在构建一个产品源应用程序。该应用程序从不同的电子商务 API 源(例如:Shopify、BigCommerce、WooCommerce 和 Prestashop)下载产品数据,对其进行处理,并将它们保存在统一的产品表中。该表将准备好处理并发送到不同的市场。
然而,存在两个挑战:
并非所有电子商务 API 都相同。它们可能具有不同的方法、参数和返回值。 如果稍后需要添加新的产品源(例如 Magento),则需要对应用程序进行更改。
解决方案
适配器模式可以解决产品源应用程序面临的挑战。它由四个构建块组成:
客户端:这是使用 Target
接口并想要连接到多个源的类。在我们的例子中,这是ProductFeed
类。它包含了程序现有的业务逻辑。目标:定义客户端将与之交互的单个 API 的接口或合约。在我们的例子中,这是 FeedAdapterInterface
接口。适配器:实现 Target
接口的类,持有Adaptee
的实例,并将来自Target
接口的调用适配到Adaptee
的接口。在我们的例子中,这将是ShopifyApiAdapter
类。适配者:客户端想要连接的源具有不兼容的接口。所有请求都委托给适应者。在我们的例子中,我们可以有三个: ShopifyApi
、BigCommerceApi
和WooCommerceApi
。
代码
首先,我们需要定义一个客户端接口,该接口定义了客户端需要的功能。在本例中,我们需要一个 ProductFeed
接口,它具有 getFeed()
方法,该方法返回一个产品数组。
interface FeedAdapterInterface
{
/**
* @return array<int, array<string, string>>
*/
public function getFeed(): array;
}
接下来,我们需要定义适应者类。适应者类将一个不兼容的接口转换为另一个兼容的接口。在本例中,我们需要将三个 API 服务转换为 ProductFeed
接口。
// Adaptee Classes aka Service classes
class ShopifyApi {
public function fetchItems(): array {
// Returns an `array` of products, that hold a `title`, `date`, `image` and `url`.
}
}
class BigCommerceApi {
public function getProducts(): array {
// returns an array of products tha have 'name', 'published_at', 'picture_url', 'url'
}
}
class WooCommerceApi{
public function getList(): array {
// returns an array of products tha have 'name', 'published_date', 'cover', 'link'
}
}
正如您所看到的,这些 API 服务具有不同的方法名称和有效负载格式。
现在,我们将创建适配器类来实现 FeedAdapterInterface
接口的 getFeed()
方法,并将调用从客户端类 ProductFeed
委托给特定的适应者服务,例如 ShopifyApi
、BigCommerceApi
和 WooCommerceApi
。
class ShopifyApiAdapter implements FeedAdapterInterface
{
public function __construct(public ShopifyApi $api) {}
public function getFeed(): array
{
// 从 Shopify API 获取产品数据
$products = $this->api->fetchItems();
// 转换产品数据格式,使其符合 FeedAdapterInterface 的规范
return array_map(function ($item) {
return [
'title' => $item['title'],
'date' => DateTime::createFromFormat('H:i:s Y-m-d', $item['date'])->format('Y-m-d H:i:s'),
'img' => $item['image'],
'url' => $item['url'],
];
}, $products);
}
}
class BigCommerceApiAdapter implements FeedAdapterInterface
{
public function __construct(public BigCommerceApi $api) {}
public function getFeed(): array
{
// 从 BigCommerce API 获取产品数据
$products = $this->api->getProducts();
// 转换产品数据格式
return array_map(function ($item) {
return [
'title' => $item['name'],
'date' => DateTime::createFromFormat('H:i:s Y-m-d', $item['published_at'])->format('Y-m-d H:i:s'),
'img' => $item['picture_url'],
'url' => $item['url'],
];
}, $products);
}
}
class WooCommerceApiAdapter implements FeedAdapterInterface
{
public function __construct(public WooCommerceApi $api) {}
public function getFeed(): array
{
// 从 WooCommerce API 获取产品数据
$products = $this->api->getList();
// 转换产品数据格式
return array_map(function ($item) {
return [
'title' => $item['name'],
'date' => DateTime::createFromFormat('H:i:s Y-m-d', $item['published_date'])->format('Y-m-d H:i:s'),
'img' => $item['cover'],
'url' => $item['link'],
];
}, $products);
}
}
我们将创建实现 FeedAdapterInterface
接口的 ProductFeed
类。该类将使用适配器类来连接到服务类。
class ProductFeed implements FeedAdapterInterface
{
public function __construct(private array $adapters) {}
public function getFeed(): array
{
// 获取适配器列表
$adapters = is_array($this->adapters) ? $this->adapters : func_get_args();
// 循环每个适配器并获取产品数据
$products = [];
foreach ($adapters as $adapter) {
$products = array_merge($products, $adapter->getFeed());
}
return $products;
}
}
该类循环每个适配器类,并从每个适配器类获取产品数据。然后,该类将从每个适配器类获取的产品数据合并为一个数组。
用法
ProductFeed
类通过其构造函数获取适配器列表。
// 创建适配器
$shopify = new ShopifyApiAdapter();
$wooCommerce = new WooCommerceApiAdapter();
$bigCommerce = new BigCommerceApiAdapter();
// 创建 ProductFeed 实例
$productFeed = new ProductFeed([
$shopify,
$wooCommerce,
$bigCommerce,
]);
// 获取产品数据
$products = $productFeed->getAllProducts();
// 打印产品数据
foreach ($products as $product) {
var_dump($product);
}
优点
可以将接口或数据转换代码与程序的主要业务逻辑分离,提高程序的可维护性和扩展性。 可以使客户端类与特定的适配器类解耦,从而可以灵活地添加或更换不同的适配器类。 符合单一职责原则、开放/关闭原则和依赖倒置原则。
其他例子:
可以使用适配器设计模式的其他示例包括:
从不同来源获取数据,例如从 Twitter、Facebook 或 Instagram 获取新闻源。 通过不同渠道发送通知,例如通过短信、Slack 或推送通知。 使用不同的缓存驱动程序,例如 Redis 或 MemCache。 使用不同的文件存储适配器,例如本地文件系统、Amazon S3 或 Google Cloud Storage。 记录数据,例如日志记录或事件记录。 连接到不同的数据库,例如 MySQL、PostgreSQL 或 MongoDB。
发表评论