До ООП члени структури даних залишалися загальнодоступними?


44

Коли структура даних (наприклад, черга) реалізована за допомогою мови OOP, деякі члени структури даних повинні бути приватними (наприклад, кількість елементів у черзі).

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


75
"деякі члени структури даних повинні бути приватними" Існує велика різниця між "мабуть, має бути" і "потрібно бути". Дайте мені мову OO, і я гарантую, що зможу зробити чергу, яка прекрасно працює навіть з усіма її членами та методами, які є публічними, доки ви не зловживаєте цією свободою.
8bittree

48
Для того, щоб сказати @ 8bittree, можна сказати, що все загальнодоступне - це добре, якщо люди, які використовують ваш код, досить дисципліновані, щоб дотримуватися заданого вами інтерфейсу. Конструкція приватного члена виникла через людей, які не могли тримати ніс там, де вони не належать.
Blrfl

20
Ви мали на увазі "до того, як інкапсуляція стала популярною"? Інкапсуляція була досить популярною до того, як мови OO стали популярними.
Френк Хілеман

6
@FrankHileman Я думаю, що це насправді суть питання: ОП хоче знати, чи існувало інкапсуляція на процедурних мовах, перш ніж Simula / Smalltalk / C ++
dcorking

18
Мені заздалегідь шкода, якщо це трапляється як поблажливе, я не маю на увазі. Вам потрібно вивчити деякі інші мови. Мови програмування не для роботи машин, вони для думки програмістів . Вони обов'язково формують те, як ви думаєте. У вас просто не виникне цього питання, якби ви витратили якийсь значний час, працюючи з JavaScript / Python / Ocaml / Clojure, навіть якщо ви цілий день займалися Java на своєму робочому дні. Окрім одного проекту C ++ з відкритим кодом, над яким я працюю (в основному це все-таки C), я не використовував мову з модифікаторами доступу ще з коледжу, і я не пропустив їх.
Джаред Сміт

Відповіді:


139

OOP не винайшов інкапсуляцію і не є синонімом інкапсуляції. Багато мов OOP не мають модифікаторів доступу в стилі C ++ / Java. У багатьох мовах, що не входять в ООП, доступні різні методики для пропонування капсуляції.

Класичним підходом до інкапсуляції є закриття , яке використовується у функціональному програмуванні . Це значно старше, ніж ООП, але є в чомусь еквівалентом. Наприклад, у JavaScript ми можемо створити такий об’єкт:

function Adder(x) {
  this.add = function add(y) {
    return x + y;
  }
}

var plus2 = new Adder(2);
plus2.add(7);  //=> 9

Наведений вище plus2об'єкт не має члена, який би дозволяв прямий доступ до xнього - він повністю інкапсульований. add()Метод являє собою замикання по xзмінної.

Мова C підтримує деякі види інкапсуляції через механізм файлів заголовків , зокрема, непрозору техніку вказівника . В C можна оголосити назву структури без визначення її членів. На той момент жодна змінна типу цієї структури не може бути використана, але ми можемо вільно використовувати покажчики на цю структуру (оскільки розмір структурного вказівника відомий під час компіляції). Наприклад, розглянемо цей заголовок:

#ifndef ADDER_H
#define ADDER_H

typedef struct AdderImpl *Adder;

Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);

#endif

Тепер ми можемо записувати код, який використовує цей інтерфейс Adder, не маючи доступу до його полів, наприклад:

Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7));  /* => 9 */
Adder_free(plus2);

А ось детально вкладені деталі реалізації:

#include "adder.h"

struct AdderImpl { int x; };

Adder Adder_new(int x) {
  Adder self = malloc(sizeof *self);
  if (!self) return NULL;
  self->x = x;
  return self;
}

void Adder_free(Adder self) {
  free(self);
}

int Adder_add(Adder self, int y) {
  return self->x + y;
}

Існує також клас модульних мов програмування , який фокусується на інтерфейсах рівня модулів. Мовна сім'я ML в т.ч. OCaml включає цікавий підхід до модулів, званих функторами . OOP затьмарює і значною мірою поширюється на модульне програмування, але багато нібито переваги OOP більше стосуються модульності, ніж орієнтації на об'єкти.

