Тип литья с рисунком декоратора - PullRequest
1 голос
/ 30 апреля 2020

У меня есть интерфейс двигателя и различные реализации интерфейса, определяемые поставщиком c.

public interface IMotor
{
  void Home();
  void Move();
}

public class VenodorAMotor : IMotor
{
  public void Home()
  { /*home motor*/  }

  public void Move()
  { /*move motor*/  }     
}

Я украшаю IMotor, чтобы добавить некоторые функциональные возможности в его домашний метод.

public class HomeAdjustmentDecorator : IMotor
{
  private IMotor decoratedMtr;

  public static explicit operator VendorAMotor(HomeAdjustmentDecorator mtr) 
  { return (VendorAMotor)mtr.decoratedMtr; }

  public HomeAdjustmentDecorator(IMotor mtr)
  { 
    if(! mtr is VenodorAMotor)
      throw new Exception("HomeAdjustmentDecorator can only decorate VenodorAMotor objects");
    decoratedMtr = mtr; 
  }

  public void Home()
  {
    decoratedMtr.Home();
    double adjustment = ((VendorAMotor)myMotor).GetHomeAdjustment();//vendor-specific functionality
    Log(adjustment);
  }

  public void Move()
  { decoratedMtr.Move();  }  
}

У меня нет контроля над созданием VendorAMotor, поэтому я не могу легко использовать наследование.

Проблема заключается в существующем коде, который уже переводит неокрашенный IMotor в свой реализующий класс для получения доступа к другим функциональным возможностям c, определяемым поставщиком. Приведение не выполняется, поскольку HomeAdjustmentDecorator не наследует VenodorAMotor.

IMotor myDecoratedMotor;//Implementation is HomeAdjustmentDecorator composed of VendorAMotor
...
((VendorAMotor)myDecoratedMotor).DoOtherVendorSpecificStuff();

Я попытался переопределить оператор приведения, как показано, но это не работает. Я получаю InvalidCastExeption: «Невозможно привести объект типа« HomeAdjustmentDecorator »к типу« VenodorAMotor ».»
Все компилируется нормально. Явное переопределение оператора приведения не выполняется, точка останова не достигает. Вопрос в том, не выполнен ли этот переопределение приведения?

Ближайший подобный вопрос я нашел здесь: Украшение с несколькими интерфейсами , но это не совсем помогает.

Я думаю, что ответ здесь: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#user -defined-Conversions но я думаю, что мне не хватает некоторых тонких деталей.

Ответы [ 2 ]

2 голосов
/ 30 апреля 2020

Я думаю, что приведение из интерфейса к конкретному классу для вызова специфицированного c метода - плохая практика, и, как вы видели, это уже вызвало проблему.

Операторы преобразования НЕ работают для интерфейсов, потому что во время компиляции не существует конкретного типа, который будет использоваться (компилятором) для поиска таких операторов.

Учитывая, что у вас нет доступа к исходному коду IMotor, вы можете хотите, по крайней мере, централизовать приведение и расширить IMotor с помощью метода расширения :

public static class MotorExtensions
{
    public static void DoOtherVendorSpecificStuff(this IMotor motor)
    {
        if (motor is HomeAdjustmentDecorator decorator)
        {
            // This calls implicit cast operator
            ((VendorAMotor)decorator).DoOtherVendorSpecificStuff();
            return;
        }

        ((VendorAMotor)motor).DoOtherVendorSpecificStuff();
    }
}

и изменить приведение на:

IMotor motor = new VendorAMotor();
IMotor decorated = new HomeAdjustmentDecorator(motor);

// Both should work
motor.DoOtherVendorSpecificStuff();
decorated.DoOtherVendorSpecificStuff();
1 голос
/ 30 апреля 2020

Как уже упоминалось, плохая практика - смешивать интерфейсы с преобразованием типов для конкретной реализации. Примечание: обычно можно проверить, реализует ли ваш объект один или несколько других интерфейсов:

if (motor is IDisposable disposable)
    disposable.Dispose();

Причина: существуют интерфейсы для определения поведения объектов. Интерфейсы должны гарантировать, что любая реализация будет работать для вашей функции. Когда вы проверяете конкретную реализацию, вы нарушаете Принцип замещения Лискова.

Что касается исходного вопроса, вы можете определить дополнительный интерфейс:

public interface IMotorWithVendorStuff: IMotor
{
    DoOtherVendorSpecificStuff();
}

и адаптер для VendorAMotor для реализации этого интерфейса:

public class VendorAMotorAdapter: IMotorWithVendorStuff
{
    // ...
}

Вы должны использовать IMotorWithVendorStuff во всех местах, где вы разыгрываете IMotor в VendorAMotor.

...