Множественная явная реализация интерфейса - PullRequest
7 голосов
/ 20 июня 2011

У меня есть следующий базовый интерфейс

public interface IBaseAction
{
   bool CanAct(...)
}

и два наследующих интерфейса говорят

public interface IAction1 : IBaseAction{}

и

public interface IAction2 : IBaseAction{}

Моя проблема в том, что у меня есть класс, который реализует оба, и я хочу реализовать CanAct РАЗНО.

public class ComplexAction : IAction1, IAction2
{
   bool IAction1.CanAct(...){} //doesn't compile as CanAct is not a member of IAction1!!
}

ComplexAction c=new ComplexAction();
var a1 = (IAction1)c;
var a2 = (IAction2)c;
a1.CanSave(); //THESE TWO CALLS SHOULD BE IMPLEMENTED DIFFERENTLY
a2.CanSave();

Есть ли достаточно чистый способ сделать это?
(Кроме того, мои интерфейсы имеют семантическое значение и, по крайней мере, еще три функции, поэтому исключить всю иерархию исключено, но я бы хотел скопировать bool CanAct в каждый наследующий интерфейс, если это единственное решение их 4-6))

Ответы [ 7 ]

5 голосов
/ 20 июня 2011

Вы не можете делать то, что вы описываете. Только представьте, что произойдет, если клиент запросит интерфейс IBaseAction. Какой из них должен быть возвращен?

Мне кажется, что каждое действие должно быть реализовано отдельными объектами.

5 голосов
/ 20 июня 2011

А что должен делать CLR, если кто-то звонит ((IBaseAction)a1).CanSave()? Для IBaseAction.CanSave() может быть только одна реализация. Поэтому я думаю, что вы не можете сделать это концептуально.

Это фундаментальная проблема множественного наследования, называемая алмазной проблемой . Суть в следующем: если вы нажмете на нее, ваша иерархия типов определенно будет неправильной. Например. в данном конкретном случае вам лучше использовать модель класса роли (также известную как шаблон роли).

3 голосов
/ 20 июня 2011

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

Если вы хотите объединить два интерфейса вместе как ComplexAction, вы должны сделать что-то вроде следующего:

interface IAct
{
    bool CanAct();
}

class Act1 : IAct
{
    public bool CanAct()
    {
        return true;
    }
}

class Act2 : IAct
{
    public bool CanAct()
    {
        return false;
    }
}

class ComplexAction : IAct
{
    private Act1 action1;
    private Act2 action2;

    public ComplexAction(Act1 action1, Act2 action2)
    {
        this.action1 = action1;
        this.action2 = action2;
    }

    public bool CanAct()
    {
        return action1.CanAct() && action2.CanAct();
    }
}

A ComplexAction - это композиция разных IAct с.Если вы добавляете число к имени интерфейса, высока вероятность того, что вы делаете что-то не так.

Если вместо этого вы хотите определить другое поведение на основе интерфейса, этот интерфейс должен иметь свой метод, определенныйна себя.

interface IAct1
{
    bool CanAct();
}

interface IAct2
{
    bool CanAct();
}

class SometimesAct1SometimesAct2 : IAct, IAct1, IAct2
{
    bool IAct1.CanAct()
    {
        return false;
    }

    bool IAct2.CanAct()
    {
        return true;
    }

    public bool CanAct()
    {
        Console.WriteLine("Called on IAct or SometimesAct1SometimesAct2");
        return false;
    }
}

Чтобы избежать проблем наследования алмазов, вы должны предоставить реализацию для ВСЕХ интерфейсов, которые определяют конкретный метод, поэтому нет никакой двусмысленности.

1 голос
/ 20 июня 2011

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

Ваш ComplexAction должен иметь свойства Action1 и Action2 типов IAction1 и IAction2, которые правильно реализовали CanSave().

0 голосов
/ 20 июня 2011

Это не сработает.Вы предполагаете, что IAction1 наследует IBaseAction, но это не так.Интерфейсы не могут наследовать другие интерфейсы.Вы можете проверить это с помощью отражения, чтобы увидеть базовый класс IAction1.Это не будет IActionBase.

Что вы говорите со своим кодом: когда класс реализует IAction1, тогда также требуется реализовать IBaseAction.Затем C # поможет вам, предполагая, что вы реализуете IActionBase, просто сообщив, что вы внедряете IAction1.Также при использовании переменной типа IAction1 вы можете вызывать члены из IBaseAction, потому что он знает, что он реализован.

0 голосов
/ 20 июня 2011

Вам необходимо повторно объявить члены IBaseAction на интерфейсах, которые необходимо отложить, но затем вам также нужно реализовать члены IBaseAction неявно или явно, если не существует неявных реализаций членов ( чтобы убедиться, что все интерфейсы выполнены).

interface IBase {
    void Act();
}

interface IAction1 : IBase {
    void Act();
}

interface IAction2 : IBase {
    void Act();
}

class MyClass : IAction1, IAction2 {
    public void Act() {
        Console.WriteLine( "satisfies IBase.Act()" );
    }
    void IAction1.Act() {
        Console.WriteLine( "IAction1.Act()" );
    }
    void IAction2.Act() {
        Console.WriteLine( "IAction2.Act()" );
    }

    static void Main( string[] args ) {
        MyClass cls = new MyClass();
        cls.Act();
        IAction1 a = cls;
        a.Act();
        IAction2 b = cls;
        b.Act();
        Console.ReadKey();
    }
}

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

Вы сказали, что не можете отказаться от своей нынешней иерархии, но может быть возможно реорганизовать ее, чтобы избежать этих махинаций.

0 голосов
/ 20 июня 2011

Это не определяется IAction1, но IBaseAction.

Решение состоит в том, чтобы не позволить сложным действиям реализовывать оба (ваше текущее решение может нарушать SRP)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...