Існує також зауваження, що класи на мовах OOP, такі як C ++ або Java, часто не використовуються для об'єктів (у розумінні об'єктів, які вирішують операції за допомогою пізнього прив'язування / динамічної диспетчеризації), а лише для абстрактних типів даних (де ми визначаємо публічний інтерфейс, який ховається внутрішні деталі реалізації). У статті " Розуміння абстрагування даних", Revisited (Cook, 2009) більш детально розглядається ця різниця.

Але так, багато мов не мають механізму інкапсуляції. Цими мовами члени структури залишаються загальнодоступними. У крайньому випадку, це буде конвенцією про іменування, яка не перешкоджає використанню. Наприклад, я думаю, що Паскаль не мав корисного механізму інкапсуляції.


11
Бачите помилку в Adder self = malloc(sizeof(Adder));? Є причина, що набирає вказівники, і, sizeof(TYPE)як правило, нахмуриться.
Дедуплікатор

10
Ви не можете просто писати sizeof(*Adder), тому що *Adderце не тип, як *int *і не тип. Вираз T t = malloc(sizeof *t)є ідіоматичним і правильним. Дивіться мою редакцію.
wchargin

4
Паскаль міняв одиниці змінних, які не було видно ззовні. Ефективно одиничні змінні були еквівалентні private staticзмінним на Java. Точно так само на C ви можете використовувати непрозорі покажчики для передачі даних у Pascal, не оголошуючи, що це було. Класичний MacOS використовував багато непрозорих покажчиків, оскільки публічні та приватні частини запису (структура даних) можуть передаватися разом. Я пам'ятаю, що Window Manager робив багато цього, оскільки частини Вікна записів були загальнодоступними, але також була включена деяка внутрішня інформація.
Майкл Шопсін

6
Можливо, кращим прикладом, ніж Паскаль, є Python, який підтримує орієнтацію на об'єкти, але не інкапсулюється, вдаючись до іменних конвенцій, таких як _private_memberта output_property_, або більш досконалих методів створення незмінних об'єктів.
Мефі

11
У літературі з ООД існує прикрою тенденція представляти кожен принцип дизайну як принцип дизайну ОО . Література (неакадемічна) OOD має тенденцію малювати картину "темного віку", коли всі робили все не так, і тоді практикуючі ООП приносять світло. Наскільки я можу сказати, це здебільшого походить від незнання. Наприклад, наскільки я можу сказати, Боб Мартін надавав функціональному програмуванню серйозний вигляд лише кілька років тому.
Дерек Елкінс

31

По-перше, те, що орієнтується на процедуру та об'єкт, не має нічого спільного з публічним та приватним. Безліч об'єктно-орієнтованих мов не мають поняття контролю доступу.

По-друге, у "С" - який більшість людей називали б процедурним, а не об'єктно-орієнтованим, існує безліч хитрощів, якими можна скористатися, щоб ефективно зробити речі приватними. Дуже поширеним є використання непрозорих (наприклад, пустоти *) покажчиків. Або - ви можете переслати об’єкт, а просто не визначити його у файлі заголовка.

foo.h:

struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...

foo.c:

struct queue {
    int* head;
    int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }

Подивіться на Windows SDK! Він використовує HANDLE та UINT_PTR, а такі речі є загальними ручками для пам’яті, що використовується в API - це ефективно робить реалізації приватними.


1
Мій зразок продемонстрував кращий підхід (С) - використовуючи вперед оголошені структури. для використання підходу void * я би використовував typedefs: у файлі .h кажіть typedef void * queue, а потім скрізь у нас була черга на структуру просто сказати чергу; Потім у файлі .c перейменуйте структуру черги на struct queueImpl, і будь-які аргументи стають чергою (isntead структури черги *), і перший рядок коду для кожної такої функції стає struct queueImpl * qi = (struct queueImpl *) q
Lewis Pringle

7
Хм. Це робить його приватним, оскільки ви не можете отримати доступ (прочитати чи записати) будь-які поля 'черги' з будь-якого місця, окрім її реалізації (файл foo.c). Що ще ви мали на увазі під приватним? BTW - це справедливо для НАДУ типу typedef void * apporach та (краще) вперед оголосити структуру підходу
Lewis Pringle

