多异构系统通信:适配不同协议的设计模式与抽象方案咨询
Absolutely—there are several design patterns that let you abstract away the differences between these systems without forcing awkward, one-size-fits-all interfaces. The Adapter Pattern is the perfect starting point here, and you can pair it with a few complementary patterns to keep your code clean and maintainable.
Here's how to approach it:
1. Define a Common Interface Based on Your Business Needs
First, forget about the protocols (REST, NET.TCP) for a minute. Focus on what you need to do with these systems—e.g., fetch user data, submit orders, retrieve metrics. Create an abstract interface that represents these operations, not the specific methods each system exposes.
For example:
public interface ISystemIntegrationClient { Task<DomainData> FetchCriticalDataAsync(string requestIdentifier); Task<bool> SubmitProcessedDataAsync(DomainData data); }
DomainData here is your application's common data model—this is what your core code will work with, not the system-specific formats.
2. Build Adapters for Each System
Each adapter implements the ISystemIntegrationClient interface, handling all protocol-specific communication and data conversion internally. This way, your core application never has to deal with REST endpoints, TCP streams, or system-specific DTOs.
Example: REST API Adapter
public class RestSystemAdapter : ISystemIntegrationClient { private readonly HttpClient _httpClient; public RestSystemAdapter(HttpClient httpClient) { _httpClient = httpClient; // Configure base address, auth headers, etc., here } public async Task<DomainData> FetchCriticalDataAsync(string requestIdentifier) { // Call the REST API var response = await _httpClient.GetAsync($"api/data/{requestIdentifier}"); response.EnsureSuccessStatusCode(); // Parse the system-specific REST DTO var restDto = await response.Content.ReadFromJsonAsync<RestSystemDataDto>(); // Convert to your common DomainData model return new DomainData { Id = restDto.UniqueId, Value = restDto.MetricValue, RecordedAt = restDto.CreatedTimestamp }; } public async Task<bool> SubmitProcessedDataAsync(DomainData data) { // Convert DomainData to the REST system's expected DTO var restDto = new RestSystemSubmitDto { Id = data.Id, UpdatedValue = data.Value, UpdatedTimestamp = data.RecordedAt }; var response = await _httpClient.PostAsJsonAsync("api/submit", restDto); return response.IsSuccessStatusCode; } }
Example: NET.TCP Adapter
public class NetTcpSystemAdapter : ISystemIntegrationClient { private readonly TcpClient _tcpClient; private readonly NetworkStream _networkStream; public NetTcpSystemAdapter(string host, int port) { _tcpClient = new TcpClient(host, port); _networkStream = _tcpClient.GetStream(); } public async Task<DomainData> FetchCriticalDataAsync(string requestIdentifier) { // Send request over TCP (custom payload format) var requestBytes = Encoding.UTF8.GetBytes($"FETCH:{requestIdentifier}"); await _networkStream.WriteAsync(requestBytes, 0, requestBytes.Length); // Read and parse the TCP response var buffer = new byte[1024]; var bytesRead = await _networkStream.ReadAsync(buffer, 0, buffer.Length); var rawResponse = Encoding.UTF8.GetString(buffer, 0, bytesRead); var tcpDto = ParseTcpResponse(rawResponse); // Convert to DomainData return new DomainData { Id = tcpDto.RequestId, Value = tcpDto.DataPoint, RecordedAt = tcpDto.Timestamp }; } public async Task<bool> SubmitProcessedDataAsync(DomainData data) { // Convert DomainData to TCP-specific payload var payload = $"SUBMIT:{data.Id}|{data.Value}|{data.RecordedAt:o}"; var payloadBytes = Encoding.UTF8.GetBytes(payload); await _networkStream.WriteAsync(payloadBytes, 0, payloadBytes.Length); // Wait for acknowledgment var ackBuffer = new byte[32]; var ackBytesRead = await _networkStream.ReadAsync(ackBuffer, 0, ackBuffer.Length); var acknowledgment = Encoding.UTF8.GetString(ackBuffer, 0, ackBytesRead); return acknowledgment.Equals("OK", StringComparison.OrdinalIgnoreCase); } private TcpSystemDataDto ParseTcpResponse(string rawResponse) { // Implement parsing logic for your TCP system's payload format var parts = rawResponse.Split('|'); return new TcpSystemDataDto { RequestId = parts[0], DataPoint = decimal.Parse(parts[1]), Timestamp = DateTimeOffset.Parse(parts[2]) }; } }
3. Complementary Patterns to Enhance This Setup
- Factory Pattern: If you need to dynamically select which adapter to use (e.g., based on configuration), create a factory that returns the correct
ISystemIntegrationClientinstance.public class SystemClientFactory { public ISystemIntegrationClient CreateClient(string systemType) { return systemType switch { "RestSystemA" => new RestSystemAdapter(new HttpClient { BaseAddress = new Uri("https://systema.example.com") }), "RestSystemB" => new RestSystemAdapter(new HttpClient { BaseAddress = new Uri("https://systemb.example.com") }), "NetTcpSystem" => new NetTcpSystemAdapter("tcpsystem.example.com", 1234), _ => throw new ArgumentOutOfRangeException(nameof(systemType)) }; } } - Dependency Injection: Register your adapters with your DI container (e.g., ASP.NET Core's built-in DI) so that your application code can receive the correct client via constructor injection, rather than creating instances directly.
Key Takeaways
- You don't need a "generic type with different methods"—instead, abstract the business operations and let adapters handle the protocol-specific details.
- Data conversion is necessary, but encapsulating it within adapters keeps your core code clean and decoupled from external systems.
- This approach makes it easy to add new systems later: just create a new adapter that implements
ISystemIntegrationClientwithout changing any existing code (following the Open/Closed Principle).
内容的提问来源于stack exchange,提问作者Lucian

