Эти два класса нарушают инкапсуляцию? - PullRequest
9 голосов
/ 19 января 2010
class X
{
protected:
    void protectedFunction() { cout << "I am protected" ; }
};

class Y : public X
{
public:
    using X::protectedFunction;
};

int main()
{
    Y y1;
    y1.protectedFunction();
}

Таким образом, я могу раскрыть одну из функций базового класса.

  1. Разве это не нарушает принцип инкапсуляции?
  2. Есть ли конкретная причина, почему это в стандарте?
  3. Существует ли какое-либо использование этого или оно будет изменено в новом стандарте?
  4. Есть ли какие-либо открытые вопросы, связанные с этим в стандарте?

Ответы [ 10 ]

18 голосов
/ 19 января 2010

Вы сделали это самостоятельно.
Вы могли бы написать

class Y : public X
{
public:
    void doA()
    {
       protectedFunction();
    }
};

int main()
{
    Y y1;
    y1.doA(); 
}

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

12 голосов
/ 19 января 2010

Да, это так, и именно поэтому защищенный получил значительную долю критики.

Бьярн Страуструп, создатель C ++, сожалеет об этом в своей превосходной книге «Дизайн и развитие C ++:

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

11 голосов
/ 19 января 2010

Я думаю, что сам Страуструп сказал, что функции инкапсуляции и целостности данных, встроенные в C ++, предназначены для обеспечения честности честных людей, а не для остановки преступников.

3 голосов
/ 19 января 2010

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

Если бы вы могли сделать это с помощью частного метода, возникла бы проблема ... (Отредактировано, чтобы быть немного яснее).

2 голосов
/ 19 января 2010

В C ++ даже закрытый модификатор не гарантирует инкапсуляцию.Никто не может помешать вам выстрелить себе в ногу.

Херб Саттер в своей статье " Использование и нарушение прав доступа " демонстрирует различные способы, которыми вы можете "нарушить инкапсуляцию".

Вот забавный пример из статьи Херба.

Злая макро-магия

#define protected public // illegal
#include "X.h"
//...
X y1;
y1.protectedFunction();
2 голосов
/ 19 января 2010

Нет.

Чтобы использовать публичный метод, у вас должен быть экземпляр Y, это не будет работать:

int main()
{
    X y1;
    y1.protectedFunction();
}

Если Y в своем новом определении предоставляет новый интерфейс, значит, Y. X все еще защищен.

2 голосов
/ 19 января 2010

С языковой точки зрения это не является скорее нарушением инкапсуляции, чем созданием метода делегата в производном объекте:

class Y : public X
{
public:
    void protectedFunction() {
       X::protectedFunction();
    }
};

Как только метод защищен, он предлагается производным классам делать с ним по своему усмотрению. Целью объявления using было не изменение доступа к унаследованным методам, а избежание проблем с скрытием методов (где открытый метод в X будет скрыт в типе Y, если определена другая перегрузка).

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

Если реальный вопрос заключается в том, является ли конкретное использование нарушением инкапсуляции с точки зрения дизайна (где дизайн здесь - это дизайн приложения, а не дизайн языка), то, скорее всего, да. Точно так же, как обнародование внутренних данных или методов. Если он был разработан для защиты начальным конструктором X, то есть вероятность, что разработчик Y не хотел, чтобы он был общедоступным ... (или предлагал метод делегирования для той же цели)

1 голос
/ 20 января 2010

Лично я считаю, что на этот вопрос следует ответить скорее как вопрос дизайна, а не как вопрос технологии.

Я бы спросил: «Какой смысл был защищенный метод в первую очередь?» Был ли это метод, который должны вызывать только подклассы, или это метод, который подклассы должны переопределять? Тем не менее, это может быть метод, который не ожидается в общем базовом классе, но может ожидаться в подклассе. Клиенту базового класса они никогда не знали об этом защищенном методе. Создатель подкласса решил расширить контракт, и теперь этот защищенный метод является общедоступным. Вопрос должен быть не в том, позволяет ли это C ++, а в том, подходит ли вам класс, контракт и будущие сопровождающие. Конечно, это может быть неприятный запах, но на самом деле вам нужно сделать это правильно для соответствующего варианта использования.

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

В целом, хотя в целях безопасности, как упоминалось ранее, вы, вероятно, захотите использовать функцию делегата (с другим именем) в подклассе. Таким образом, вместо «get (int)» вы можете использовать «getAtIndex (int)» или что-то в этом роде. Это позволяет вам легче рефакторинг / защита / аннотация / документ, что в будущем.

1 голос
/ 19 января 2010

Нет, я не вижу проблемы.

Вместо using вы могли бы сделать это:

class X
{
protected:
    void protectedFunction() { cout << "i am protected" ; }
};

class Y : public X
{
public:
    void protectedFunction() { X::protectedFunction(); }
};

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

Если мы следуем вашей логике, то геттер и сеттер тоже нарушают инкапсуляцию. И иногда они делают. Но не потому, что язык сломан. Просто потому, что вы выбираете дизайн ломаных классов.

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

1 голос
/ 19 января 2010

Разработчик класса должен знать, что объявление функции-члена или переменной (хотя на самом деле все переменные должны быть частными) как защищенной - это в основном то же самое, что и объявление ее общедоступной (как вы показали, легко получить доступ к защищенной вещи). Имея это в виду, будьте осторожны при реализации этих функций в базовом классе, чтобы учесть «неожиданное» использование. Это не нарушение инкапсуляции, потому что вы можете получить к нему доступ и раскрыть его.

protected - это более сложный способ объявить что-то публичное!

...