5
Я мушу зізнатися, що минуло майже 40 років, як я прочитав книгу на smalltalk-80, але я не пригадую жодного поняття членів публічних чи приватних даних. Я думаю, що CLOS теж не мав такого поняття. Об'єкт Паскаля такого поняття не мав. Я пригадую, що Simula зробив (ймовірно, де Stroustrup отримав ідею), і більшість мов OO з моменту C ++. Так чи інакше - ми погоджуємось, що інкапсуляція та приватні дані - це хороші ідеї. Навіть оригінальний запитувач був зрозумілий з цього приводу. Він просто запитував - як старі люди роблять інкапсуляцію мовами, що передували C ++.
Льюїс Прінгл

5
@LewisPringle в Smalltalk-80 не згадується про публічні члени даних, оскільки всі "змінні екземпляри" (члени даних) є приватними, якщо ви не використовуєте відображення. AFAIU Smalltalkers виписує доступ до кожної змінної, яку вони хочуть оприлюднити.
декор

4
@LewisPringle, навпаки, всі "методи" Smalltalk (члени функцій) є загальнодоступними (існують незграбні умови для позначення їх приватними)
dcorking

13

"Непрозорі типи даних" були добре відомою концепцією, коли я закінчував ступінь інформатики 30 років тому. Ми не висвітлювали OOP, оскільки в той час воно не було загальноприйнятим, а "функціональне програмування" вважалося більш правильним.

Модула-2 мала для них пряму підтримку, див. Https://www.modula2.org/reference/modules.php .

Льюїс Прінгл вже пояснив, як вперед декларувати структуру можна використовувати в C. На відміну від Модуля-2, для створення об'єкта потрібно було забезпечити заводську функцію. ( Віртуальних методів також було легко реалізувати в C , коли перший член структури був вказівником на іншу структуру, яка містила вказівники функції на методи.)

Часто використовувались і умовні умови. Наприклад, будь-яке поле, що починається з "_", не має доступу до файлу, який належить даним. Це легко забезпечити створення спеціальних інструментів перевірки.

