Явное ограничение реализации интерфейса

У меня очень простой сценарий: "человек" может быть "клиентом" или "сотрудником" компании.

«человеку» можно позвонить по телефону с помощью метода «Позвонить».

В зависимости от того, какую роль играет "человек" в контексте звонка, например. объявление о новом продукте или объявление об изменении в организации, мы должны либо использовать номер телефона, предоставленный для роли «клиент», либо номер, предоставленный для «сотрудника» роль.

Вот итог ситуации:

interface IPerson
{
    void Call();
}

interface ICustomer : IPerson
{
}

interface IEmployee : IPerson
{
}

class Both : ICustomer, IEmployee
{
    void ICustomer.Call()
    {
        // Call to external phone number
    }

    void IEmployee.Call()
    {
        // Call to internal phone number
    }
}

Но этот код не компилируется и выдает ошибки:

error CS0539: 'ICustomer.Call' in explicit interface declaration is not a member of interface
error CS0539: 'IEmployee.Call' in explicit interface declaration is not a member of interface
error CS0535: 'Both' does not implement interface member 'IPerson.Call()'

Есть ли шанс реализовать этот сценарий на C# другим способом или мне придется искать другой дизайн?

Если да, то какие альтернативы вы предлагаете?

Заранее спасибо за вашу помощь.


person Pragmateek    schedule 07.12.2010    source источник
comment
Что бы вы хотели, чтобы произошло, если я напишу ((IPerson)new Both()).Call()?   -  person SLaks    schedule 07.12.2010
comment
Если я не знаю, какую роль я использую, я мог бы применить политику по умолчанию, внедрив IPerson.Call и перенаправив его на один из двух других методов или позвонив на оба номера один за другим или... бизнес правила.   -  person Pragmateek    schedule 07.12.2010


Ответы (6)


Ваша цель не имеет смысла.

Ни ICustomer, ни IEmployee не определяют метод Call(); они просто наследуют метод от того же интерфейса. Ваш класс Both реализует один и тот же интерфейс дважды.
Любой возможный вызов Call всегда будет вызывать IPerson.Call; нет инструкций IL, которые бы специально вызывали ICustomer.Call или IEmployee.Call.

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

person SLaks    schedule 07.12.2010
comment
Можно явно реализовать два интерфейса с одинаковыми именами методов, см. msdn.microsoft. .com/en-us/library/4taxa8t2.aspx - person cspolton; 07.12.2010
comment
@Spolto: Да, но это плохая идея. Также возможны конфликты с базовым интерфейсом. - person SLaks; 07.12.2010
comment
@SLaks: спасибо за ваши полезные ответы. С технической точки зрения ограничения реализации языка C# действительно могут оправдать ошибки, но с точки зрения дизайна, в чем здесь недостаток? - person Pragmateek; 07.12.2010
comment
@Серьезно: Да; это запутанно и непонятно. Два метода с одинаковыми именами должны делать одно и то же. Кроме того, функция, работающая с IEmployee, набранным как IPerson, будет вести себя некорректно. - person SLaks; 07.12.2010
comment
Два метода с одинаковыми именами должны делать одно и то же. : это может быть причиной того, что такая функция не была реализована в Java. - person Pragmateek; 07.12.2010
comment
@Serious: Явные реализации интерфейса предназначены для использования в качестве обходных путей, таких как использование типа internal, реализация закрытого члена или наличие двух членов с разными типами возвращаемого значения (например, int и long или IDbCommand и SqlCommand). - person SLaks; 07.12.2010

Помимо проблем, на которые точно указал SLaks...

Избавьтесь от IPerson и создайте IContactable с помощью метода Contact(), затем создайте два конкретных типа с именами Customer и Employee, которые реализуют IContactable. Затем, когда вам нужно связаться с кем-то, вы можете вызвать свой метод IContactable.Contact() по желанию, поскольку возможность установления контакта может расшириться, тогда как IPerson немного абстрактен.

