Чому явно реалізувати інтерфейс?


122

Отже, що саме є корисним випадком використання для явного впровадження інтерфейсу?

Це лише так, що людям, які використовують клас, не потрібно дивитися на всі ці методи / властивості в інтелісенсі?

Відповіді:


146

Якщо ви реалізуєте два інтерфейси, як з тим самим методом, так і з різними реалізаціями, тоді вам доведеться явно реалізувати.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Так, саме цей випадок вирішує EIMI. А інші пункти охоплені відповіддю "Майкл Б".
криптовалюта

10
Відмінний приклад. Любіть назви інтерфейсів / класів! :-)
Брайан Роджерс

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

4
@Mike Інтерфейси можуть належати до деяких API або до двох різних API. Можливо, любов тут трохи перебільшена, але я, принаймні, радий, що явна реалізація доступна.
TobiMcNamobi

@BrianRogers І назви методів теж ;-)
Snađошƒаӽ

66

Корисно приховати небажаного члена. Наприклад, якщо ви реалізуєте обидва, IComparable<T>і IComparableзазвичай приємніше приховувати IComparableперевантаження, щоб не створювати людям враження, що ви можете порівнювати об'єкти різних типів. Так само деякі інтерфейси не відповідають стандартам CLS, наприклад IConvertible, якщо ви не реалізуєте явно інтерфейс, кінцеві користувачі мов, які потребують відповідності CLS, не можуть використовувати ваш об’єкт. (Що було б дуже згубно, якби реалізатори BCL не приховували IConvertible членів примітивів :))

Ще одна цікава примітка полягає в тому, що звичайно використання такої конструкції означає, що структура, яка явно реалізує інтерфейс, може викликати їх лише за допомогою боксу для типу інтерфейсу. Ви можете обійти це за допомогою загальних обмежень ::

void SomeMethod<T>(T obj) where T:IConvertible

Не буде поле, коли ви перейдете до нього.


1
У вас є помилка друку. Щоб очистити, код вище працює. Це повинно бути в початковій заяві про підпис методу в інтерфейсі. У початковій публікації цього не вказано. Також правильний формат - "недійсний SomeMehtod <T> (T obj), де T: IConvertible. Зауважте, що між") "і" там ", де цього не повинно бути, є додаткова двокрапка. Ще, +1, для розумного використання дженерики, щоб уникнути дорогого боксу
Зак Янсен

1
Привіт, Майкл Б. Отже, чому при реалізації рядка в .NET публічна реалізація IComparable: public int CompareTo (значення об'єкта) {if (value == null) {return 1; } if (! (значення - String)) {киньте новий ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } повернути String.Compare (це, (String) значення, StringComppare.CurrentCulture); } Дякую!
zzfima

stringраніше навколо дженериків, і ця практика була в моді. Коли .net 2 прийшов навколо, вони не хотіли порушувати публічний інтерфейс, stringтому вони залишили його навколо, як це було з захисною системою на місці.
Майкл Б

37

Деякі додаткові причини, щоб явно реалізувати інтерфейс:

зворотна сумісність : у разі ICloneableзміни інтерфейсу, учасникам класу методу реалізації не потрібно змінювати свої підписи методів.

чистіший код : у випадку Cloneвидалення методу з ICloneable буде помилка компілятора , проте якщо ви реалізуєте метод неявно, ви можете отримати невикористані загальнодоступні загальнодоступні методи

сильне введення тексту : Щоб проілюструвати історію supercat на прикладі, це був би мій кращий зразок коду, реалізація ICloneableявно дозволяє Clone()чітко вводити, коли ви викликаєте його безпосередньо як MyObjectчлен екземпляра:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Для цього я вважаю за краще interface ICloneable<out T> { T Clone(); T self {get;} }. Зауважте, що навмисно немає ICloneable<T>обмежень на T. Хоча об'єкт, як правило, може бути безпечно клонований лише тоді, коли його база може бути, можна побажати виходити з базового класу, який міг би бути безпечно клонований об'єктом класу, який не може. Щоб дозволити це, я б рекомендував не мати спадкових класів піддавати відкритий метод клонування. Натомість мають успадковані класи protectedметодом клонування та запечатані класи, які походять від них та піддають публічному клонуванню.
supercat

Звичайно, це було б приємніше, за винятком того, що в BCL немає коваріантної версії ICloneable, тож вам доведеться створити одну правильну?
Wiebe Tijsma

Усі 3 приклади залежать від малоймовірних ситуацій та найкращих практичних інтерфейсів.
MickyD

13

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

Наприклад, об’єкт може реалізовувати ICloneable, але все-таки мати свій загальнодоступний Cloneметод повернення власного типу.

Так само IAutomobileFactoryможе бути Manufactureметод, який повертає Automobile, але FordExplorerFactory, який реалізує IAutomobileFactory, може мати його Manufactureметод return a FordExplorer(який походить від Automobile). Код, який знає, що він FordExplorerFactoryможе використовуватиFordExplorer специфічні властивості для об'єкта, поверненого FordExplorerFactoryбез введення тексту, тоді як код, який просто знав, що у нього є якийсь тип IAutomobileFactory, просто матиме справу з його поверненням як Automobile.


2
+1 ... це моє переважне використання явних реалізацій інтерфейсу, хоча, малий зразок коду, мабуть, трохи зрозуміліший, ніж ця історія :)
Wiebe Tijsma

