Является ли ссылка на базовый тип реализации в интерфейсе запахом кода?

Я столкнулся с дизайнерским решением, которое не пахнет для меня, но заставляет меня задуматься. Взгляните на следующий пример кода:

public interface IGenerator
{
  ///<summary>
  /// Combines two generators; performs magic as well
  /// </summary>
  BaseGenerator Combine(BaseGenerator next);

  ///<summary>
  /// Does what a generator does.
  /// </summary>
  object GenerateLolKThx();
}

public abstract class BaseGenerator : IGenerator
{  
  ///<summary>
  /// Combines two generators; performs magic as well
  /// </summary>
  public BaseGenerator Combine(BaseGenerator next)
  {
     // do stuff that I really don't want implementors to try and do
     // because its complex and can result in bad juju if done wrong
     return SuperSecretCombine(this, next);
  }

  ///<summary>
  /// Does what a generator does.
  /// </summary>
  public abstract object GenerateLolKThx();

  /* other base class methods */
}

Я не хочу вдаваться в подробности о ПОЧЕМУ, я не хочу доверять разработчикам с помощью метода Combine; достаточно сказать его сложный. Однако я хочу сделать все возможное, чтобы заставить любого, кто хочет внедрить IGenerator, расширить BaseGenerator, поскольку это единственный способ правильно объединить два генератора. Это обеспечивается самим интерфейсом.

Я обеспокоен тем, что есть непредвиденные проблемы (обозначенные "запахом"), вызванные моей ссылкой на реализацию интерфейса в этом интерфейсе. Но я также знаю, что такого рода вещи не являются неслыханными в CS, и при этом они сами по себе не плохие (то есть схема XML, которая описывает XSD и языковые компиляторы, которые написаны на языке, который они компилируют).

Меня интересуют причины, по которым это может быть запах кода, возможные подводные камни, вызванные этим типом дизайна, и альтернативные проекты, которые выполняют то, что я желаю (убедитесь, что моя реализация Combine используется для объединения любых типов генераторов). ТИА.

6 ответов

Решение

Я заинтересован в причинах, почему это может быть запах кода

Вот мой ответ: одна из целей (возможно, даже основной) отделения интерфейса от реализации состоит в том, чтобы сделать возможным создание множества различных реализаций для интерфейса. Привязка интерфейса к конкретному классу реализации нарушает это.

Вы фактически говорите - "этот интерфейс должен быть реализован BaseGenerator или его подклассом", но тогда зачем IGenerator от BaseGenerator?

Основываясь на показанном вами ограниченном коде, мне кажется, что вы используете интерфейс только для того, чтобы более подходящий абстрактный базовый класс. Если вы не доверяете кому-либо еще в объединении BaseGenerators, тогда это должно стать основой вашей иерархии, и Combine не должен быть виртуальным, чтобы никто не мог его переопределить.

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

Это работает лучше всего из вас в первую очередь говорить о IGenerator (не конкретные типы) - поскольку разрешение расширения предпочитает типы - т.е. FredsGenerator может объявить на заказ Combine метод, который будет (если вы печатаете переменные как FredsGenerator) возьмите предпочтение. Но это ничем не отличается от вашего примера, так как FredsGenerator может повторно реализовать интерфейс, украдя реализацию:

using System;
interface IFoo {
    void Bar();
}
abstract class FooBase {
    public void Bar() { Console.WriteLine("Non-virtual; you can't override me!!!"); }
}
class FooImpl : FooBase, IFoo {
    new public void Bar() { Console.WriteLine("mwahahahahah"); }
}
static class Program {
    static void Main() {
        IFoo foo = new FooImpl();
        foo.Bar();
    }
}

По крайней мере, с помощью метода расширений ваш код не может быть обманутым до запуска повторно реализованной версии; ваш код знает только о IGeneratorтак что только Generator.Combine версия будет использоваться. Когда-либо.


Пример (отредактируйте переработанный из вашего примера):

using System;
public interface IGenerator
{
    // note: no Combine
    object GenerateLolKThx();
}

public static class Generator
{
    ///<summary>
    /// Combines two generators; performs magic as well
    /// </summary>
    public static IGenerator Combine(this IGenerator current, IGenerator next)
    {
        // do stuff that I really don't want implementors to try and do
        // because its complex and can result in bad juju if done wrong
        Console.WriteLine("Super secret logic here...");
        // and prove we have access the the objects...
        Console.WriteLine(current.GenerateLolKThx());
        Console.WriteLine(next.GenerateLolKThx());
        return next; // just for illustration
    }
}
class MyGenerator : IGenerator
{
    private readonly object state;
    public MyGenerator(object state) {this.state = state;}
    public object GenerateLolKThx() { return state; }
}
public static class Program
{
    static void Main()
    {
        IGenerator foo = new MyGenerator("Hello"),
             bar = new MyGenerator("world");

        IGenerator combined = foo.Combine(bar);
    }
}

Является ли BaseGenerator (и его подклассы) единственным классом, реализующим интерфейс IGenerator?

  • Если так, то зачем вообще нужен IGenerator (а не просто BaseGenerator)?
  • Если нет, ожидаете ли вы (или нет), что другие не-BaseGenerator классы, которые реализуют IGenerator, смогут определить разумную реализацию метода Combine?

[Это похоже на вопрос, а не на ответ, но он должен быть сократовским.]

Почему бы не ссылаться на интерфейс?

public interface IGenerator
{
  ///<summary>
  /// Combines two generators; performs magic as well
  /// </summary>
  IGenerator Combine(IGenerator next);

  ///<summary>
  /// Does what a generator does.
  /// </summary>
  object GenerateLolKThx();
}

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

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