Когда вы должны использовать закрытый / внутренний класс?


21

Чтобы уточнить, о чем я спрашиваю,

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

против

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

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


2
Ответ будет зависеть от возможностей, предоставляемых языком и основными библиотеками. Предполагая язык C #, попробуйте запустить их с помощью StyleCop. Я почти уверен, что вы получите предупреждение о выделении этого класса в отдельный файл. Это означает, что достаточное количество людей в MSFT считают, что вам не нужно использовать ни один из примеров, которые вы использовали.
Работа

Каким будет убедительный пример, если академические примеры не достаточно хороши?

@Job StyleCop выдаст предупреждение, только если внутренние классы видны снаружи. Если это приватно, это совершенно нормально и на самом деле имеет много применений.
Jullealgon

Ответы:


20

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


2
Вот как я их использую. Если с функциональной точки зрения класс представляет собой не что иное, как частную деталь реализации другого класса, то он должен быть объявлен таким же образом, как и частный метод или поле. Нет причин загрязнять пространство имен мусором, который никогда не будет использоваться где-либо еще.
Ааронаут

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делает код неясным.
Лассе Эспехолт

2
@ Job Да, Лассиспехолт понял это правильно. Я бы предпочел увидеть внутренний класс {string Title; Строка Описание; } чем кортеж <строка, строка>.
Эд Джеймс

в этот момент не имеет смысла помещать этот класс в свой собственный файл?
Работа

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

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

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


3
На самом деле Java имеет оба из них.
Иоахим Зауэр

3

Я избегаю внутренних классов в Java, потому что горячая замена не работает при наличии внутренних классов. Я ненавижу это, потому что я действительно ненавижу компрометировать код для таких соображений, но эти перезапуски Tomcat складываются.


эта проблема где-то задокументирована?
комнат

Downvoter: моё утверждение неверно?
Кевин Клайн

1
Я понизил голосование, потому что в вашем ответе отсутствуют детали, а не потому, что он неправильный Отсутствие деталей затрудняет проверку вашего утверждения. Если вы добавите больше по этому вопросу, я, вероятно, вернусь к upvote, потому что ваша информация выглядит довольно интересной и может быть полезной
gnat

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

мы не на Skeptics.SE :) просто какая-то ссылка подойдет, она не должна быть окончательной
комнат

2

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


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 требует, чтобы объект типа callback был передан ему. Этот обратный вызов имеет открытую подпись функции-члена, которую использует 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) «Для высокой производительности в статическом поле ...»
gnat

1

Закрытые и внутренние классы используются для повышения уровня инкапсуляции и скрытия деталей реализации.

В C ++, в дополнение к закрытому классу, та же концепция может быть реализована путем реализации анонимного пространства имен класса cpp. Это прекрасно скрывает / приватизирует детали реализации.

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


Есть конструктивные отзывы о -1?
Hiwaylon

1
Я не отрицал, но думаю, что вы на самом деле не отвечаете на вопрос: у вас нет никаких оснований использовать закрытый / внутренний класс. Вы просто описываете, как это работает и как сделать подобное в c ++
Саймон Бергот

В самом деле. Я думал, что это было очевидно для меня и других ответов (скрытие деталей реализации, повышение уровня инкапсуляции и т. Д.), Но я попытаюсь уточнить некоторые из них. Спасибо, Симон.
Hiwaylon

1
Хорошее редактирование. И, пожалуйста, не обижайтесь из-за такой реакции. Сеть SO пытается применять политики для улучшения отношения сигнал / шум. Некоторые люди довольно строги в отношении вопросов / ответов, и иногда забывают быть хорошими (например, комментируя свое отрицательное голосование)
Саймон Бергот

@ Симон Спасибо. Это разочаровывает из-за плохого качества связи, которое он демонстрирует. Может быть, слишком легко быть «строгим» за анонимной завесой в Интернете? Ну, ничего нового, я думаю. Еще раз спасибо за положительный отзыв.
Hiwaylon
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.