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


75

Що є аргументом проти оголошення членів захищеного доступу на інтерфейсах? Це, наприклад, недійсне:

public interface IOrange
{
    public OrangePeel Peel { get; }
    protected OrangePips Seeds { get; }
}

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

public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    public OrangePips Seeds { get { return new OrangePips(6); } }
}

Метою protectedчленів інтерфейсів є надання контракту на підтримку спадкоємців (підкласів), наприклад:

public class SpecialNavelOrange : NavelOrange
{
    ...
    // Having a seed value is useful to me.
    OrangePips seeds = this.Seeds; 
    ...
}

(Слід визнати, це не буде працювати для structs)

Я не бачу великої кількості випадків privateабо internalмодифікаторів в інтерфейсах, але підтримка обох publicта protectedмодифікаторів видається цілком розумною.


Я спробую пояснити корисність protectedчленів на interfaces, повністю відокремивши їх від interfaces:

Давайте уявимо нове ключове слово C # supportдля забезпечення виконання контрактів спадкоємців, щоб ми декларували такі речі:

public support IOrangeSupport
{
    OrangePips Seeds { get; }
}

Це дозволило б нам укласти класи для надання захищених членів своїм спадкоємцям:

public class NavelOrange : IOrange, IOrangeSupport
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

Це не особливо корисно, оскільки класи вже передбачають цей контракт, надаючи protectedспочатку членів.

Але тоді ми могли б зробити і таке:

public interface IOrange : IOrangeSupport
{
   ...
}

Тим самим застосовуючи IOrangeSupportдо всіх класів, які реалізують, IOrangeі вимагаючи від них надання конкретних protectedчленів - що ми зараз не можемо зробити.


можливий дублікат
непублічних

1
Щоб уточнити це питання, розгляньте цей варіант використання. У мене є похідний клас, який успадковується від загального базового класу. Я хочу додати захищений член, до якого можна отримати доступ до будь-якого із загальних ароматів похідного класу, але не піддається зовнішньому світу. class Base<T> { } interface IDerived { string Secret { get; set; } } class Derived<T> : Base<T>, IDerived { protected string Secret; protected void LearnSecret(IDerived other) { var x = other.Secret; } }
hypehuman

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

Можливо, з’явиться в C # 8.0 jeremybytes.blogspot.com/2019/11/…
Дуг Домені

Відповіді:


61

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

public interface IOrange
{
    OrangePeel Peel { get; }
}

public abstract class OrangeBase : IOrange
{
    protected OrangeBase() {}
    protected abstract OrangePips Seeds { get; }
    public abstract OrangePeel Peel { get; }
}

public class NavelOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return new OrangePips(6); } }
}

Редагувати: Справедливо стверджувати, що якщо ми маємо PlasticOrange, що походить від орнаменту класу, він може реалізовувати лише IOrange, а не захищений метод Seeds. Це добре. Інтерфейс за визначенням - це контракт між абонентом та об'єктом, а не між класом та його підкласами. Абстрактний клас настільки близький, наскільки ми підходимо до цього поняття. І це добре. Те, що ви по суті пропонуєте, - це ще одна конструкція на мові, за допомогою якої ми можемо переключати підкласи з одного базового класу на інший, не порушуючи збірку. Для мене це не має сенсу.

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

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


10
Абстрактний клас працює лише тоді, коли доцільно встановити базові класи для одного NavelOrangeі ValenciaOrangeтого ж. Припустимо, клас, PlasticOrangeякий вже походить від Ornament.
ajlane

4
Тоді я б сумнівався, що апельсин має насіння чи шкірку. Серйозно, це справедлива думка, але ми обмежені моделлю єдиного успадкування ООП, тому я не маю хорошої відповіді. З огляду на це, це рішення вирішує оригінальний випадок, якщо ви не плануєте створити модель, на яку натякаєте. :)
Szymon Rozga

50

Не розумію, чому хтось би цього хотів. Якщо ви хочете, щоб похідний клас забезпечував реалізацію певного методу, перейдіть до абстрактних базових класів. Інтерфейси - саме це - інтерфейси. Державний контракт, нічого іншого. Подумайте про інтерфейс як про специфікацію, яка описує, як реалізація повинна виглядати у зовнішньому світі. Специфікація двоконтактного штекера не визначає (принаймні, я припускаю), якою має бути його внутрішня структура. Він просто повинен бути сумісним з інтерфейсом із розеткою. (джерело: made-in-china.com )Вилка


13
Найкраще пояснення, я кожен раз чув про інтерфейс. +1
WolfmanDragon