person Aaron McIver    schedule 07.12.2010
comment
Это нельзя использовать для создания класса Both. - person SLaks; 07.12.2010

Я столкнулся с этим сам.

Вы можете решить проблему, используя композицию:

interface IPerson
{
    void Call();
}

interface ICustomer : IPerson
{
}

interface IEmployee : IPerson
{
}

class Both
{
    public ICustomer Customer { get; }
    public IEmployee Employee { get; }
}

Вышеприведенное предполагает, что Employee в классе Both является пользовательской реализацией IEmployee и создан на основе объекта Both.

Но это зависит от того, как вы планируете использовать класс Both.
Если вы хотите использовать класс Both следующим образом:

((IEmployee)both).Call();

Тогда вместо этого вы можете использовать это:

both.Employee.Call();
person Alex G    schedule 08.05.2012

Меня интересует ваш вклад в мое решение...

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

Итак, чтобы иметь возможность иметь несколько реализаций IPerson, в этом примере я бы использовал общий, чтобы иметь возможность разделить интерфейс IPerson от клиента к сотруднику.

interface IPerson<T>
{
    void Call();
}

interface ICustomer : IPerson<ICustomer>
{
}

interface IEmployee : IPerson<IEmployee>
{
}

class Both : ICustomer, IEmployee
{
    void IPerson<ICustomer>.Call()
    {
        // Call to external phone number 
    }

    void IPerson<IEmployee>.Call()
    {
        // Call to internal phone number 
    }
} 
person crackity_jones666    schedule 21.06.2012
comment
Это ответ или вопрос? - person Austin Henley; 21.10.2012

Вы не можете сделать это, потому что метод Call исходит из интерфейса IPerson в двух случаях. Итак, вы пытаетесь определить метод Call два раза. Я предлагаю вам изменить интерфейс ICustomer и IEmployee на класс и определить метод Call в этом классе:

interface IPerson
{
    void Call();
}

class Customer : IPerson
{
    public void Call()
    {
    }
}

class Employee : IPerson
{
    public void Call()
    {
    }
}
person Nicolas    schedule 07.12.2010
comment
Это не делает то, что он хочет. (Это нельзя использовать для создания класса Both) - person SLaks; 07.12.2010
comment
Но вместо класса Both можно использовать интерфейс IPerson с тем же эффектом. - person Nicolas; 08.12.2010

Не знаю, поможет это или нет, но попробовать можно.

//ran in linqpad c# program mode, you'll need to provide an entry point.....
void Main()
{
    IPerson x;
    x = new Both(new Employee());
    x.call(); //outputs "Emplyee"
    x = new Both(new Customer());
    x.call(); //outputs "Customer"
}

class Customer :  ICustomer
{
    public void call() {"Customer".Dump();}
}
class Employee :  IEmployee
{
    public void call() {"Employee".Dump();}
}
class Both : IPerson
{
     private IPerson Person { get; set; }
     public Both(IPerson person)
     {
         this.Person = person;
     }
     public void call()
     {
        Person.call();
     }
} 
interface IPerson { void call(); }  
interface ICustomer : IPerson { } 
interface IEmployee : IPerson { } 
person asawyer    schedule 07.12.2010
comment
Это не то, что он пытается сделать. - person SLaks; 07.12.2010
comment
Вроде соответствует заявленным требованиям. - person asawyer; 07.12.2010
comment
Он хочет выбрать интерфейс на месте вызова, а не создание объекта. - person SLaks; 07.12.2010
comment
Может ли человек быть Клиентом, Сотрудником или обоими одновременно? Если они оба, вы бы хотели вызвать их дважды? - person asawyer; 07.12.2010
comment
Прочитайте вопрос. Человек является и тем, и другим, и его можно вызывать в разных контекстах с помощью разных фрагментов кода. - person SLaks; 08.12.2010