Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 809
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 18 Табличные методы
405
18.1. Основные вопросы применения
табличных методов
При определенных обстоятельствах табличный код проще, чем сложные логические выражения, легче поддается изменению и эффективнее. До- пустим, вы хотите классифицировать символы, выделив буквы, знаки пре- пинания и цифры. Вы можете использовать сложную логическую последователь- ность вроде этой:
Пример использования сложной логики для классификации символов (Java)
if ( ( ( ‘a’ <= inputChar ) && ( inputChar <= ‘z’ ) ) ||
( ( ‘A’ <= inputChar ) && ( inputChar <= ‘Z’ ) ) ) {
charType = CharacterType.Letter;
}
else if ( ( inputChar == ‘ ‘ ) || ( inputChar == ‘,’ ) ||
( inputChar == ‘.’ ) || ( inputChar == ‘!’ ) || ( inputChar == ‘(‘ ) ||
( inputChar == ‘)’ ) || ( inputChar == ‘:’ ) || ( inputChar == ‘;’ ) ||
( inputChar == ‘?’ ) || ( inputChar == ‘-’ ) ) {
charType = CharacterType.Punctuation;
}
else if ( ( ‘0’ <= inputChar ) && ( inputChar <= ‘9’ ) ) {
charType = CharacterType.Digit;
}
Если бы вместо этого фрагмента вы использовали таблицу подстановки, то помес- тили бы тип каждого элемента в массив и обращались бы к нему по коду символа.
Сложный фрагмент кода, представленный выше, заменялся бы на такое выражение:
Пример использования таблицы подстановки для классификации символов (Java)
charType = charTypeTable[ inputChar ];
Этот фрагмент предполагает, что массив
charTypeTable был заранее заполнен. Вы поместили знания, доступные программе, в данные, а не в логику: в таблицу, а не в условия
if.
Два вопроса применения табличных методов
При применении табличных методов перед вами стоят два основных вопроса. Во- первых, вам надо решить, как будет выполняться поиск записей в таблице. Вы можете использовать какие-либо данные для прямого доступа к таблице. Так, если вам нужно систематизировать данные по месяцам, то выбор ключа для таблицы месяцев очевиден. Вы можете использовать массив с индексом от 1 до 12.
Другие данные затруднительно использовать для прямого поиска таблич- ной записи. Так, для классификации информации по номеру социального страхования (SSN) вы не можете использовать этот номер в качестве ключа непосредственно, если, конечно, вы не собираетесь хранить в таблице
999-99-9999 записей. Вам понадобится более сложный подход. Вот какие спосо- бы, применяются для поиска записи в таблице:
406
ЧАСТЬ IV Операторы
쐽
прямой доступ;
쐽
индексированный доступ;
쐽
ступенчатый доступ.
Каждый из этих вариантов доступа подробно описан ниже.
Второй вопрос, который нужно решить при использовании табличных методов: что хранить в таблице. Иногда результатом поиска в таблице являются данные — тогда можно хранить в таблице сами данные. Если же результатом поиска является действие, код, который описывает это действие,
можно хранить, а в некоторых языках можно хранить ссылку на метод, выпол- няющий это действие. В каждом из этих случаев таблицы усложняются.
18.2. Таблицы с прямым доступом
Как и все таблицы подстановки, таблицы с прямым доступом предназначены для замены более сложных логических структур. Они имеют «прямой доступ», пото- му что вам не нужно ходить по кругу, чтобы найти в таблице необходимую ин- формацию. Вы можете непосредственно выбрать нужную запись (рис. 18-1).
Рис. 18-1. Как следует из ее имени, таблица с прямым доступом позволяет
обращаться к требуемому элементу напрямую
Пример определения количества дней в месяце
Допустим, вы хотите получить число дней в месяце (для простоты забудем о ви- сокосных годах). Разумеется, создание большого условия
if — неуклюжий способ решения этой проблемы:
Пример неуклюжего способа определения количества дней в месяце (Visual Basic)
If ( month = 1 ) Then days = 31
ElseIf ( month = 2 ) Then days = 28
ElseIf ( month = 3 ) Then days = 31
ElseIf ( month = 4 ) Then days = 30
ElseIf ( month = 5 ) Then days = 31
ElseIf ( month = 6 ) Then
1 ... 46 47 48 49 50 51 52 53 ... 104
ГЛАВА 18 Табличные методы
407
days = 30
ElseIf ( month = 7 ) Then days = 31
ElseIf ( month = 8 ) Then days = 31
ElseIf ( month = 9 ) Then days = 30
ElseIf ( month = 10 ) Then days = 31
ElseIf ( month = 11 ) Then days = 30
ElseIf ( month = 12 ) Then days = 31
End If
Более простой и удобный для модификации способ выполнения тех же самых действий — разместить данные в таблице. В Visual Basic первым делом нужно за- полнить таблицу:
Пример элегантного способа определения количества дней в месяце (Visual Basic)
‘ Инициализируем таблицу данными о количестве дней в месяцах.
Dim daysPerMonth() As Integer = _
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
А теперь вместо создания длинного выражения
if для выяснения числа дней в месяце можно просто обратиться к элементу массива:
Пример элегантного способа определения количества
дней в месяце на Visual Basic (продолжение)
days = daysPerMonth( month-1 )
Если вы хотите учитывать високосные годы в этой версии табличного поиска, код все еще будет простым. Допустим,
LeapYearIndex() возвращает 0 или 1:
Пример элегантного способа определения количества
дней в месяце на Visual Basic (продолжение)
days = daysPerMonth( month-1, LeapYearIndex() )
Если бы в версии с выражением
if тоже учитывались високосные годы, то длин- ная строка условий
if еще более усложнилась бы.
Определение количества дней в месяце — удобный пример, так как переменную
month можно использовать для поиска записи в таблице. Для прямого доступа к таблице зачастую можно использовать данные, которые могли бы управлять пос- ледовательностью
if-выражений.
Пример со ставками страхования
Допустим, вы пишете программу для вычисления ставок медицинского страхова- ния, которые варьируются в зависимости от возраста, пола, семейного положе-
408
ЧАСТЬ IV Операторы ния и от того, курит ли страхователь. Если бы вы писали для этих ставок логиче- скую управляющую структуру, то получилось бы нечто вроде этого:
Пример неуклюжего способа расчета
ставки страхования (Java)
if ( gender == Gender.Female ) {
if ( maritalStatus == MaritalStatus.Single ) {
if ( smokingStatus == SmokingStatus.NonSmoking ) {
if ( age < 18 ) {
rate = 200.00;
}
else if ( age == 18 ) {
rate = 250.00;
}
else if ( age == 19 ) {
rate = 300.00;
}
else if ( 65 < age ) {
rate = 450.00;
}
else {
if ( age < 18 ) {
rate = 250.00;
}
else if ( age == 18 ) {
rate = 300.00;
}
else if ( age == 19 ) {
rate = 350.00;
}
else if ( 65 < age ) {
rate = 575.00;
}
}
else if ( maritalStatus == MaritalStatus.Married )
}
Эта сокращенная версия логической структуры — хорошая иллюстрация того,
насколько сложной может получиться программа. Она не учитывает замужних женщин, всех мужчин и большинства возрастов между 18 и 65 годами. Вы можете вообразить, насколько сложной станет эта структура, если запрограммировать всю таблицу ставок.
Вы можете сказать: «Да, но почему вы проверяете каждый возраст? Почему бы не поместить ставку для каждого возраста в массив?» Хороший вопрос, и одним из очевидных усовершенствований будет размещение ставок для каждого возраста в отдельных массивах.
ГЛАВА 18 Табличные методы
409
Однако лучшее решение — создать массив ставок не только для каждого возрас- та, но вообще для всех факторов. Вот как объявить такой массив на Visual Basic:
Пример объявления данных для заполнения таблицы ставок страхования (Visual Basic)
Public Enum SmokingStatus
SmokingStatus_First = 0
SmokingStatus_Smoking = 0
SmokingStatus_NonSmoking = 1
SmokingStatus_Last = 1
End Enum
Public Enum Gender
Gender_First = 0
Gender_Male = 0
Gender_Female = 1
Gender_Last = 1
End Enum
Public Enum MaritalStatus
MaritalStatus_First = 0
MaritalStatus_Single = 0
MaritalStatus_Married = 1
MaritalStatus_Last = 1
End Enum
Const MAX_AGE As Integer = 125
Dim rateTable ( SmokingStatus_Last, Gender_Last, MaritalStatus_Last, _
MAX_AGE ) As Double
Определив массив, необходимо придумать способ его за- полнения. Вы можете использовать операторы присваива- ния, читать данные из дискового файла, вычислять данные или делать что-то еще. После подготовки данные могут при- меняться при расчете ставок. Сложная логическая струк- тура, показанная ранее, заменяется простым выражением,
например:
Пример элегантного способа определения
ставки страхования (Visual Basic)
rate = rateTable( smokingStatus, gender, maritalStatus, age )
Основное преимущество этого подхода — в замене сложной логики табличным поиском. Такой код удобней читать и проще изменять.
Перекрестная ссылка Одно из преимуществ табличного подхо- да в том, что можно поместить данные из таблицы в файл и читать его во время выполне- ния. Это позволит вам изменять такие параметры, как ставки страхования, не изменяя саму программу (см. раздел 10.6).
410
ЧАСТЬ IV Операторы
Пример гибкого формата сообщения
Таблицу можно использовать для реализации такой логики, которая слишком динамична для представления в коде. В примерах по классификации символов,
количеству дней в месяцах и страховым ставкам вы хотя бы знали, что можете в случае необходимости написать длинную строку условий
if. Однако иногда дан- ные слишком сложны, чтобы жестко закодировать их с помощью операторов
if.
Если вам кажется, что вы поняли принцип работы таблиц с прямым доступом,
можете пропустить следующий пример. Тем не менее он немного сложнее пре- дыдущих и продолжает демонстрацию мощности табличных подходов.
Допустим, вы разрабатываете метод для печати сообщений, хранящихся в файле.
Обычно файл содержит около 500 сообщений, которые бывают примерно 20 видов.
Изначально сообщения поступают от бакенов и включают в себя информацию о температуре воды, расположении бакена и т. д.
Каждое сообщение имеет несколько полей и начинается с заголовка, содержаще- го идентификатор, позволяющий узнать, с каким из примерно 20 видов сообще- ний вы имеете дело (рис. 18-2).
Рис. 18-2. Сообщения хранятся в произвольном порядке,
каждое определяется идентификатором
Сообщения имеют переменный формат, определяемый заказчиком, и вы не мо- жете заставить его стабилизировать формат (рис. 18-3).
ГЛАВА 18 Табличные методы
411
Рис. 18-3. За исключением идентификатора каждое сообщение имеет свой формат
Логический подход
Используя логический подход, вы, вероятно, прочитали бы каждое сообщение,
проверили его идентификатор, а затем вызвали метод, разработанный для чтения,
преобразования и печати каждого сообщения. Имей вы 20 типов сообщений, вы создали бы 20 методов. Для поддержки пришлось бы написать неизвестное коли- чество методов более низкого уровня. Так, вы могли бы создать метод
PrintBuoy-
TemperatureMessage() для печати сообщения о температуре. Объектно-ориентиро- ванный подход не дал бы никаких преимуществ: скорее всего вы задействовали бы абстрактный объект, представляющий сообщение, и породили от него подклас- сы для каждого типа сообщения.
При каждом изменении формата какого-нибудь сообщения вам пришлось бы менять логику в соответствующем классе или методе. Если в приведенном выше содержимом сообщения поле со средней температурой поменяло бы тип с пла- вающей запятой на иной, вам пришлось бы изменить логику метода
PrintBuoyTempe-
ratureMessage(). (А если бы изменился тип самого бакена, вам бы пришлось раз- рабатывать класс для нового!)
В логическом подходе метод для чтения сообщений состоит из цикла, читающе- го каждое сообщение, декодирующего его идентификатор, а затем вызывающего на основе этого идентификатора один из 20 методов. Вот пример псевдокода логического подхода:
Пока есть сообщения для чтения
Прочитать заголовок сообщения
Декодировать идентификатор сообщения из заголовка
Если заголовок сообщения соответствует типу 1,
то напечатать сообщение 1-го типа.
Иначе, если заголовок сообщения соответствует типу 2,
то напечатать сообщение 2-го типа.
Перекрестная ссылка Этот псев- докод низкого уровня исполь- зуется в иных целях, нежели псевдокод, предназначенный для проектирования метода. О
разработке с помощью псевдо- кода см. главу 9.
412
ЧАСТЬ IV Операторы
Иначе, если заголовок сообщения соответствует типу 19,
то напечатать сообщение 19-го типа.
Иначе, если заголовок сообщения соответствует типу 20,
то напечатать сообщение 20-го типа.
Текст этого псевдокода приводится не полностью — понять его смысл можно и без просмотра всех 20 вариантов.
Объектно-ориентированный подход
При использовании механического объектно-ориентированного подхода логи- ка была бы скрыта в структуре унаследованных объектов, но основная структура была бы столь же сложной:
Пока есть сообщения для чтения,
прочитать заголовок сообщения.
Декодировать идентификатор сообщения из заголовка.
Если заголовок сообщения соответствует типу 1,
то создать объект сообщения 1-го типа.
Иначе, если заголовок сообщения соответствует типу 2,
то создать объект сообщения 2-го типа.
Иначе, если заголовок сообщения соответствует типу 19,
то создать объект сообщения 19-го типа.
Иначе, если заголовок сообщения соответствует типу 20,
то создать объект сообщения 20-го типа.
Конец Если
Конец цикла Пока
Независимо от того, будет ли логика написана непосредственно или реализова- на в специальных классах, каждое из 20 видов сообщений будет иметь собствен- ный метод печати. Каждый такой метод тоже можно изобразить с помощью псев- докода. Вот его пример для метода, считывающего и печатающего сообщение о температуре бакена:
Напечатать «Сообщение о температуре бакена».
Прочитать значение с плавающей запятой.
Напечатать «Средняя температура».
Напечатать значение с плавающей запятой.
Прочитать значение с плавающей запятой.
Напечатать «Диапазон температур».
Напечатать значение с плавающей запятой.
Прочитать целое значение.
Напечатать «Количество проб».
Напечатать целое значение.
Прочитать символьную строку.
Напечатать «Местонахождение».
Напечатать символьную строку.