Это утечка абстракции, если реализация интерфейса вызывает Dispose

Рассмотрим этот код:

public class MyClass()
{
  public MyClass()
  {    
  }

  public DoSomething()
  {
    using (var service = new CustomerCreditServiceClient())
    {
       var creditLimit = service.GetCreditLimit(
         customer.Firstname, customer.Surname, customer.DateOfBirth);       
    }
  }
}

Теперь мы хотим реорганизовать его, чтобы объединить. Мы заканчиваем с этим:

public class MyClass()
{
  private readonly ICustomerCreditService service;

  public MyClass(ICustomerCreditService service)
  {
     this.service= service;
  }

  public DoSomething()
  {
     var creditLimit = service.GetCreditLimit(
       customer.Firstname, customer.Surname, customer.DateOfBirth);       
  }
}

Выглядит хорошо, верно? Теперь любая реализация может использовать интерфейс и все хорошо.

Что, если я сейчас скажу, что реализация является классом WCF и что оператор using до рефакторинга был выполнен по какой-то причине. т.е. / чтобы закрыть соединение WCF.

Так что теперь наш интерфейс должен реализовать Dispose вызов метода или мы используем фабричный интерфейс, чтобы получить реализацию и поместить в нее оператор использования.

Для меня (хотя и новичок в этой теме) это выглядит как дырявая абстракция. Нам нужно помещать вызовы методов в наш код только ради того, как реализация обрабатывает вещи.

Может ли кто-нибудь помочь мне понять это и подтвердить, прав я или нет.

Спасибо

5 ответов

Решение

Да, это утечка абстракции, когда вы позволяете ICustomerCreditService воплощать в жизнь IDisposable, так как вы сейчас написали ICustomerCreditService с конкретной реализацией в виду. Более того, это сообщает потребителю этого интерфейса, что он может распоряжаться этой услугой, что может быть неверно, особенно потому, что в целом ресурс должен распоряжаться тем, кто его создает (у которого есть владелец). Когда вы внедряете ресурс в класс (например, с помощью инжектора конструктора), неясно, предоставлен ли потребитель владельцу.

Таким образом, в целом, ответственный за создание этого ресурса должен распоряжаться им.

Однако, в вашем случае, вы можете просто предотвратить это, внедрив одноразовую реализацию ICustomerCreditServiceClient он просто создает и удаляет клиента WCF в рамках одного и того же вызова метода. Это делает все намного проще:

public class WcfCustomerCreditServiceClient
    : ICustomerCreditServiceClient
{
    public CreditLimit GetCreditLimit(Customer customer)
    {
        using (var service = new CustomerCreditServiceClient())
        {
            return service.GetCreditLimit(customer.Firstname,
                customer.Surname, customer.DateOfBirth);       
        }
    }
}

Вы должны позвонить утилизировать ICustomerCreditService где он был создан как MyClass теперь не имеет представления о жизненном цикле ICustomerCreditService,

Вы должны справиться с жизненным циклом customerCreditService в коде вызова. Как должен MyClass знать, если услуга все еще нужна вызывающей стороне? Если вызывающий отвечает за очистку своих ресурсов, то MyClass не должен быть одноразовым.

// calling method

using (var service = new CustomerCreditServiceClient()) {
    var myClass = new MyClass(service);
    myClass.DoSomething();
}

Обновление: в комментариях OP упоминается использование IoC, подобного Ninject. Код может выглядеть так:

IKernel kernel = ...;

using (var block = kernel.BeginBlock())
{
    var service = block.Get<ICustomerCreditService>();
    var myClass = new MyClass(service);
    myClass.DoSomething();
}

kernel.BeginBlock() создает блок активации. Он гарантирует, что разрешенные экземпляры удаляются по окончании блока.

Начиная снова с первой реализации, я попытался бы добавить getInterface-Request к классу, чтобы реализация могла остаться более или менее такой же. Тогда можно смело звонить Dispose (фактически он только откладывает создание реализации интерфейса, но контролирует его жизненный цикл): (C#-код не проверен...)

public class MyClass()
{
  public delegate ICustomerCreditService InterfaceGetter;
  private InterfceGetter getInterface;
  public MyClass(InterfaceGetter iget)
  {
    getInterface = iget;
  }
  public DoSomething()
  {
    using (var customerCreditService = getInterface())
    {
       var creditLimit = customerCreditService.GetCreditLimit(customer.Firstname, customer.Surname, customer.DateOfBirth);       
    }
  }
}

Да, это. Но это неизбежное зло. Само существование IDisposable Интерфейс является дырявой абстракцией. Утечка абстракций - это просто повседневный факт программирования. Избегайте их, где это возможно, но не волнуйтесь, когда не можете - они в любом случае повсеместны.

Другие вопросы по тегам