Назначение закрытых членов в классе

Каковы цели наличия частных / защищенных членов класса / структуры в объектно-ориентированном программировании? Что плохого в том, чтобы все участники были публичными?


person wrongusername    schedule 03.03.2010    source источник


Ответы (10)


Инкапсуляция. Т.е. скрытие реализации данных вашего класса. Это позволяет вам изменить его позже, не нарушая весь клиентский код. Например. если у вас есть

class MyClass {
    public int foo;
}

ваши клиенты могут писать код вроде

MyClass bar = new MyClass();
bar.foo++;

теперь, если вы понимаете, что foo на самом деле должно быть double, а не int, вы меняете его:

class MyClass {
    public double foo;
}

и клиентский код не компилируется :-(

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

class Person {
    public String getName();
    public String getStreetAddress();
    public String getZipCode();
    public String getCountryCode();
    public int hashCode();
}

(с использованием свойств String для простоты - в реальном дизайне некоторые из них, вероятно, заслуживают своего собственного типа.)

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

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

    public void setCountryCodeAndZip(String countryCode, String zipCode);

Однако с общедоступными полями у вас просто нет этих вариантов.

Особый вариант использования частных полей - неизменяемые объекты; это очень часто, например, Java, примеры String и BigDecimal. У этих классов вообще нет публичных сеттеров, что гарантирует, что их объекты, однажды созданные, не изменят своего состояния. Это позволяет значительно оптимизировать производительность, а также упрощает их использование, например, в многопоточные программы, ORM и т. д.

person Péter Török    schedule 03.03.2010
comment
@wrongusername - изменение аргументов или типа возвращаемого значения общедоступного метода нарушит любой код, который может вызвать этот метод, или любой дочерний класс, который его переопределяет. Вы знаете, что с частными методами вы можете изменять их по своему желанию, не беспокоясь о том, что их могут использовать какие-либо другие классы. - person Eric Petroelje; 04.03.2010
comment
@wrongusername: посмотрите мой ответ, я ответил на ваш комментарий, пока вы его писали - person Pops; 04.03.2010
comment
Спасибо за добавление дополнительных материалов к своему ответу, Питер! - person wrongusername; 04.03.2010

Вы можете прочитать тему Скрытие информации в Википедии.

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

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

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

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

person LBushkin    schedule 03.03.2010

Красиво объяснено в разделе 7.4: Защитите свои личные части этого интерактивное руководство по C ++.

Зачем возиться с этим?

Спецификаторы позволяют классу быть очень сложным, с множеством функций-членов и данных-членов, имея простой общедоступный интерфейс, который могут использовать другие классы. Класс, который имеет двести членов данных и сто функций-членов, может быть очень сложным для написания; но если есть только три или четыре общедоступных функции-члена, а все остальные частные, кому-то может быть легко научиться использовать этот класс. Ему нужно только понимать, как использовать небольшую горстку общедоступных функций, и ему не нужно беспокоиться о двухстах элементах данных, потому что ему не разрешен доступ к этим данным. Он может получить доступ к личным данным только через открытый интерфейс класса. Без сомнения, в небольшой программе использование этих спецификаторов может показаться ненужным. Однако их стоит понять, если вы планируете делать любую программу разумного размера (более пары сотен строк). В общем, рекомендуется делать члены данных закрытыми. Функции-члены, которые должны вызываться извне класса, должны быть общедоступными, а функции-члены, которые вызываются только изнутри класса (также известные как вспомогательные функции), вероятно, должны быть частными. Эти спецификаторы особенно полезны в большой программе, в которой участвует более одного программиста.

Приведенное выше объяснение объясняет, как использование private облегчает обучение. Вот пример, объясняющий аспект нарушения кода:

Вот класс ParameterIO, который читает и записывает вектор целочисленных параметров

class ParameterIO
{
public:
    // Main member
    vector<int> *Params;
    string param_path;

    // Generate path
    void GeneratePath()
    {       
        char szPath[MAX_PATH];
        sprintf(szPath,"params_%d.dat",Params->size());
        param_path = szPath;
    }

    // Write to file
    void WriteParams()
    {
        assert_this(!Params->empty(),"Parameter vector is empty!");
        ofstream fout(param_path.c_str());
        assert_this(!fout.fail(),"Unable to open file for writing ...");
        copy(Params->begin(),Params->end(),ostream_iterator<int>(fout,"\n"));
        fout.close();
    }

    // Read parameters
    void ReadParams(const size_t Param_Size)
    {
        // Get the path
        Params->resize(Param_Size);
        GeneratePath();
        // Read
        ifstream fin(param_path.c_str());
        assert_this(!fin.fail(),"Unable to open file for reading ...");
        // Temporary integer
        for(size_t i = 0; i < Params->size() && !fin.eof() ; ++i) fin>>(*Params)[i];
        fin.close();
    }

    // Constructor
    ParameterIO(vector<int> * params):Params(params)
    {
        GeneratePath();
    }

    // Destructor
    ~ParameterIO()
    {
    }      

    // Assert
    void assert_this(const bool assertion, string msg)
    {
        if(assertion == false) 
        {
            cout<<msg<<endl;
            exit(1);
        }
    }
};

Следующий код разрушает этот класс:

const size_t len = 20;
vector<int> dummy(len);
for(size_t i = 0; i < len; ++i) dummy[i] = static_cast<int>(i);
ParameterIO writer(&dummy);

// ParameterIO breaks here!
// param_path should be private because 
    // the design of ParameterIO requires a standardized path
writer.param_path = "my_cool_path.dat";
// Write parameters to custom path
writer.WriteParams();

vector<int> dunce;
ParameterIO reader(&dunce);
// There is no such file!
reader.ReadParams(len);
person Jacob    schedule 03.03.2010
comment
@Jacob: хороший ответ, но что вы могли бы отредактировать, чтобы указать, что это за раздел 7.4? - person Pops; 04.03.2010
comment
@Torgamus: Спасибо за отзыв! Исправил это и добавил пример - person Jacob; 04.03.2010
comment
Хорошая цитата. Но это заставило меня смеяться / плакать: класс, который имеет двести членов данных и сотню функций-членов ... Скрытие данных - это наименьшая проблема такого класса свиней. - person Wayne Conrad; 04.03.2010

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

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

person Daniel Vassallo    schedule 03.03.2010

Это действительно зависит от вашей идеологии. Идея состоит в том, чтобы скрыть информацию, которую по каким-то причинам нельзя раскрывать.

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

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

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

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

person Alan Plum    schedule 03.03.2010

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

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

person Pops    schedule 03.03.2010
comment
Спасибо! Теперь я лучше понимаю его и Эрика ответы. - person wrongusername; 04.03.2010

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

person fastcodejava    schedule 03.03.2010

Каковы цели наличия внутренних органов в человеческом теле? Какой вред в том, что все органы находятся снаружи?

Точно!

Краткий ответ: потому что они вам нужны, поэтому вы не можете жить без них, и вы не можете предоставить их всем, чтобы изменить и поиграть с ними, потому что это может вас убить (из-за того, что ваш класс не будет работать должным образом).

person Leo Jweda    schedule 03.03.2010

Никакого вреда в зависимости от аудитории и потребления класса. Позвольте мне повторить это еще раз, чтобы понять.

Никакого вреда в зависимости от аудитории и потребления класса.

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

Так что вам действительно нужно посмотреть, как и кем будет использоваться класс, прежде чем вы сможете даже начать отвечать на этот вопрос. Точно так же, как долго будет длиться жизненный цикл разработки программного обеспечения? Это месяцы? Годы? Десятилетия? Собираются ли другие люди, кроме вас, использовать этот класс?

Чем более «общедоступным» класс (т. Е. Чем больше людей будет потреблять и использовать класс), тем важнее закрепить прочный публичный интерфейс и придерживаться его.

person Steven Noyes    schedule 03.03.2010

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

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

person Johann Philipp Strathausen    schedule 03.03.2010