what is Intermediary in software design?
The Use an Intermediar tactic is a software design approach where a middle layer (intermediary) is introduced between two or more components to manage their interactions. Instead of components directly communicating with each other, they interact through the intermediary. This intermediary can handle tasks like coordination, transformation, or decoupling, making the system more modular and flexible.
The intermediary acts as a mediator or abstraction layer that reduces dependencies between components. This allows components to evolve independently, making the system easier to maintain, test, and extend.
when to use it?
1. To Reduce Tight Coupling
- If two components are tightly coupled (e.g., one directly depends on the implementation of the other), introducing an intermediary can decouple them.
- Example: A message broker (like RabbitMQ or Kafka) decouples producers and consumers in a publish-subscribe system.
2. To Handle Complex Interactions
- When multiple components need to interact in a coordinated way, an intermediary can centralize and simplify the interaction logic.
- Example: An API Gateway in a microservices architecture routes requests to the appropriate service.
3. To Resolve Incompatibilities
- If two components use different protocols, data formats, or interfaces, an intermediary can act as a translator or adapter.
- Example: A protocol translator converts data between two incompatible systems.
4. To Improve Scalability
- An intermediary can help distribute workloads or manage communication in a way that improves scalability.
- Example: A load balancer distributes incoming requests across multiple servers.
5. To Enable Loose Coupling in Distributed Systems
- In distributed systems, intermediaries like service discovery tools or shared data repositories help components find and interact with each other without hardcoding dependencies.
6. To Enhance Maintainability
- If you anticipate frequent changes in one component, an intermediary can isolate those changes from other components.
- Example: A repository pattern abstracts database access, so changes to the database don’t affect the business logic.
中介的分类
中介可以解决不同类型的依赖关系。
Dependency Type | Example Intermediaries |
---|---|
数据生产者和数据消费者之间的依赖 | 发布-订阅总线 Publish-Subscribe Bus, 共享数据存储库 Shared Data Repository,动态服务发现 Dynamic Service Discovery |
语法和语义差异 Forms of Syntactic and Data Semantic Distance | 数据转换器 Data Transformers, 协议翻译器 Protocol Translators |
Examples
Publish-Subscribe Pattern Example
发布者 和 subscirbe解耦
// Message broker intermediary
class MessageBroker {
public:
template<typename T>
void publish(const std::string& topic, const T& data) {
for (const auto& callback : subscribers[topic]) {
callback(data);
}
}
template<typename Callback>
void subscribe(const std::string& topic, Callback callback) {
subscribers[topic].push_back(callback);
}
private:
std::map<std::string, std::vector<std::function<void(const void*)>>> subscribers;
};
// Publisher
class WeatherStation {
private:
// 为什么发布者需要持有中介者的指针?
// 因为在过去,发布者持有订阅者的指针,现在替换为中介者
std::shared_ptr<MessageBroker> broker;
public:
void updateWeatherData(const WeatherData& data) {
broker->publish("weather.updates", data);
// Publisher doesn't need to know about subscribers
}
};
// Subscriber
class WeatherDisplay {
public:
// 为什么订阅者不需要持有中介者的指针?
// 1, 订阅者只需一次性注册
// 订阅者只需要在初始化时向中介者注册自己的回调函数或指针。注册完成后,中介者会缓存订阅者的信息。
// 当发布者发布消息时,中介者会自动调用订阅者的回调函数,订阅者无需主动与中介者交互
// 2,中介者的职责是转发消息
// 中介者的主要职责是转发消息,而不是提供额外的功能。对于订阅者来说,中介者的其他信息(如订阅者列表)没有意义,因此订阅者不需要持有中介者的指针。
WeatherDisplay(std::shared_ptr<MessageBroker> broker) {
broker->subscribe("weather.updates",
[this](const WeatherData& data) { handleWeatherUpdate(data); });
}
};
数据库连接池(Connection Pool)
解耦了 数据库 和 数据库的使用者
class ConnectionPool {
public:
Connection getConnection() {
std::lock_guard<std::mutex> lock(mtx_);
if (pool_.empty()) {
return createNewConnection();
}
Connection conn = pool_.front();
pool_.pop();
return conn;
}
void releaseConnection(Connection conn) {
std::lock_guard<std::mutex> lock(mtx_);
pool_.push(conn);
}
private:
std::queue<Connection> pool_;
std::mutex mtx_;
};
// 服务通过连接池获取数据库连接,而不是直接连接
ConnectionPool pool;
Connection conn = pool.getConnection();
// 使用conn查询数据库...
pool.releaseConnection(conn);
Repository Pattern Example
// Repository interface as intermediary
class ICustomerRepository {
public:
virtual Customer getById(int id) = 0;
virtual void save(const Customer& customer) = 0;
virtual ~ICustomerRepository() = default;
};
class CustomerService {
private:
std::unique_ptr<ICustomerRepository> repository;
public:
CustomerService(std::unique_ptr<ICustomerRepository> repo)
: repository(std::move(repo)) {
// Service doesn't need to know about database details
}
};
Adapter Pattern Example
// Third-party payment service
class PayPalAPI {
public:
virtual void makePayment(const PayPalPayload& payload) = 0;
};
// Adapter as intermediary
class PaymentAdapter {
private:
std::shared_ptr<PayPalAPI> paymentService;
public:
PaymentAdapter(std::shared_ptr<PayPalAPI> service)
: paymentService(service) {}
bool processPayment(const Order& order) {
// Converts internal order format to PayPal format
PayPalPayload payload = transformToPayPalFormat(order);
paymentService->makePayment(payload);
return true;
}
private:
PayPalPayload transformToPayPalFormat(const Order& order) {
// Transform logic here
PayPalPayload payload;
return payload;
}
};
第二种类型(数据转换,协议转换)的中介 Example
协议翻译器(Protocol Translator)
// 内部订单数据结构
struct InternalOrder {
int orderId;
std::string customerName;
double amount;
};
// 外部服务所需的订单数据结构
struct ExternalOrder {
std::string id;
std::string client;
std::string totalAmount;
};
// 协议翻译器类
class ProtocolTranslator {
public:
// 将内部订单转换为外部订单
ExternalOrder translateToExternal(const InternalOrder& internalOrder) {
ExternalOrder externalOrder;
externalOrder.id = std::to_string(internalOrder.orderId);
externalOrder.client = internalOrder.customerName;
externalOrder.totalAmount = formatAmount(internalOrder.amount);
return externalOrder;
}
private:
// 格式化金额为字符串
std::string formatAmount(double amount) {
std::ostringstream oss;
oss.precision(2);
oss << std::fixed << amount;
return oss.str();
}
};
// 示例:使用协议翻译器
int main() {
InternalOrder internalOrder = {123, "Alice", 250.75};
ProtocolTranslator translator;
ExternalOrder externalOrder = translator.translateToExternal(internalOrder);
// 输出转换后的外部订单
std::cout << "External Order ID: " << externalOrder.id << std::endl;
std::cout << "Client: " << externalOrder.client << std::endl;
std::cout << "Total Amount: " << externalOrder.totalAmount << std::endl;
return 0;
}
问题: 数据转换逻辑的放置位置
假设数据A在表示层(Presentation Layer),数据B在持久化层(Persistence Layer)
原则
单一职责原则:转换逻辑应独立于业务逻辑和数据访问逻辑。
高内聚低耦合:避免将转换代码分散在各层。
最佳实践
方案1:独立适配层(Adapter Layer)
在表示层和业务逻辑层之间插入一个适配层,专门处理数据转换。
优点:转换逻辑集中管理,易于维护和测试。
class PresentationToBusinessAdapter {
public:
static BusinessData convertToBusinessFormat(const PresentationData& uiData) {
BusinessData bizData;
// 转换逻辑(如日期格式、字段映射)
return bizData;
}
};
方案2:防腐层(Anti-Corruption Layer)
在系统边界(如集成第三方服务时)设置防腐层,隔离外部数据模型与内部模型。
// 防腐层(隔离外部支付接口)
class PaymentGatewayACL {
public:
static InternalPaymentRequest convertFromExternalFormat(const ExternalPaymentRequest& req) {
InternalPaymentRequest internalReq;
// 转换外部API的字段到内部模型
return internalReq;
}
};
方案3:数据访问层的转换
如果数据转换仅涉及持久化格式(如数据库表结构到对象映射),可使用ORM工具(如Hibernate)或DAO层的转换代码
class UserDAO {
public:
UserEntity convertToEntity(const UserDTO& dto) {
UserEntity entity;
entity.setName(dto.username);
entity.setBirthDate(formatDate(dto.birthday)); // 转换日期格式
return entity;
}
};
场景 | 推荐方案 | 示例 |
---|---|---|
外部系统集成(如第三方API) | 防腐层(Anti-Corruption Layer) | 支付网关、社交媒体登录接口 |
前后端数据交互(如REST API) | 适配层(Adapter Layer) | 将前端JSON请求转换为业务层DTO |
数据库表结构到对象映射 | DAO层或ORM工具 | 将数据库日期字段转换为业务对象的时间戳 |
跨团队协作的微服务架构 | 独立转换服务(如ETL工具) | Apache Kafka + 数据转换流 |
总结
- 数据转换器是解决语法和语义差异的通用手段,核心是标准化接口。
- 转换逻辑的位置取决于系统架构,目标是保持各层职责单一,避免污染核心业务逻辑。