Этот вопрос о том, когда использовать приватный, а когда использовать защищенный в классах, заставил меня задуматься. (Я распространю этот вопрос также на окончательные классы и методы, так как он связан. Я программирую на Java, но я думаю, что это актуально для каждого языка ООП)
Хорошее практическое правило: сделайте все как можно более приватным.
- Сделайте все классы окончательными, если вам не нужно сразу создать их подкласс.
- Сделайте все методы окончательными, если вам не нужно сразу создавать подклассы и переопределять их.
- Сделайте все параметры метода окончательными, если вам не нужно изменять их в теле метода, что в любом случае довольно неудобно.
Это довольно просто и понятно, но что если я в основном пишу библиотеки (с открытым исходным кодом на GitHub) вместо приложений?
Я мог бы назвать много библиотек и ситуаций, где
- Библиотека была расширена таким образом, что разработчики никогда бы не подумали
- Это должно было быть сделано с помощью "магии загрузчика классов" и других хаков из-за ограничений видимости
- Библиотеки привыкли к тому, что они не созданы, а необходимый функционал «взломан» в
- Библиотеки не могут быть использованы из-за небольшой проблемы (ошибка, отсутствие функциональности, «неправильное» поведение), которую нельзя изменить из-за ограниченной видимости
- Проблема, которая не могла быть устранена, привела к огромным, уродливым и ошибочным обходным путям, где могло бы помочь переопределение простой функции (частной или конечной)
И я фактически начал называть их, пока вопрос не стал слишком длинным, и я решил удалить их.
Мне нравится идея не иметь больше кода, чем нужно, больше видимости, чем нужно, больше абстракции, чем нужно. И это может сработать при написании приложения для конечного пользователя, где код используется только теми, кто его пишет. Но как это действует, если код предназначен для использования другими разработчиками, когда маловероятно, что первоначальный разработчик заранее продумал все возможные варианты использования, а изменения / рефакторы трудно / невозможно сделать?
Поскольку большие библиотеки с открытым исходным кодом не являются чем-то новым, каков наиболее распространенный способ обработки видимости в таких проектах с объектно-ориентированными языками?
Ответы:
К сожалению, многие библиотеки пишутся , а не проектируются . Это печально, потому что предварительная мысль может предотвратить множество проблем в будущем.
Если мы планируем проектировать библиотеку, будет некоторый набор ожидаемых вариантов использования. Библиотека может не удовлетворять всем сценариям использования напрямую, но может служить частью решения. Таким образом, библиотека должна быть достаточно гибкой, чтобы адаптироваться.
Ограничение состоит в том, что обычно не стоит брать исходный код библиотеки и модифицировать его для обработки нового варианта использования. Для проприетарных библиотек источник может быть недоступен, а для библиотек с открытым исходным кодом может быть нежелательно поддерживать разветвленную версию. Возможно, нецелесообразно объединить очень специфические адаптации в основной проект.
Вот тут-то и возникает принцип открытого-закрытого: библиотека должна быть открыта для расширения без изменения исходного кода. Это не приходит естественно. Это должно быть намеренной целью дизайна. Здесь есть множество методов, которые могут помочь, классические шаблоны проектирования ООП - некоторые из них. В общем, мы указываем хуки, где пользовательский код может безопасно подключаться к библиотеке и добавлять функциональность.
Просто сделать каждый метод общедоступным или позволить каждому классу быть разделенным на подклассы недостаточно для достижения расширяемости. Прежде всего, действительно трудно расширить библиотеку, если неясно, где пользователь может подключиться к библиотеке. Например, переопределение большинства методов небезопасно, поскольку метод базового класса был написан с неявными допущениями. Вам действительно нужно разработать для расширяемости.
Что еще более важно, если что-то является частью общедоступного API, вы не можете его вернуть. Вы не можете изменить его, не нарушая нижестоящий код. Преждевременная открытость ограничивает библиотеку неоптимальным дизайном. Напротив, сделать внутренний материал частным, но добавить хуки, если позже они понадобятся, - более безопасный подход. Хотя это нормальный способ решения долгосрочной эволюции библиотеки, это неудовлетворительно для пользователей, которым необходимо использовать библиотеку прямо сейчас .
Так что же происходит вместо этого? Если текущее состояние библиотеки вызывает серьезные затруднения, разработчики могут взять все знания о реальных случаях использования, накопленные с течением времени, и написать версию 2 библиотеки. Это будет замечательно! Это исправит все эти ошибки в дизайне! Это также займет больше времени, чем ожидалось, во многих случаях кончится. И если новая версия сильно отличается от старой, может быть трудно поощрить пользователей к миграции. Затем вы оставили две несовместимые версии.
источник
Каждый открытый и расширяемый класс / метод является частью вашего API, который должен поддерживаться. Ограничение этого набора разумным подмножеством библиотеки обеспечивает максимальную стабильность и ограничивает количество вещей, которые могут пойти не так. Это управленческое решение (и даже проекты OSS управляются в определенной степени), основанное на том, что вы можете разумно поддержать.
Разница между OSS и закрытым исходным кодом заключается в том, что большинство людей пытаются создать и развить сообщество вокруг кода, чтобы библиотеку обслуживало более одного человека. Тем не менее, есть ряд доступных инструментов управления:
В зрелых проектах вы увидите что-то вроде этого:
В этот момент, если изменение было принято, но пользователь хочет ускорить его исправление, он может выполнить работу и отправить либо запрос на извлечение, либо исправление (в зависимости от инструмента контроля версий).
Ни один API не является статичным. Однако его рост должен быть определенным образом. Держа все закрытыми до тех пор, пока не возникнет явная необходимость открыть вещи, вы избежите репутации глючной или нестабильной библиотеки.
источник
Я перефразирую свой ответ, так как кажется, что он поразил некоторых людей.
Видимость свойства / метода класса не имеет ничего общего ни с безопасностью, ни с открытостью исходного кода.
Причина, по которой существует видимость, заключается в том, что объекты хрупки по 4 конкретным проблемам:
Если вы соберете свой модуль без инкапсуляции, то ваши пользователи привыкнут к непосредственному изменению состояния модуля. Это прекрасно работает в однопоточной среде, но если вы даже подумаете о добавлении потоков; вы будете вынуждены сделать состояние частным и использовать блокировки / мониторы вместе с геттерами и сеттерами, которые заставляют другие потоки ждать ресурсов, а не гоняться за ними. Это означает, что ваши пользовательские программы больше не будут работать, потому что частные переменные не могут быть доступны обычным способом. Это может означать, что вам нужно много переписать.
Правда в том, что кодирование намного проще с учетом однопоточной среды выполнения, а закрытое ключевое слово позволяет просто добавить синхронизированное ключевое слово или несколько блокировок, и код ваших пользователей не сломается, если вы инкапсулировали его с самого начала ,
У каждого объекта есть куча вещей, которые должны быть истинными, чтобы быть в согласованном состоянии. К сожалению, эти вещи живут в видимом для клиента пространстве, потому что дорого перемещать каждый объект в отдельный процесс и общаться с ним посредством сообщений. Это означает, что объекту очень легко разрушить всю программу, если пользователь имеет полную видимость.
Это неизбежно, но вы можете предотвратить случайный перевод объекта в несогласованное состояние, закрыв интерфейс своих служб, предотвращая случайные сбои, позволяя пользователю взаимодействовать с состоянием объекта только через тщательно разработанный интерфейс, который делает программу намного более надежной. , Это не означает, что пользователь не может преднамеренно повредить инварианты, но если они это сделают, то это их клиент, который падает, все, что им нужно сделать, это перезапустить программу (данные, которые вы хотите защитить, не должны храниться на стороне клиента ).
Еще один хороший пример, где вы можете улучшить удобство использования ваших модулей - сделать конструктор приватным; потому что если конструктор выдает исключение, он убьет программу. Один из ленивых подходов к решению этой проблемы - заставить конструктор выдать ошибку времени компиляции, которую вы не можете создать, если она не находится в блоке try / catch. Сделав конструктор закрытым и добавив открытый статический метод create, вы можете получить метод create, возвращающий значение null, если он не сможет его создать, или воспользоваться функцией обратного вызова для обработки ошибки, что сделает программу более удобной для пользователя.
Многие классы имеют много состояний и методов, и их легко перепутать, пытаясь прокрутить их; Многие из этих методов - просто визуальный шум, такой как вспомогательные функции, состояние. конфиденциальность переменных и методов помогает уменьшить загрязнение области и облегчить пользователям поиск услуг, которые они ищут.
По сути, это позволяет вам избежать использования вспомогательных функций внутри класса, а не за его пределами; без контроля видимости, не отвлекая пользователя кучей сервисов, которые пользователь никогда не должен использовать, так что вы можете сойти с рук, разбив методы на кучу вспомогательных методов (хотя это все равно будет загрязнять вашу область, но не пользователя).
Хорошо разработанный интерфейс может скрывать свои внутренние базы данных / окна / образы, от которых он зависит, чтобы выполнять свою работу, и если вы хотите перейти на другую базу данных / другую систему управления окнами / другую библиотеку изображений, вы можете оставить интерфейс таким же, как и у пользователей. не заметит
С другой стороны, если вы этого не сделаете, вы легко можете сделать невозможным изменение ваших зависимостей, потому что они выставлены, и код полагается на это. При достаточно большой системе стоимость миграции может стать недоступной, в то время как ее инкапсуляция может защитить хорошо работающих пользователей клиентов от будущих решений об обмене зависимостями.
источник