Проблема инкапсуляции с делегатами?

Мне интересно, почему это работает?

Например, у меня есть некоторый класс-исполнитель, который выглядит так:

public class Executor
{
    public void Execute(Action action)
    {
        action();
    }
}

Теперь у меня есть некоторый должен быть выполнен класс, который выглядит так:

public class NeedToBeExecuted
{
    public void Invoke()
    {
        Executor executor = new Executor();
        executor.Execute(DoSomething);
    }

    private void DoSomething()
    {
        // do stuff private
    }
}

Мой вопрос: почему это работа, я передаю частный метод в другой класс?

Разве это не проблема инкапсуляции?


person IamStalker    schedule 23.03.2013    source источник
comment
у нас есть проблема с инкапсуляцией, когда мы определяем метод, который выполняется как ThreadRunner?   -  person bas    schedule 23.03.2013
comment
делать что-то личное ... хорошо   -  person Ken Kin    schedule 23.03.2013
comment
Вы передаете метод как Action. Хотя это и не совсем то же самое, подумайте, если бы вы передавали массив private или некоторый экземпляр класса в качестве параметра другому методу. В этом нет ничего плохого. DoSomething также сможет получить доступ к закрытым переменным из NeedToBeExecuted, которые он использует из-за замыканий.   -  person Oscar Mederos    schedule 23.03.2013


Ответы (6)


Нет, это не нарушение инкапсуляции.

Во-первых: сам класс — это то, что решило передать делегат одному из своих приватных методов! Класс имеет доступ к своим закрытым методам, и если он решит передать ссылку на один из них для кода за пределами области доступности этого метода, это вполне его права.

Во-вторых: область доступности метода не ограничивает, где этот метод может быть вызван. Это ограничивает поиск метода по имени. Класс Executor никогда не использует имя DoSomething для вызова закрытого метода. Он использует имя action.

person Eric Lippert    schedule 23.03.2013

Позвольте мне тогда попробовать.

Нет, у нас нет проблемы с инкапсуляцией. Вы определяете класс, который отвечает за Execute что-то. Executer ничего не знает о том, что на самом деле выполняется. Он знает только, что должен выполнить DoSomething. То, что делает DoSomething, указано в классе, который отвечает за то, чтобы что-то было выполнено.

Как я уже сказал, это очень похоже на то, что вы сделали бы с Thread. Вы определяете метод, который необходимо выполнить в другом потоке. Вот почему класс имеет поток и определяет, какой метод следует запускать в этом потоке. Поток по-прежнему ничего не знает о классе, в котором он играет роль.

Отношения: Класс => Поток. А не наоборот.

В вашем примере отношения NeedToBeExecuted => Executer. А не наоборот.

Концепция может показаться вам сложной, но вы не делаете ничего плохого.

person bas    schedule 23.03.2013

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

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

person Daniel Imms    schedule 23.03.2013
comment
но исполнитель не может получить доступ к закрытым членам класса NeedToBeExecuted. Он получает только «указатель на функцию». Инкапсуляция здесь не при чем. Не то чтобы это «могло» быть проблемой с отражением. - person bas; 23.03.2013

Учтите следующее: NeedToBeExecuted не раскрывает метод DoSomething() таким образом, чтобы его можно было вызывать произвольно — вместо этого он передает его в качестве делегата другой функции. С таким же успехом можно передать () => DoSomething() - результат тот же. В конечном счете модификаторы доступа предназначены для предотвращения использования вашего метода другим классом, вы по-прежнему можете использовать его по своему усмотрению. Если вы решите передать его другому классу, это допустимое использование.

person AlexFoxGill    schedule 23.03.2013

Инкапсуляция означает возможность изменять внутренности какого-либо модуля без изменения внешнего кода. Здесь все еще так. Таким образом, нарушения инкапсуляции не произошло.

person usr    schedule 23.03.2013
comment
Мне интересно, как разные люди по-разному видят цель инкапсуляции. Хотя инкапсуляция обеспечивает страховку от зависимости от деталей механизма, которые могут измениться, я рассматриваю основную цель инкапсуляции как предоставление программисту возможности поддерживать инвариант, на который, в свою очередь, может полагаться другой код. - person Eric Lippert; 23.03.2013

Вы передаете DoSomething в метод Invoke(), и он доступен в контексте. Нет проблем с инкапсуляцией.

Однако, как вы видите комментарий в коде, DoSomething недоступен в EitherNeedToBeExecuted.

public class Executor {
    public void Execute(Action action) {
        action();
    }
}

public class NeedToBeExecuted {
    public virtual void Invoke() {
        Executor executor=new Executor();
        executor.Execute(this.DoSomething);
    }

    private void DoSomething() {
        Console.WriteLine("I'M DOING IT MYSELF!!");
    }

    protected Executor m_Executor=new Executor();
}

public class EitherNeedToBeExecuted: NeedToBeExecuted {
    public override void Invoke() {
        // 'NeedToBeExecuted.DoSomething()' is inaccessible due to its protection level
        m_Executor.Execute(base.DoSomething);
    }
}
person Ken Kin    schedule 23.03.2013