Коли слід використовувати приватний / внутрішній клас?


21

Щоб уточнити, про що я прошу

public class A{
    private/*or public*/ B b;
}

vs.

public class A{
    private/*or public*/ class B{
        ....
    }
}

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


2
Відповідь залежатиме від засобів, що надаються мовними та основними бібліотеками. Припускаючи мову C #, спробуйте запустити їх за допомогою StyleCop. Я впевнений, що ви отримаєте попередження про відокремлення цього класу у його власний файл. Це означає, що достатньо людей у ​​MSFT вважають, що вам не потрібно використовувати жоден із прикладів, які ви використовували.
Робота

Що може бути переконливим прикладом, якщо академічні приклади недостатньо хороші?

@Job StyleCop попередить лише тоді, коли внутрішні класи видно зовні. Якщо він приватний, це повністю нормально і насправді має багато застосувань.
julealgon

Відповіді:


20

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


2
Ось як я їх використовую. Якщо клас з функціональної точки зору - це не що інше, як приватні деталі реалізації іншого класу, то його слід оголосити таким чином, як і приватний метод чи поле. Немає причин забруднювати простір імен сміттям, до якого ніде не звикнути.
Aaronaught

9

Припустимо, ви будуєте дерево, список, графік тощо. Чому слід виставляти внутрішні деталі вузла чи комірки зовнішньому світу?

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

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

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


5

На мою думку, справедливий коп використовувати внутрішньо визначені класи для класів, які коротко використовуються в класі, щоб дозволити конкретне завдання.

Наприклад, якщо вам потрібно прив’язати список даних, що складається з двох непов'язаних класів:

public class UiLayer
{
    public BindLists(List<A> as, List<B> bs)
    {
        var list = as.ZipWith(bs, (x, y) => new LayerListItem { AnA = x, AB = y});
        // do stuff with your new list.
    }

    private class LayerListItem
    {
        public A AnA;
        public B AB;
    }
}

Якщо ваш внутрішній клас використовується іншим класом, слід поставити його окремо. Якщо ваш внутрішній клас містить будь-яку логіку, слід поставити його окремо.

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


2
Якщо припустити C #, не можна тут просто використовувати кортежі?
Робота

3
@Job Звичайно, але іноді tuple.ItemXробить код незрозумілим.
Lasse Espeholt

2
@Job yup, lasseespeholt отримав це право. Я вважаю за краще внутрішній клас {string Title; рядок Опис; }, ніж Tuple <рядок, рядок>.
Ед Джеймс

чи не було б тоді сенсу класти цей клас у власний файл?
Робота

Ні, не, якщо ви лише реально використовуєте їх, щоб коротко пов'язати деякі дані, щоб досягти завдання, яке не виходить за рамки компетенції батьківського класу. Принаймні, не на мій погляд!
Ед Джеймс

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

  • Вкладені класи іншою мовою (наприклад, C ++) не мають такої прив'язки. Ви просто використовуєте контроль масштабування та доступності, який надає клас.


3
Насправді у Java є і те, і інше .
Йоахім Зауер

3

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


це питання десь задокументовано?
гнат

Downvoter: чи моє твердження неправильне?
Кевін Клайн

1
Я заявив, що у вашій відповіді відсутні деталі, а не тому, що вона неправильна. Відсутність деталей робить ваше твердження важким для перевірки. Якщо ви додасте більше до цього питання, я, швидше за все, повернуться до оновлення, тому що ваша інформація виглядає досить цікавою і може бути корисною
gnat

1
@gnat: Я трохи провів дослідження, і хоча це здається загальновідомою правдою, я не знайшов остаточного посилання на обмеження Java HotSwap.
Кевін Клайн

ми не на Skeptics.SE :) просто деякі посилання зробили б, це не повинно бути остаточним
gnat

2

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


2

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

// async_op.h -- someone else's api.
struct callback
{
    virtual ~callback(){}
    virtual void fire() = 0;
};

void do_my_async_op(callback *p);



// caller.cpp -- my api
class caller
{
private :
    struct caller_inner : callback
    {
        caller_inner(caller &self) : self_(self) {}
        void fire() { self_.do_callback(); }
        caller &self_;
    };

    void do_callback()
    {
        // Real callback
    }

    caller_inner inner_;

public :
    caller() : inner_(*this) {}

    void do_op()
    {
        do_my_async_op(&inner_);
    }
};

У цьому випадку do_my_async_op вимагає передавання йому об’єкта типу зворотного виклику. Цей зворотний дзвінок має підпис функції громадського члена, який використовує API.

Коли do_op () викликається з external_class, він використовує екземпляр приватного внутрішнього класу, який успадковує необхідний клас зворотного виклику замість вказівника на себе. Зовнішній клас має посилання на зовнішній клас для простої мети шунтування зворотного виклику в приватну функцію члена do_callback зовнішнього класу .

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


2

За словами Джона Скета в C # in Depth, використання вкладеного класу було єдиним способом реалізувати повністю ледачий безпечний для потоків синглтон (див. П'яту версію) (до .NET 4).


1
це ідіома " Ініціалізація на вимогу" , на Java також потрібен приватний статичний внутрішній клас. Рекомендується в JMM FAQ , Брайан Гетц в JCiP 16,4 і Джошуа Блох в JavaOne 2008 Понад ефективної Java (PDF) «для високопродуктивних на статичне поле ...»
комар

1

Приватні та внутрішні класи використовуються для підвищення рівня інкапсуляції та приховування деталей реалізації.

У C ++, крім приватного класу, тієї ж концепції можна досягти, реалізуючи анонімний простір імен класу cpp. Це добре допомагає приховати / приватизувати деталі реалізації.

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


Будь-які конструктивні відгуки про -1?
hiwaylon

1
Я не спровокував, але я б припустив, що ти насправді не відповідаєш на питання: ти не даєш жодних причин використовувати приватний / внутрішній клас. Ви просто описуєте, як це працює і як зробити подібну річ в c ++
Саймон Бергот

Справді. Я подумав, що це було очевидним моїми та іншими відповідями (приховування деталей реалізації, підвищення рівня інкапсуляції тощо), але я спробую уточнити деякі. Дякую @Simon
hiwaylon

1
Приємна редакція. І, будь ласка, не ображайтесь через подібну реакцію. Мережа SO намагається застосувати політику щодо покращення співвідношення сигнал / шум. Деякі люди досить суворі щодо питання / відповіді, і коли-небудь забувають бути приємними (коментуючи, наприклад, свій голос)
Simon Bergot

@ Симон Дякую Це неприємно через низьку якість зв'язку, яку він демонструє. Може бути, занадто просто бути "суворим" за анонімною завісою Інтернету? О, нічого нового я не здогадуюсь. Ще раз дякую за позитивні відгуки.
hiwaylon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.