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

9

Предположим, у меня есть различные объекты в моей модели (с использованием EF), например, Пользователь, Продукт, Счет-фактура и Заказ.

Я пишу пользовательский элемент управления, который может распечатывать сводки объектов сущностей в моем приложении, где сущности принадлежат заранее определенному набору, в этом случае я говорю, что сводки пользователей и продуктов можно суммировать.

Все сводки будут иметь только идентификатор и описание, поэтому я создам простой интерфейс для этого:

 public interface ISummarizableEntity {     
       public string ID { get; }    
       public string Description { get; } 
 }

Затем для рассматриваемых сущностей я создаю частичный класс, который реализует этот интерфейс:

public partial class User : ISummarizableEntity
{
    public string ID
    {
        get{ return UserID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age); }
    }
}

public partial class Product: ISummarizableEntity
{
    public string ID
    {
        get{ return ProductID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department); }
    }
}

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

Я мог бы создать конкретный тип данных и сопоставить его с сущностями, но не вижу преимущества. Я мог бы заставить объекты сущности наследоваться от абстрактного класса, а затем определить свойства, но затем я блокирую сущности для дальнейшего использования, поскольку у нас не может быть множественного наследования. Я также открыт для любого объекта ISummarizableEntity, если бы я хотел (очевидно, я бы переименовал интерфейс)

Решение, которое я использую в своем уме, является обслуживаемым, расширяемым, тестируемым и достаточно надежным. Вы можете увидеть здесь анти-паттерн?

Si-N
источник
Есть ли какая-то причина, по которой вы предпочитаете это, а не что-то вроде EntitySummary, Userи у Productкаждого такой метод public EntitySummary GetSummary()?
Бен Ааронсон
@Ben, вы предлагаете правильный вариант. Но для этого все равно потребуется определение интерфейса, который позволяет вызывающей стороне знать, что он может ожидать, что объект будет иметь метод GetSummary (). По сути, это тот же дизайн с добавленным уровнем модульности в реализации. Может быть, даже хорошая идея, если резюме должно жить самостоятельно (хотя и кратко) отдельно от своего источника.
Кент А.
@KentAnderson Я согласен. Это может или не может быть хорошей идеей, в зависимости от общего интерфейса этих классов и того, как используются сводки.
Бен Ааронсон

Ответы:

17

Интерфейсы не описывают поведение. Иногда наоборот.

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

Это идеальное использование интерфейсов. Здесь нет анти-паттерна.

прецизионный самописец
источник
2
«Интерфейсы не описывают поведение». Как «подвести итог» не поведение?
Доваль
2
@ThomasStringer, наследование, с точки зрения пуристского ОО, подразумевает общее происхождение (например, квадрат и круг - это фигуры ). В примере OP пользователь и продукт не имеют общих разумных предков. Наследование в этом случае будет явным анти-паттерном.
Кент А.
2
@Doval: Я предполагаю, что имя интерфейса может описывать ожидаемое поведение. Но это не обязательно; интерфейс мог бы также называться IHasIdAndDescription, и ответ был бы тем же. Сам интерфейс не описывает поведение, он описывает ожидания.
фунтовые
2
@pdr Если вы отправите 20 В через разъем для наушников, произойдут плохие вещи. Форма не достаточно; Существует очень реальное и очень важное ожидание того, какой сигнал будет проходить через этот разъем. Именно поэтому делать вид, что к интерфейсам не привязаны спецификации поведения, неправильно. Что вы можете сделать с Listсписком, который не ведет себя как список?
Доваль
3
Электрическая вилка с соответствующим интерфейсом может вставляться в розетку, но это не значит, что она будет проводить электричество (желаемое поведение).
JeffO
5

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

Кент А.
источник
3
Интерфейсы не имеют ничего общего с наследованием.
DougM
1
@ DougM, возможно, я не очень хорошо это сказал, но я уверен, что мы согласны.
Кент А.
1

Следует избегать интерфейсов, которые несут только свойства , так как:

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

Здесь вы смешиваете две проблемы:

  • сводка как данные
  • резюме как контракт

Сводка состоит из двух строк: идентификатора и описания. Это простые данные:

public class Summary {
    private readonly string id;
    private readonly string description;
    public Summary(string id, string description) {
        this.id = id;
        this.description = description;
    }
    public string Id { get { return id; } }
    public string Description { get { return description; } }
}

Теперь, когда вы определили, что такое сводка, вы хотите определить контракт:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Обратите внимание, что использование интеллекта в геттерах - это анти-паттерн, и его следует избегать: вместо этого его следует размещать в функциях. Вот как выглядят реализации:

public partial class User : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = UserID.ToString();
        var description = String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age);
        return new Summary(id,description);
    }
}

public partial class Product : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = ProductID.ToString();
        var description = String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department);
        return new Summary(id,description);
    }
}
Vanna
источник
«Следует избегать интерфейсов, несущих только свойства», - я не согласен. Приведите аргументацию, почему вы так думаете.
Euphoric
Вы правы , я добавил некоторые детали
Vanna