Чи використання інтерфейсів для типів даних є антитілом?


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, якщо б я хотів (очевидно, я перейменував би інтерфейс)

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


Чи є якийсь - небудь причини ви віддаєте перевагу це знову мати що - щось подібне EntitySummary, з Userі Productкожен з яких має метод , як public EntitySummary GetSummary()?
Бен Аронсон

@Ben, ви пропонуєте дійсний варіант. Але все-таки потрібно буде визначити інтерфейс, який дозволяє абоненту знати, що може очікувати, що об'єкт має метод GetSummary (). Це по суті та сама конструкція з додатковим рівнем модульності в реалізації. Можливо, навіть гарна ідея, якщо резюме потрібно жити самостійно (хоча коротко) окремо від джерела.
Кент А.

@KentAnderson Я згоден. Насправді це може бути, а може і не бути хорошою ідеєю, залежно від загального інтерфейсу цих класів та того, як використовуються зведення.
Бен Аронсон

Відповіді:


17

Інтерфейси не описують поведінку. Іноді навпаки.

Інтерфейси описують контракти, такі як "якщо я пропоную цей об'єкт будь-якому методу, який приймає ISummarizableEntity, цей об'єкт повинен бути об'єктом, здатним підсумувати себе" - у вашому випадку це визначається як можливість повернути ідентифікатор рядка та рядок Опис.

Це ідеальне використання інтерфейсів. Тут немає анти-візерунка.


2
"Інтерфейси не описують поведінку." Як "підсумувати себе" - це не поведінка?
Доваль

2
@ThomasStringer, спадкування, з точки зору пуристів ОО, означає загальний рід (наприклад, квадрат і коло - обидві форми ). У прикладі ОП, Користувач і Продукт не мають спільного походження. Спадщина в цьому випадку буде чіткою антидіаграмою.
Кент А.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.