Объяснение
Почему это невозможно?Я попал в реальную ситуацию, в которой это было бы полезно.
Полезно или нет, это нелогично.Вы частично нарушаете договор, предусмотренный интерфейсом.
Чтобы показать, почему ваш вывод недействителен, давайте рассмотрим отдельные шаги, которые мы предприняли, чтобы туда попасть.
public class Food {}
public class Meat : Food {}
public class Poultry : Food {}
public class Tofu : Food {}
Ничего изпока что обычный.
Допустим, у меня есть ресторан под названием Chez Flater.Я хочу нанять шеф-поваров, поэтому составляю контракт на то, что, как я ожидаю, будут делать мои шеф-повара, когда они работают:
public interface IChezFlaterChef
{
void Cook(Food food);
}
Обратите внимание, что определяет этот интерфейс.Чтобы заключить контракт в слова:
Если вы хотите быть IChezFlaterChef, вы должны уметь готовить все, что является едой .
public class VeganChef : IChezFlaterChef
{
public void Cook(Tofu tofu) {}
}
Теперь вы должны увидеть, почему этот класс нарушает контракт.Он хочет быть IChezFlaterChef
, но на самом деле он не выполняет всю работу, которая мне нужна от моих поваров.VeganChef
хочет только готовить Tofu
.Я бы не стал его нанимать.
Ваш ответ на это может быть "но у меня есть только тофу, который нуждается в приготовлении"Это может быть правильным сегодня , но что, если я решу, что я хочу добавить мясо в меню завтра?
Предполагается, что следующий код всегда будет работать:
IEnumerable<Food> myFood = GetSomeFoods();
foreach(var food in myFoods)
{
var availableChef = FindAvailableChef();
chef.Cook(food);
}
Если вы реализуете VeganChef так, как хотите, этот код может взорваться в неожиданное время, когда он станет доступным шеф-поваром, и вы попросите его приготовить что-то, что не является тофу.
Этопроблема.Мой ресторан не будет работать эффективно, потому что теперь мне приходится иметь дело с шеф-поваром, который отказывается работать (= выбрасывает исключение), и мне придется искать второго шеф-повара.
Мне специально требовались всемои повара могли готовить любую еду , потому что я не хотел, чтобы это произошло .Либо я не должен ожидать этого от моих поваров;или я не должен был нанять веганского шеф-повара.Компилятор указывает, что я не должен нанимать шеф-повара:
«Реализация» не реализует элемент интерфейса «SomeInterface.Method (BaseType)»
Эквивалентнона
[VeganChef] не выполняет ожидаемое [приготовление какой-либо пищи].
Другими словами, компилятор не позволяет вам открыть свой ресторан, потому что онзнает, что этот VeganChef станет для вас проблемой, как только ресторан начнет работать.
Когда вы пишете код, у вас нет возможности узнать, какие именно повара и продукты будут использоваться в этом конкретном случае.ресторан, потому что персонал и меню еще не определены (мы все еще разрабатываем сам ресторан)
Если я напишу этот код:
public string GetEmployeeName(Employee e)
{
return e.Name;
}
Это основано на логике, что каждый Сотрудник гарантированно имеет свойство Name.
Но ваш VeganChef
не может готовить каждую пищу.Поскольку мне требуется, чтобы мои IChezFlaterChef
s могли готовить любую еду, это логически означает, что VeganChef
нельзя нанять как IChezFlaterChef
.
Решение
Ваш текущийКонтракт с IChezFlaterChef просто неверен:
Если вы хотите быть IChezFlaterChef, вы должны иметь возможность готовить все, что является пищей .
То, что вы хотите, более корректно выражается в виде:
Если вы хотите быть IChezFlaterChef для определенного типа пищи , вы должны иметь возможность готовить этого конкретного типа пищи.
Как владелец ресторана, я изменил свои ожидания от того, что значит быть шеф-поваром в моем ресторане.Вместо того, чтобы требовать от всех моих поваров возможности готовить всю мою еду, я теперь готов нанимать специалистов, которые могут готовить только одну конкретную еду.
Когда шеф-повар хочет быть IChezFlaterChef
, очевиднымПервым вопросом тогда становится «для какой еды?».Эта информация предоставляется универсальным типом :
public interface IChezFlaterChef<TFood> where T : Food
{
void Cook(TFood food);
}
Уведомление where T : Food
.Это гарантирует, что вы не можете делать такие вещи, как создание IChezFlaterChef<DateTime>
, что не имеет смысла.
public class VeganChef : IChezFlaterChef<Tofu>
{
public void Cook(Tofu tofu) {}
}
И теперь этот класс действителен, потому что он выполняет контракт, определенный IChezFlaterChef<TFood>
.
Предположим, вам нужен шеф-повар, который может готовить два блюда.Вы можете сделать это, но вы должны реализовать интерфейс дважды (каждый со своим типом пищи):
public class MeatAndPoultryChef : IChezFlaterChef<Meat>, IChezFlaterChef<Poultry>
{
//Needed for IChezFlaterChef<Meat>
public void Cook(Meat meat) {}
//Needed for IChezFlaterChef<Poultry>
public void Cook(Poultry poultry) {}
}