После долгих размышлений и дополнительного чтения подход, который я описал выше, более точно следует паттерну Моста.Эта реализация помогла мне увидеть недостающую часть - адаптеры!Как и говорил Массимилиано, у меня теперь есть адаптер, который находится между моим бизнес-объектом и сервисом.Адаптер отвечает за «адаптацию» POCO / DTO / Entity / ..., предоставляемой службой WCF, к / от моих бизнес-объектов.
Вместо того, чтобы мой бизнес-объект брал ссылку на службу (ITheService)в своем конструкторе теперь используется ссылка на сервисный адаптер (ITheServiceAdapter).Этот интерфейс выглядит следующим образом:
internal interface ITheServiceAdapter
{
void DoSomething();
MyBusinessObject GetData();
}
В конкретной реализации (TheServiceAdapter) я использую AutoMapper для «адаптации» серверного POCO / DTO, возвращаемого реальной службой, к моему бизнес-объекту, например:
internal class TheServiceAdapter : ITheServiceAdapter
{
private ITheService _service;
public TheServiceAdapter(ITheService service) { _service = service; }
public void DoSomething() { ... }
public MyBusinessObject GetData()
{
var data = _service.GetData();
return Mapper.Map<ServiceObject, MyBusinessObject>(data);
}
}
Это прекрасно работает и удовлетворяет моему требованию абстрагировать реализацию сервиса от моих бизнес-объектов.Единственный код, связанный с типами прокси WCF, - это адаптер.Кроме того, я все еще могу выполнять модульное тестирование своих бизнес-объектов, внедряя фиктивную реализацию сервисного адаптера.И, поскольку я решил доверять AutoMapper, мне не нужно тестировать модули классов адаптера, и я буду выявлять любые проблемы в этом коде с помощью интеграционных тестов.Итак, все хорошо - верно?
Конечно, это еще не решило вопрос инкапсуляции.К счастью, Рокфорд Лхотка (из известности CSLA) имеет большую диссертацию на эту тему в своей книге.Мое решение состояло в том, чтобы «подделать» инкапсуляцию, поместив весь этот код в отдельную сборку и предоставив установщикам внутреннюю область действия для всех свойств, которые должны показываться только для чтения для потребляющего кода.Это позволяет адаптеру устанавливать свойства, не позволяя клиентскому коду делать то же самое.
Это не идеально, но это решение.Если у вас есть другие идеи, которые кажутся вам не совсем удачными, я готов их выслушать!