2010-01-07 3 views
30

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

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

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

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

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

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

This question и this one были несколько полезными, так же как this one, но так как я пишу распределяемую библиотеку, я пытаюсь охватить все мои базы.

+1

(отвечая на комментарии, которые были удалены!) @ (отредактированный) - не соответствует действительности; вы можете пометить «переопределить» как «запечатанный», чтобы предотвратить дальнейшие переопределения. –

ответ

24

Начните с Microsoft Design Guidelines for Developing Class Libraries, в частности, раздел Extensibility, где вы найдете статьи о virtual members и sealing.

Цитирование здесь:

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

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

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

Прочтите все статьи.

+0

Оставьте это в MSDN. Эти статьи очень полезны, и я буду уверен, что они будут отмечены закладками. Благодаря! :) – Sapph

+2

В этой статье MSDN говорится, что нельзя использовать делегатов и события в областях с интенсивной интенсивностью, но насколько точна эта проблема (с учетом статьи с 2005 года)? Из моего, по общему признанию, ограниченного тестирования, вызов делегата происходит так же быстро, как вызов метода интерфейса, который примерно в два раза медленнее статического метода. – JulianR

+0

Julian, это изменилось с помощью .NET 2.0: http://www.sturmnet.org/blog/2005/09/01/ Итак, вы правы; это устарело. –

2

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

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

+1

Очень интересная статья! – Sapph

6

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

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

3

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

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

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

2

Спорные мнение: C# classes should be sealed by default.

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

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

+1

Я считаю, что также есть небольшое преимущество в производительности для закрытых классов, поскольку среда выполнения точно знает, что ей не нужно искать переопределения и т. Д., И может оптимизировать ее соответствующим образом. –

19

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

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

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

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

+0

Полезный способ взглянуть на это, спасибо! – Sapph

+0

+1 для "вам нужна причина оставить что-то незапечатанное" – Doval

5

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

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

1

У вас есть интерес конфликт здесь.

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