Как разработчик C ++ я довольно привык к заголовочным файлам C ++ и считаю полезным иметь какую-то принудительную «документацию» внутри кода. У меня обычно бывает плохое время, когда мне приходится читать код C # из-за этого: у меня нет такой ментальной карты класса, с которой я работаю.
Давайте предположим, что как инженер-программист я разрабатываю каркас программы. Было бы слишком безумно определять каждый класс как абстрактный не реализованный класс, аналогично тому, что мы будем делать с заголовками C ++, и позволять разработчикам реализовывать его?
Я предполагаю, что могут быть некоторые причины, почему кто-то может найти это ужасным решением, но я не уверен, почему. Что нужно рассмотреть для такого решения?
c#
c++
object-oriented-design
abstract-class
Дани Барса Касафонт
источник
источник
Ответы:
Причина, по которой это было сделано в C ++, заключалась в том, чтобы сделать компиляторы быстрее и проще в реализации. Это был не дизайн, чтобы облегчить программирование .
Цель заголовочных файлов состояла в том, чтобы дать возможность компилятору выполнить супер быстрый первый проход, чтобы узнать все ожидаемые имена функций и выделить для них места в памяти, чтобы к ним можно было обращаться при вызове в файлах cp, даже если определяющий их класс имел еще не разобрали.
Попытка воспроизвести последствия старых аппаратных ограничений в современной среде разработки не рекомендуется!
Определение интерфейса или абстрактного класса для каждого класса снизит вашу производительность; что еще ты мог сделать с этим временем ? Кроме того, другие разработчики не будут следовать этому соглашению.
Фактически, другие разработчики могут удалить ваши абстрактные классы. Если я нахожу интерфейс в коде, который удовлетворяет обоим этим критериям, я удаляю его и реорганизую его из кода:
1. Does not conform to the interface segregation principle
2. Only has one class that inherits from it
Другое дело, что в Visual Studio есть инструменты, которые выполняют то, что вы хотите выполнить автоматически:
Class View
Object Browser
Solution Explorer
вы можете нажать на треугольники, чтобы расходовать классы, чтобы увидеть их функции, параметры и типы возвращаемых данных.Попробуйте один из вышеперечисленных, прежде чем уделять время репликации заголовочных файлов C ++ в C #.
Кроме того, есть технические причины не делать этого ... это сделает ваш окончательный двоичный файл больше, чем нужно. Я повторю этот комментарий Марка Беннингфилда:
Также, как отметил Роберт Харви, технически самым близким эквивалентом заголовка в C # был бы интерфейс, а не абстрактный класс.
источник
Во-первых, следует понимать, что чисто абстрактный класс - это просто интерфейс, который не может выполнять множественное наследование.
Напишите класс, извлеките интерфейс, это занятие для мозга. Настолько, что у нас есть рефакторинг для этого. Что жаль. Следуя этому шаблону «каждый класс получает интерфейс», он не только создает беспорядок, но и полностью теряет смысл.
Интерфейс не должен рассматриваться как просто формальное подтверждение того, что может сделать класс. Интерфейс должен рассматриваться как контракт, заключаемый с использованием клиентского кода, детализирующего его потребности.
У меня нет проблем с написанием интерфейса, который в настоящее время имеет только один класс, реализующий его. Мне действительно все равно, если ни один класс вообще не реализует его. Потому что я думаю о том, что нужно для моего кода. Интерфейс выражает то, что требует использование кода. Все, что приходит позже, может делать то, что ему нравится, при условии, что оно соответствует этим ожиданиям.
Теперь я не делаю это каждый раз, когда один объект использует другой. Я делаю это при пересечении границы. Я делаю это, когда не хочу, чтобы один объект точно знал, с каким другим объектом он разговаривает. Это единственный способ, которым полиморфизм будет работать. Я делаю это, когда ожидаю, что объект, о котором говорит мой клиентский код, может измениться. Я, конечно, не делаю этого, когда я использую класс String. Класс String хорош и стабилен, и я не чувствую необходимости защищать его от меня.
Когда вы решаете напрямую взаимодействовать с конкретной реализацией, а не через абстракцию, вы предсказываете, что реализация достаточно стабильна, чтобы доверять, чтобы не изменяться.
Именно так я и усмиряю принцип обращения зависимости . Вы не должны слепо фанатично применять это ко всему. Когда вы добавляете абстракцию, вы действительно говорите, что не доверяете выбору реализации класса, который будет стабильным в течение всего жизненного цикла проекта.
Все это предполагает, что вы пытаетесь следовать принципу открытого закрытого типа . Этот принцип важен только тогда, когда затраты, связанные с внесением прямых изменений в установленный код, значительны. Одна из главных причин, почему люди не согласны с тем, насколько важны развязки объектов, заключается в том, что не все испытывают одинаковые затраты при внесении прямых изменений. Если повторное тестирование, перекомпиляция и перераспределение всей вашей кодовой базы является тривиальным для вас, то решение необходимости изменений с помощью прямого изменения, вероятно, является очень привлекательным упрощением этой проблемы.
На этот вопрос просто нет мертвого ответа. Интерфейс или абстрактный класс - это не то, что вы должны добавить в каждый класс, и вы не можете просто посчитать количество реализующих классов и решить, что это не нужно. Это касается изменения. Что означает, что вы предвидите будущее. Не удивляйтесь, если вы ошибаетесь. Держите это простым, как вы можете, не заглядывая в угол.
Поэтому, пожалуйста, не пишите абстракции только для того, чтобы помочь нам прочитать код. У нас есть инструменты для этого. Используйте абстракции, чтобы отделить то, что нужно отделить.
источник
Да, это было бы ужасно, потому что (1) Он вводит ненужный код (2) Это сбивает с толку читателя.
Если вы хотите программировать на C #, вы просто должны привыкнуть читать C #. Код, написанный другими людьми, в любом случае не будет следовать этому шаблону.
источник
Модульные тесты
Я настоятельно рекомендую вам писать модульные тесты вместо того, чтобы загромождать код интерфейсами или абстрактными классами (кроме случаев, когда это оправдано по другим причинам).
Хорошо написанный модульный тест описывает не только интерфейс вашего класса (как это сделал бы заголовочный файл, абстрактный класс или интерфейс), но и, например, желаемую функциональность.
Пример: где вы могли бы написать заголовочный файл myclass.h, например так:
Вместо этого, в c # напишите такой тест:
Этот очень простой тест передает ту же информацию, что и заголовочный файл. (У нас должен быть класс с именем «MyClass» с функцией без параметров «Foo»). Хотя заголовочный файл более компактен, тест содержит гораздо больше информации.
Предостережение: такой процесс, когда старший инженер-программист поставляет (проваливает) тесты другим разработчикам для насильственного разрешения столкновений с методологиями, такими как TDD, но в вашем случае это было бы огромным улучшением.
источник