6
Я думаю, ви виявите, що досить важливо знати, чи реалізація, що стоїть за цим штекером, вимагає струму 110 В або 220 В ;-)
mindplay.dk

5
@ mindplay.dk У ідеальному світі штекери для 110 і 220 повинні бути різними.
Антон Гоголєв

6
@Anton в моєму ідеальному світі всі пристрої працювали б однаково добре на напрузі 110 і 220 вольт, а штекери - однакові ;-)
mindplay.dk

1
Інтерфейси гарантують реалізацію для зовнішніх клієнтів. Ви точно знаєте, що клас A застосує методи інтерфейсу B, якщо клас A реалізує B. Отже, ви можете загрожувати A як B і гарантувати, що він буде працювати як один. Абстрактні класи гарантують реалізацію для внутрішніх клієнтів, класів, які походять від цього, але це також дозволяє часткову реалізацію. Це подвійне використання є проблематичним. Я думаю, що абстрактні класи слід було розділити на дві конструкції. Один для часткової реалізації, який не дозволяє багаторазове успадкування, і один для похідного контракту, який дозволяє.
Дідьє А.

15

Тому що це не має сенсу. Інтерфейс - це відкритий договір. Я IThing, тому я буду виконувати методи IThing, якщо запитають. Ви не можете попросити IThing підтвердити, що він виконує методи, про які вам не може розповісти.


Але IThing може розповісти своїм спадкоємцям про неметоди public. Чому я не можу попросити його виконати додаткові методи IThing у цьому контексті?
ajlane

Оскільки це не методи IThing, це методи / властивості базового класу. Що робити, якщо дві реалізації IThing реалізують цього захищеного члена, а третя ні, оскільки це просто не має сенсу. Що тоді?
Szymon Rozga

@AJ - розгублений. IThing не може сказати своїм спадкоємцям про непублічні, в цьому справа?
annakata

@siz: Третій повинен це реалізувати: це в інтерфейсі.
ajlane

@annakata: Припустимо, що AbstractThing реалізує IThing, а ConcreteThing походить від AbstractThing. AbstractThing може розповісти ConcreteThing про захищених (непублічних) членів. Ідея полягає в тому, що IThing також може вказати деякі з них
Айлане

9

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

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


7

Члени інтерфейсу є загальнодоступним API; такі речі як protectedтощо - це деталі реалізації - а інтерфейси не мають жодної реалізації. Я підозрюю, що ви шукаєте, це явна реалізація інтерфейсу:

public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    OrangePips IOrange.Seeds { get { return null; } }
}

Явні реалізації інтерфейсу все ще залишаються public, тому це насправді не те, що я шукаю. Зокрема, я думаю, що protectedчлени нададуть API між наборами базових класів та їх спадкоємцями.
ajlane

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

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

2

Інтерфейс схожий на форму ключа.

введіть тут опис зображення

Це не ключ.

Це не замок.

Це лише тонка контактна точка.

З цієї причини всі члени інтерфейсу (що визначає форму ключа) повинні бути загальнодоступними.

Щоб ключ відкрив замок, важливо, щоб вони обидва мали однакову форму.

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

В іншому випадку, зробивши його (інтерфейс) внутрішнім, ви не дозволите іншим створювати сумісні замки або сумісні ключі.


1

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


1

на відміну від абстрактних базових класів, захищені інтерфейси дозволять "множинне успадкування" (абстрактних класів), що я вважав би корисним один-два рази ...


0

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


1
Згоден, але я не стільки запитую, чому реалізація членів інтерфейсу не може бути необов’язковою (як це не повинно бути), але чому я не можу вказати їх рівень доступу.
ajlane

0

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


0

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


0

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


"методи" повинні читати "учасники" - це також можуть бути властивості та події
Марк Гравелл

0

Будь-який клас, який реалізує .net-інтерфейс, повинен включати реалізації всіх членів інтерфейсу. Далі, будь-який клас може представити похідному класу будь-яких членів, яких він бажає. Вимога про те, що реалізація інтерфейсу повинна включати член, який буде використовуватися лише з похідних класів, не буде корисною, якщо (1) такий член може бути видимим для чогось поза інтерфейсом, або (2) реалізації інтерфейсу можуть використовувати членів, яких вони самі не визначили. Якщо інтерфейсам було дозволено включати вкладені класи (які могли отримати доступ до protectedчленів інтерфейсів ), тодіprotectedчлени інтерфейсу мали б сенс. Дійсно, вони могли б бути дуже корисними, якби клас, вкладений в інтерфейс, міг визначати методи розширення для цього інтерфейсу. На жаль, такого закладу не існує.

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

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