Ваши способы написания действительно гибкого и масштабируемого кода

Первый вопрос, который приходит вам в голову: почему следует избегать конструкций switch-case?

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

Почему я постоянно добавляю новые кейсы в любые сменные кейсы?

Почему я постоянно меняю код, который раньше работал нормально?

Перенесемся на несколько месяцев после прочтения этой статьи, и вы действительно сможете создавать надежные и масштабируемые приложения. Код, написанный один раз, используется повторно, пока не сгорит дерево вызовов. Добавление новых функций без изменения старого кода. Гарантирована даже понятная история git, и проект / игра / приложение эволюционировали, а не видоизменялись.

И все потому, что вы научились избегать использования переключателя.

Причина позади

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

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

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

За несколько лет разработки на C # я много использовал кейсы switch и несколько раз сталкивался с недостатками switch-кейсов:

  • Отсутствие использования переменных означает меньшую гибкость и жестко запрограммированные случаи
  • Отсутствие использования одной и той же константы в нескольких случаях означает меньшую гибкость во время выполнения
  • Не использовать выражения отношения (==,! =, ‹= И т. Д.)
  • Отсутствие использования чисел с плавающей запятой или констант
  • Невозможно расширить за счет переопределения среды, необходимо переопределить корпус коммутатора

Основная проблема корпуса переключателя

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

Вытягивание карт из колоды во время хода.

Следовательно, нам нужен класс CardDeck, из которого мы позже сможем брать карты.

CardDeck - это класс, в котором есть поле amountOfCards для количества карт, оставшихся для розыгрыша.

Делаем это с помощью переключателя - чехол

Чтобы перестать быть простым программистом, мы должны сначала думать как базовый программист.

Следовательно, первый подход направлен на использование корпуса выключателя.

Функция Draw() проверит этот факт и примет колоду карт и тип карт в качестве параметров:

Каждый рисунок должен сообщать нам, какую карту мы нарисовали. Будет 4 варианта:

  1. Монстр-карта
  2. Magic-Card
  3. Карта-ловушка
  4. Земельная карта

Если осталась хотя бы 1 карта, мы получаем случайную запись cardTypes, которая попадает в case-case. Независимо от того, что было нарисовано, вызов PutCardToHand() распечатает тип.

Теперь функция Main вызывает этот код. Сначала мы помещаем эту функцию рисования в открытый класс SwitchCaseUser, что означает создание экземпляра:

Кроме того, cardTypes определены здесь как массив строк. Этот код приводит к

Хотя шнур работает нормально и делает то, для чего он был разработан, мы сталкиваемся с тремя серьезными проблемами:

  1. Корпус переключателя жестко запрограммирован: определение типов для рисования снаружи сделает эту функцию тесно связанной с этим внешним миром. Откуда этой функции знать, какие карты она вытягивает? Изменение внешнего пространства повлечет за собой изменение корпусов внутри этой функции.
  2. Определение типов карточек, которые можно было бы нарисовать внутри этой функции, приводит к той же проблеме, потому что теперь функция точно знает, что можно нарисовать. Но почему функция рисования должна это знать? Функция рисования должна рисовать, что бы она ни рисовала. В этом случае смена типа карты означает также смену корпуса переключателя.
  3. Расширять это поведение неудобно, а код негибкий. Потому что, если у нас есть полный функциональный класс, который делает именно то, что должен делать, и делает это хорошо, он должен оставаться таким, как есть навсегда. Мы должны программировать так, чтобы код работал и всегда должен работать. Не прикасайтесь к нему снова, если внешнее поведение изменится. Это означает передачу всех необходимых ссылок для достижения этого результата и независимости от того, что было передано.

Избегание случайного переключателя поможет устранить все упомянутые недостатки.

Как избежать переключателя - 5 возможностей

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

Каждая глава функции и каждая ее первая часть будут одинаковыми для всех пяти методов:

Внутри оператора if идет сменная часть:

Первый и второй варианты, позволяющие избежать случаев переключения, сохраняют исходный строковый массив cardTypes. Все остальные примеры основаны на преобразовании в List<string>.

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

# 1 For-Loop / ForEach-Loop

Цикл for выполняет итерацию по массиву, сравнивает cardType со всеми записями из cardTypes и передает совпадение функции PutCardToHand.

# 2 Локальная функция

Локальная функция делает почти то же самое, что и цикл for, но инкапсулирует ее в локальную функцию с помощью цикла forEach.

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

# 3 Метод IndexOf

Преобразование array в list сейчас.

Поскольку у нас есть список, мы можем использовать некоторые предыдущие функции для их агрегирования.

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

Передача этого в нашу функцию PutCardToHand приведет к желаемому поведению.

# 4 Анонимный метод

Анонимный метод похож на Хитрого Койота и его непонятного оружия, чтобы поймать дорожного бегуна. Мип Мип.

Мы должны иметь дело с определением накладных расходов.

Анонимная функция внутри именованной функции в качестве параметра приводит к двойному определению функции неисправности.

Это довольно сложный способ программирования и, следовательно, его нелегко понять впоследствии. Будьте внимательны!

((Func<string (inputParameter1), List<string> (inputParameter2), string (return parameter)>) anonymousFunction(inputParameter1, inputParameter2, returnParameter)
{
  Lambda Expression returning cardType
}(passingParameter1, passingParameter2) <- Instant Invokation

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

Следовательно, определение именованной функции и загрязнение пространства вашего класса не требуется.

Более простой способ - сохранить анонимную функцию внутри делегата, а затем вызвать ее так:

Тем не менее, анонимные функции должны быть кратковременными.

# 5 Лямбда-выражение

Анонимные функции в этом сценарии длинны и излишни. Я тебя прикрыл. Следовательно, лямбда-выражение легко и подходит для такого рода задач.

Посмотрите, как быстро и быстро вы получите результат.

Это простой лайнер с использованием метода Find() для списков.

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

Звонок в главном - Избегайте финальной стадии переключения

Поскольку это не случай переключения, я упаковал все альтернативы в класс SwitchCaseAvoider.

Внутри main мы называем его так же, как и другой:

Приведет к

Вывод

Не все из них - лучший способ подойти к этой конкретной ситуации.

Некоторые из них (например, цикл for или цикл forEach) становятся интенсивными в вычислениях, если вы пытаетесь использовать их в больших наборах данных для получения одного совпадения. Также есть встроенные функции (например, для коллекций).

Что у них общего:

Их можно расширить, просто переопределив среду, а не саму функцию.

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

Ваши программы не решают конкретных задач; они всегда решат сложную задачу или несколько задач. Гораздо важнее соблюдение общих принципов, таких как создание игр с моей серией SOLID в Unity.

Спасибо за то, что прочитали, и если вы хотите получить более выдающийся обработанный программный контент, посетите мои приключения на Udemy о C # и SQL.

Предварительно выбранные предметы костюма