Каковы цели наличия частных / защищенных членов класса / структуры в объектно-ориентированном программировании? Что плохого в том, чтобы все участники были публичными?
Назначение закрытых членов в классе
Ответы (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 и т. д.
Вы можете прочитать тему Скрытие информации в Википедии. strong >
По сути, частные члены позволяют классу скрывать детали своей реализации от внешних потребителей. Это позволяет классу лучше контролировать то, как будут выражаться его данные и поведение, и позволяет потребителю игнорировать детали, которые не имеет отношения к основной цели класса.
Скрытие деталей реализации улучшает ремонтопригодность программы, не позволяя внешнему по отношению к классу коду устанавливать зависимости от этих деталей. Это позволяет изменять реализацию независимо от внешних потребителей - с меньшим риском нарушения существующего поведения. Когда частные детали реализации становятся общедоступными, они не могут быть изменены без возможности нарушения потребителей класса, которые зависят от этих деталей.
Частные члены также позволяют классу защищать свою реализацию от внешнего злоупотребления. Обычно состояние класса имеет внутренние зависимости, которые определяют, когда состояние допустимо, а когда нет. Мы можем считать правила, управляющие достоверностью информации о состоянии, инвариантными - это означает, что класс всегда ожидает их истинности. Раскрытие личных данных позволяет внешнему коду изменять это состояние таким образом, чтобы это могло нарушить инварианты и, следовательно, поставить под угрозу действительность (и поведение) класса.
Дополнительное преимущество сокрытия информации заключается в том, что оно уменьшает площадь, которую потребители класса должны понимать, чтобы должным образом взаимодействовать с классом. Упрощение - это, как правило, хорошо. Это позволяет потребителям сосредоточиться на понимании общедоступного интерфейса, а не на том, как класс реализует свою функциональность.
Красиво объяснено в разделе 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);
Образно говоря, выставление частных участников как общедоступных - это все равно что иметь на приборной панели вашего автомобиля параметр, позволяющий регулировать давление масла в двигателе.
Автомобиль должен управлять этим изнутри (конфиденциально), и пользователь должен быть защищен от того, чтобы возиться с ним напрямую (инкапсуляция) по понятным причинам.
Это действительно зависит от вашей идеологии. Идея состоит в том, чтобы скрыть информацию, которую по каким-то причинам нельзя раскрывать.
Если у вас есть библиотека, которую вы хотите опубликовать в Интернете, многие люди скачают ее, а некоторые могут использовать ее в своем коде. Если вы сведете общедоступный API к минимуму и скроете детали реализации, вам будет легче обновлять его, когда вы столкнетесь с ошибками или захотите улучшить код.
Кроме того, в Java, например, у вас нет возможности ограничить доступ к переменной-члену без изменения ее видимости, поэтому вы часто обнаруживаете, что преждевременно создаете геттеры и сеттеры и делаете саму переменную частной или защищенной. В Python, например, этой проблемы не существует, потому что вы можете заставить геттеры и сеттеры вести себя как переменные для прямого доступа (там они называются свойствами).
Наконец, иногда вам нужны методы, которые требуют согласованного состояния для использования и могут привести к проблемам при прямом доступе.
Практическое правило: если вы что-то разоблачите, кто-то этим воспользуется. И чаще всего они будут использовать его по неправильным причинам (то есть не так, как вы планировали их использовать). В этом случае скрытие информации эквивалентно блокировке от детей на шкафах с оружием.
Чтобы добавить к ответу Питера, предположим, что ваш класс хранит имя, и вы хотите изменить его с использования одной строки имени на строку имени и строку фамилии. Если бы ваши члены были общедоступными, другие классы могли бы читать (или записывать) переменную имени напрямую и сломались бы, когда эта переменная исчезла.
Не говоря уже о том, что вы, возможно, вообще не хотите, чтобы другие классы имели возможность редактировать ваших членов.
Иногда вы не хотите раскрывать личную информацию всем. Например, вы не хотите, чтобы ваш возраст был известен широкой публике, но вы можете сообщить людям, если вам больше 25.
Каковы цели наличия внутренних органов в человеческом теле? Какой вред в том, что все органы находятся снаружи?
Точно!
Краткий ответ: потому что они вам нужны, поэтому вы не можете жить без них, и вы не можете предоставить их всем, чтобы изменить и поиграть с ними, потому что это может вас убить (из-за того, что ваш класс не будет работать должным образом).
Никакого вреда в зависимости от аудитории и потребления класса. Позвольте мне повторить это еще раз, чтобы понять.
Никакого вреда в зависимости от аудитории и потребления класса.
Для многих небольших проектов из одного или двух человек, которые занимают месяц или около того, полная обработка всех частных и общедоступных определений может значительно увеличить рабочую нагрузку. Однако в крупных проектах, где может быть несколько команд, и группы не расположены вместе географически, правильная разработка всех общедоступных интерфейсов с самого начала может значительно повысить вероятность успеха проекта в целом.
Так что вам действительно нужно посмотреть, как и кем будет использоваться класс, прежде чем вы сможете даже начать отвечать на этот вопрос. Точно так же, как долго будет длиться жизненный цикл разработки программного обеспечения? Это месяцы? Годы? Десятилетия? Собираются ли другие люди, кроме вас, использовать этот класс?
Чем более «общедоступным» класс (т. Е. Чем больше людей будет потреблять и использовать класс), тем важнее закрепить прочный публичный интерфейс и придерживаться его.
Краткий пример: вам может потребоваться обеспечить определенные условия для этого значения. В этом случае прямая установка может нарушить такое состояние.
Многие люди аргументируют это как «возможно, вы не хотите, чтобы все это читали», но я думаю, что ограничение на установку значения - более удобный пример.