Подтвердить что ты не робот

Рекомендации по дизайну. Когда использовать "виртуальные" и "запечатанные"

Я пишу сетевую библиотеку С# (в основном как учебное упражнение, это не слишком важно для меня, если кто-то на самом деле заканчивает использовать его, поскольку я уверен, что решения уже там).

Я доволен своей структурой до сих пор... У меня есть несколько уровней доступного клиента/сервера, которые могут связываться в необработанных байтах над сокетами или немного сложнее с помощью сериализованных объектов сообщений.

Вопрос (проблема?), с которым я столкнулся, - это когда я должен объявить метод, свойство или событие sealed, virtual или без квалификатора.

Я знаю, что все это делает - sealed предотвращает наследование класса или дальнейшее переопределение метода. virtual позволит полиморфное поведение посредством переопределения метода.

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

Любые общие указатели или советы, которые следует учитывать при разработке моих классов для использования другими?

Этот вопрос и этот были несколько полезны, как и этот, но поскольку я пишу дистрибутивную библиотеку, я пытаюсь охватить все мои базы.

4b9b3361

Ответ 1

Начните с Руководства по разработке Microsoft для разработки библиотек классов, в частности Extensibility, где вы найдете статьи в виртуальных участниках и уплотнительный.

Цитата:

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

  • Не закрывайте классы, не имея для этого веских оснований.

  • Не объявлять защищенные или виртуальные элементы на закрытых типах.
  • Рассмотрим уплотнительные элементы, которые вы переопределите.

Прочитайте полные статьи.

Ответ 2

Прежде всего, я согласен с другими ответами, которые предлагают агрессивно запечатывать все, что специально не предназначено для расширения. Я не согласен с тем, что вам нужна причина для печати; скорее, вам нужна причина оставить что-то незапечатанным.

Ваш вопрос очень общий, так что здесь общий ответ: ваша библиотека, по-видимому, представляет собой набор функций, которые можно использовать в качестве инструментов для решения проблем пользователей. Кажется, что каждая функция в вашей библиотеке существует потому, что вы провели какое-то исследование, обнаружили, что существует проблема с пользователем, которая нуждается в решении, и решила ее для них.

Каждая из этих функций имеет определенную стоимость, связанную с ней. Некоторые из этих затрат в прошлом - время, которое вы потратили на разработку, внедрение, тестирование, отладку и доставку кода. Некоторая часть этой стоимости еще впереди: обслуживание, чтение отчетов об ошибках и т.д. Есть также более тонкие затраты, например, возможно, поддержание обратной совместимости с одной из ваших существующих функций повысит стоимость реализации новой функции завтра.

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

Ответ 3

На мой взгляд, вы действительно не можете предвидеть, что в конечном итоге будут использовать ваши пользователи. Таким образом, обычно хорошая идея НЕ запечатывать что-либо, если не существует какого-либо внутреннего поведения, которое вы не хотите, чтобы пользователи сбрасывали (т.е. Им нужно знать, что это нужно установить первым, прежде чем вы это сделаете и т.д.).

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

Ответ 4

Было много раз, когда я сказал: "Черт возьми, почему этот класс запечатан!", но я никогда не говорил "черт возьми, я бы хотел, чтобы они запечатали этот класс!"

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

Ответ 5

Объявление метода virtual подразумевает определенный контракт: ваш класс соглашается с тем, что он может быть переопределен и предвосхищает это.

Обычно есть явная причина сделать что-то виртуальное. Когда вы сомневаетесь: не делайте.

И запечатанный - это все наоборот, вы можете заявить, что не хотите, чтобы он был переопределен. Вторая причина может быть здесь, но я не буду использовать ее слишком быстро.

Ответ 6

Проектирование класса для расширяемости посредством наследования, в общем, непросто. Когда вы запечатываете что-то, вы говорите, что класс действительно не подходит для наследования реализации (возможно, вы делаете кучу низкоуровневых материалов с небезопасным кодом, например).

Для максимальной совместимости (в математическом смысле) я предлагаю запечатать все, что не является абстрактным классом. Фактически, именно так реализуется Алгебраический тип данных (в данном случае Тип суммы) на языке (проверьте http://blog.lab49.com/archives/3011 для сногсшибательная статья).

Ответ 7

Спорные мнение:. классы С# должны быть запечатаны по умолчанию

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

Таким образом, классы по умолчанию по умолчанию, если вы явно не захотите разрешить это. И если вы хотите разрешить это, убедитесь, что вы документируете, какие методы должны быть переопределены путем создания виртуального и что должны были переопределить методы, чтобы гарантировать, что класс продолжает работать (например, вызов базового метода).

Ответ 8

Здесь есть интерес конфликт.

  • Если вы хотите, чтобы ваши звонящие проверяли свой код (например, для разработки, основанной на тестах), ваши пользователи должны иметь возможность высмеивать ваш класс.
    • поэтому все методы ect должны быть виртуальными или у вас должен быть реализованный интерфейс, содержащий все методы.
  • Однако, если вы хотите четко указать, какой контракт вы предоставляете в любых подклассах, которые могут создать ваши пользователи, вы должны придерживаться рекомендаций Microsoft.
    • Не делайте участников виртуальными, если у вас нет веских оснований для этого, и вам известны все затраты, связанные с проектированием, тестированием и поддержкой виртуальных участников Жаль, что нет способа отметить виртуальный метод, чтобы сказать, что вы не ожидаете, что какой-либо клиентский код будет реализовывать его, кроме насмешек.