Наследование и скрытие родительских атрибутов

Разумно ли делать что-то подобное?

Примечание: это минимальный рабочий пример.

class A {
    public:
        int getX() { return x; }
    protected:
        int x;
        virtual void setX(int newX) = 0;
};

// Children can modify X
class Can_Modify_X : public A {
     protected:
         void setX(int newX) { x = newX; }
     private:
         using A::x;
};

// Children can't modify X
class Can_Not_Modify_X : public A {
     private:
         void setX(int newX) { }
         using A::x;
};

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

Два класса должны иметь общего родителя (даже если это непосредственно один из них) и не должны иметь возможности изменять x напрямую.

БОНУС: может ли кто-нибудь указать мне где-нибудь, определяющее точное поведение using в этом случае? Я пытался погуглить, но с очень небольшим успехом.


person Svalorzen    schedule 04.12.2012    source источник
comment
Но это чистый виртуал, даже не имеет значения, какая у него видимость, поскольку его дочерние элементы могут изменить его при реализации. Здесь важно только то, что другие сущности не могут создать A* и оттуда вызвать виртуальную функцию. Или нет?   -  person Svalorzen    schedule 05.12.2012
comment
Что значит не можешь? Он компилируется, что я здесь ломаю?   -  person Svalorzen    schedule 05.12.2012


Ответы (3)


Я знаю, что не могу просто скрыть функцию, потому что это нарушит принцип Лискова.

Точно такая же концепция применяется к элементам данных.

Предположим, у вас есть указатель (или ссылка) на экземпляр Can_Modify_X или Can_Not_Modify_X. Вы не можете получить доступ или изменить элемент данных x по этой ссылке. Если вы приведете этот указатель производного класса к указателю на класс A, вы внезапно сможете изменить элемент данных x. Делая x закрытым, вы нарушаете принцип подстановки Лискова. Неважно, является ли x элементом данных, функцией-членом или определением типа. Вы нарушаете замену Лискова, чисто и просто.

Производные классы не должны скрывать возможности, предоставляемые родительским классом.

person David Hammen    schedule 04.12.2012
comment
Но поскольку x защищен в моем базовом классе, никто на самом деле не узнает, не так ли? Сам класс не является экземпляром, поэтому проблем нет. Указатель на класс A не может получить доступ к x, так как он защищен. Любой зависимый класс, производный от A, сможет получить доступ к x по мере необходимости, поскольку он защищен, так что все в порядке. Единственная разница будет для дочерних элементов Can_Modify_X и Can_Not_Modify_X, но это не имеет значения, поскольку это является целью классов. Я не вижу ни одного конкретного случая, в котором эта штука могла бы сломаться. Есть хоть один? - person Svalorzen; 05.12.2012
comment
Я даже пытался расширить Can_Not_Modify_X и создать функцию, которая приводила бы самого потомка к A и пыталась получить доступ к x таким образом, но компилятор отказался это принять. - person Svalorzen; 05.12.2012

Что вы ожидаете от следующего кода?

A * a = new Can_Not_Modify_X();
a->setX(10);
person Coincoin    schedule 04.12.2012
comment
Это не совсем ответ. - person Pete Fordham; 05.12.2012
comment
@Pete Теперь я понимаю, что мог бы быть немного более дипломатичным в этом вопросе, но я считаю, что это ответ в том смысле, что он объясняет, почему это не очень хорошая идея без ответа на грудное вскармливание. На 90% (составленная статистика) из "Why can't [insert language] do that?" вопросов легко ответить, просто спросив себя, что именно вы ожидаете от этого, и поймите, что это не имеет никакого смысла или настолько чрезмерно сложно, что становится кошмаром реализации. - person Coincoin; 05.12.2012
comment
Я вижу это, но в любом случае ваш пример всегда будет терпеть неудачу, потому что setX либо частный, либо защищенный. Это никогда не является публичным из классов, но это не имеет отношения к исходному вопросу. - person Pete Fordham; 05.12.2012

Это субъективный вопрос, поэтому мой ответ будет соответственно субъективным.

Я собираюсь сказать нет, это не разумно. Атрибуты protected позволяют дочерним классам легко случайно изменить состояние и нарушить инварианты, поэтому я предлагаю полностью их избегать. Тогда ваш родительский класс будет поддерживать x через общедоступный или защищенный интерфейс (надеюсь, с помощью осмысленного набора методов, а не просто мутаторов).

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

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

person Mark B    schedule 04.12.2012