7

Це також корисно, коли у вас є два інтерфейси з тим же ім'ям учасника та підписом, але ви хочете змінити поведінку його залежно від способу його використання. (Я не рекомендую писати такий код):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
найдивніший приклад, пов’язаний з OO, який я коли-небудь бачив:public class Animal : Cat, Dog
mbx

38
@mbx: Якби Animal також реалізував «Папугу», це була б тваринка, що перетворює Поллі.
RenniePet

3
Я пам’ятаю мультиплікаційного персонажа, який був Кіт в одному кінці та Собака в іншому кінці ;-)
Джордж Бірбіліс

2
У 80-х роках був телесеріал .. "Manimal" .. де людина могла перетворитись на ... о,
ніколи,

Моркії ніби виглядають як коти, і вони теж ніндзя.
Саміс

6

Він може тримати публічний інтерфейс чистішим, щоб явно реалізувати інтерфейс, тобто ваш Fileклас може реалізувати IDisposableявно та надавати публічний метод, Close()який може мати більше сенсу для споживача, ніж Dispose().

F # пропонує лише явну реалізацію інтерфейсу, тому вам завжди доводиться звертатися до певного інтерфейсу, щоб отримати доступ до його функціональності, що дозволяє дуже чітко (без каламбуру) використовувати інтерфейс.


Я думаю, що більшість версій VB також підтримують лише явні визначення інтерфейсу.
Гейб

1
@Gabe - це більш тонко, ніж для VB - називання та доступність членів, які реалізують інтерфейс, окремо від того, що вони вказують, що вони є частиною реалізації. Тож у VB і, дивлячись на відповідь @ Iain (поточна відповідь у верхній частині), ви могли реалізувати IDoItFast та IDoItSlow з громадськими членами "GoFast" та "GoSlow" відповідно.
Damien_The_Unbeliever

2
Мені не подобається ваш конкретний приклад (IMHO, єдині речі, які слід приховувати, - Disposeце ті, які ніколи не потребуватимуть очищення); кращим прикладом може бути щось на зразок реалізації незмінної колекції IList<T>.Add.
supercat

5

Якщо у вас є внутрішній інтерфейс, і ви не хочете публічно реалізовувати членів свого класу, ви їх явно реалізуєте. Неявні реалізації повинні бути загальнодоступними.


Гаразд, це пояснює, чому проект не компілюється з неявною реалізацією.
pauloya

4

Ще одна причина явної реалізації - це ремонтопридатність .

Коли клас стає «зайнятим» - так, це трапляється, у нас не все є розкішною переробкою коду інших членів команди - тоді, якщо явна реалізація дає зрозуміти, що існує метод для задоволення інтерфейсного контракту.

Так це покращує "читабельність" коду.


ІМХО важливіше вирішити, чи повинен клас чи не повинен піддавати метод своїм клієнтам. Це визначає, чи бути явним чи неявним. Задокументувати, що декілька методів належать разом, у цьому випадку тому, що вони задовольняють контракт - ось для чого #region, з відповідним рядком заголовка. І коментар до методу.
ToolmakerSteve

1

Інший приклад наводить System.Collections.Immutable , в якому автори вирішили використовувати техніку для збереження звичного API для типів колекції, викреслюючи ті частини інтерфейсу, які не мають значення для їх нових типів.

В Зокрема, ImmutableList<T>знаряддя IList<T>і , таким чином ICollection<T>( в порядку , щоб ImmutableList<T>використовуватися більш легко з успадкованим кодом), але void ICollection<T>.Add(T item)не має сенсу для ImmutableList<T>: так як додавання елемента до постійного списку не повинні змінити існуючий список, ImmutableList<T>також відбувається з IImmutableList<T>яких IImmutableList<T> Add(T item)може бути використаний для незмінні списки.

Таким чином, у випадку Add, реалізація ImmutableList<T>зрештою виглядає так:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

У випадку явно визначених інтерфейсів усі методи автоматично закриваються, ви не можете надати їм модифікатор доступу загальнодоступним. Припустимо:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"Усі методи автоматично є приватними" - це технічно не правильно, якщо б вони насправді були приватними, то вони взагалі не могли б дзвонити, кидаючи чи ні.
Саміс

0

Ось як ми можемо створити явний інтерфейс: якщо у нас є 2 інтерфейси, і обидва інтерфейси мають однаковий метод, і один клас успадковує ці 2 інтерфейси, тому коли ми викликаємо один метод інтерфейсу, компілятор заплутався, який метод викликати, щоб ми могли керуйте цією проблемою за допомогою явного інтерфейсу. Ось один із прикладів, які я навев нижче.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.