У кожному масштабному проекті, над яким я працював, (до того, як я перейшов на C ++, тоді C #), була створена система, яка запобігала доступу до "приватних" даних неправильним кодом. Це було трохи менше стандартизовано, ніж зараз.


9

Зауважте, існує багато мов OO без вбудованої можливості позначати членів приватними. Це можна зробити за умовами, без необхідності компілятора застосовувати конфіденційність. Наприклад, люди часто префіксують приватні змінні з підкресленням.

Існують методи, що ускладнюють доступ до "приватних" змінних, найбільш поширеною є ідіома PIMPL . Це ставить ваші приватні змінні в окрему структуру, з лише покажчиком, виділеним у ваших загальнодоступних файлах заголовків. Це означає додатковий заниження та подання для отримання будь-яких приватних змінних, таких як ((private_impl)(obj->private))->actual_value, що дратує, тому на практиці використовується рідко.


4

У структурах даних не було "членів", а лише поля даних (припускаючи, що це тип запису). Видимість зазвичай встановлювалася для всього типу. Однак це може бути не таким обмежуючим, як ви думаєте, оскільки функції не були частиною запису.

Давайте відкинемося назад і трохи пізнаємо історію тут ...

Домінуюча парадигма програмування перед ООП називалася структурованим програмуванням . Початковою основною метою цього було уникнути використання неструктурованих вискакувань ("goto" s). Це парадигма, орієнтована на контрольний потік (в той час як OOP більш орієнтована на дані), але все ж це було природним розширенням її для спроби зберегти дані логічно структурованими так само, як код.

Іншим наслідком структурованого програмування було приховування інформації , ідея про те, що реалізація структури коду (яка, швидше за все, змінюється досить часто) повинна триматися окремо від інтерфейсу (який в ідеалі не зміниться майже так само сильно). Зараз це догма, але за старих часів багато людей насправді вважали за краще, щоб кожен розробник ознайомився з деталями всієї системи, тож це було свого часу насправді суперечливою ідеєю. Оригінальне видання " Міфічного чоловіка місяця" Брука фактично заперечувало проти приховування інформації.

Пізніші мови програмування, явно розроблені як хороші Структуровані мови програмування (наприклад, Modula-2 і Ada), як правило, включали інформацію, що приховується як основну концепцію, побудовану навколо якогось поняття згуртованої функції функцій (і будь-яких типів, констант і об'єкти, які вони можуть знадобитися). У Модулі-2 вони називалися "Модулі", в Ада - "Пакети". Багато сучасних мов OOP називають те саме поняття "просторами імен". Ці простори імен були організаційною основою розвитку цих мов, і для більшості цілей можна було використовувати аналогічно класам OOP (без реальної підтримки успадкування, звичайно).

Отже, в Modula-2 та Ada (83) ви могли оголосити будь-яку рутину, тип, константу чи об'єкт у просторі імен приватним чи загальнодоступним, але якщо у вас був тип запису, не було (простий) спосіб оголосити деякі поля запису загальнодоступними та інші приватні. Або весь ваш запис є загальнодоступним, або його немає.


Я провів зовсім небагато часу, працюючи на Аді. Вибірковий приховування (частини типу даних) було чимось, що ми робили весь час; у пакеті, що містить, ви б визначили сам тип як приватний або обмежений приватний; інтерфейс пакета може відкрити публічні функції / процедури для отримання та / або встановлення внутрішніх полів. Цим процедурам, звичайно, потрібно було б взяти параметр приватного типу. Я цього не робив і зараз не вважаю це важким.
Девід

Крім того, більшість мов AFAIK працюють однаково під кришкою, тобто myWidget.getFoo () реально реалізований як getFoo (myWidget). object.method()Покликання просто синтаксичний цукор. Важливий ІМХО - див. Принцип Меєра щодо рівномірного доступу / довідки - але все ще лише синтаксичний цукор.
Девід

@David - Це було аргументом спільноти Ada протягом багатьох років епохи Ада 95. Я вважаю, що вони, нарешті, поступилися і довели свій власний аргумент, object.method()використовуючи альтернативну форму method(object, ...) для людей, які просто не змогли зробити концептуальний стрибок.
ТЕД

0

У C ви вже можете переходити навколо покажчиків на оголошені, але невизначені типи, як говорили інші, фактично обмежуючи доступ до всіх полів.

Ви також можете мати приватні та загальнодоступні функції на основі модулів до модулів. Функції, оголошені статичними у вихідному файлі, не видимі зовні, навіть якщо ви намагаєтесь відгадати їх ім'я. Так само ви можете мати статичні глобальні змінні на рівні файлів, що, як правило, є поганою практикою, але дозволяє ізолювати на основі модуля.

Напевно, важливо підкреслити, що обмеження доступу як добре стандартизована конвенція, а не мовна конструкція працює чудово (див. Python). Крім того, обмеження доступу до об'єктних полів захищає програміста лише тоді, коли після створення необхідності змінюється значення даних усередині об'єкта. Який уже кодовий запах. Можливо, constключове слово C та, зокрема, C ++ для методів та аргументів функцій набагато більше допомагають програмісту, ніж досить бідні Java final.


Єдиною особливістю C, яка була спеціально для приховування інформації, були staticглобальні дані та операції (це означало, що вони не були представлені в линкер для використання з інших компіляцій). Ви можете правдоподібно стверджувати, що будь-яка підтримка C мала добру практику дизайну програмного забезпечення, окрім цього, це було значною мірою злом, і не є частиною оригінального дизайну мови ще в 1972 році.
ТЕД,

0

Якщо ваше визначення Public - це можливість отримати доступ до реалізації та даних / властивостей за допомогою власного коду в будь-який момент, відповідь проста: Так . Однак її абстрагували різними способами - залежно від мови.

Я сподіваюся, що це лаконічно відповіло на ваше запитання.


-1

Ось дуже простий зустрічний приклад: у Java interfaces визначають об'єкти, а classes - ні. A classвизначає абстрактний тип даних, а не об'єкт.

Ergo, щоразу, коли ви використовуєте privateв classJava, ви маєте приклад структури даних з приватними членами, яка не орієнтована на об'єкти.


7
Ця відповідь, безумовно, технічно правильна, але абсолютно незрозумілою для тих, хто вже не знає, що таке ADT та чим вони відрізняються від об'єктів.
амон

1
Я дізнався щось із цієї відповіді.
маленькийО

3
Інтерфейси не "визначають" об'єкти; вони визначають договори на операції / поведінку, які об'єкти можуть виконувати або виконувати. Так само , як успадкування , як правило , описується це співвідношення і складу по має відношення, інтерфейси , як правило , описуються може зробити відносини.
code_dredd
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.