Ссылка на реализующий базовый тип в интерфейсе - PullRequest
2 голосов
/ 09 января 2009

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

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 и языковые компиляторы, написанные на языке, который они компилируют).

Ответы [ 6 ]

14 голосов
/ 09 января 2009

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

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

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

6 голосов
/ 09 января 2009

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

6 голосов
/ 09 января 2009

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

Это работает лучше всего для вас в основном говорят о 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, поэтому only будет использоваться Generator.Combine версия. Когда-либо.


Пример ( edit переработан по вашему примеру):

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);
    }
}
4 голосов
/ 09 января 2009

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

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

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

2 голосов
/ 09 января 2009

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

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();
}
0 голосов
/ 09 января 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...