Главная страница
Навигация по странице:

  • Пример элегантного способа определения количества дней в месяце (Visual Basic)

  • Пример элегантного способа определения количества дней в месяце на Visual Basic (продолжение)

  • Пример со ставками страхования

  • Пример неуклюжего способа расчета ставки страхования (Java)

  • Пример объявления данных для заполнения таблицы ставок страхования (Visual Basic)

  • Пример элегантного способа определения ставки страхования (Visual Basic)

  • ЧАСТЬ IV

  • ГЛАВА 18

  • Объектно-ориентированный подход

  • Пример определения типов данных сообщения (C++)

  • Пример определения элемента таблицы, описывающего сообщение

  • Пример создания объектных типов (C++)

  • Пример создания таблицы для хранения объектов каждого типа (C++)

  • Пример выбора объектов и их методов из таблицы (C++)

  • Руководство по стилю программирования и конструированию по


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница51 из 104
    1   ...   47   48   49   50   51   52   53   54   ...   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 видов сообщений будет иметь собствен- ный метод печати. Каждый такой метод тоже можно изобразить с помощью псев- докода. Вот его пример для метода, считывающего и печатающего сообщение о температуре бакена:
    Напечатать «Сообщение о температуре бакена».
    Прочитать значение с плавающей запятой.
    Напечатать «Средняя температура».
    Напечатать значение с плавающей запятой.
    Прочитать значение с плавающей запятой.
    Напечатать «Диапазон температур».
    Напечатать значение с плавающей запятой.
    Прочитать целое значение.
    Напечатать «Количество проб».
    Напечатать целое значение.
    Прочитать символьную строку.
    Напечатать «Местонахождение».
    Напечатать символьную строку.

    ГЛАВА 18 Табличные методы
    413
    Прочитать время суток.
    Напечатать «Время измерения».
    Напечатать время суток.
    Это код только для одного типа сообщений. Для каждого из оставшихся 19 типов нужно реализовать похожий код. И если будет добавлен 21-й тип сообщения,
    потребуется добавить 21-й метод или подкласс — в любом случае новый тип со- общения потребует изменения существующего кода.
    Табличный подход
    Табличный подход экономичнее предыдущего. Метод чтения сообщений состо- ит из цикла, который считывает заголовок каждого сообщения, декодирует его идентификатор, находит описание сообщения в массиве
    Message, а затем всегда вызывает один и тот же метод для декодирования сообщения. Этот подход позво- ляет описать формат каждого сообщения в форме таблицы, а не задавать его же- стко в логике программы. Это упрощает первоначальное программирование, со- здает меньше кода и облегчает сопровождение программы без изменения кода.
    Применение этого подхода начинается с перечисления типов сообщений и типов полей. В C++ вы можете определить типы всех возможных полей таким образом:
    Пример определения типов данных сообщения (C++)
    enum FieldType {
    FieldType_FloatingPoint,
    FieldType_Integer,
    FieldType_String,
    FieldType_TimeOfDay,
    FieldType_Boolean,
    FieldType_BitField,
    FieldType_Last = FieldType_BitField
    };
    Вместо жестко закодированных методов печати каждого из 20 видов сообщений можно создать горстку функций для печати основных типов данных: чисел с пла- вающей точкой, целых чисел, символьных строк и т. д. Вы можете описать содер- жимое каждого типа сообщения в таблице (с указанием имени каждого поля), а затем декодировать все сообщения на основе этого описания. Элемент таблицы,
    содержащий сведения об одном типе сообщений, может выглядеть так:
    Пример определения элемента таблицы, описывающего сообщение
    Message Begin
    NumFields 5
    MessageName “Buoy Temperature Message”
    Field 1, FloatingPoint, “Average Temperature”
    Field 2, FloatingPoint, “Temperature Range”
    Field 3, Integer, “Number of Samples”
    Field 4, String, “Location”
    Field 5, TimeOfDay, “Time of Measurement”
    Message End

    414
    ЧАСТЬ IV Операторы
    Эта таблица может быть жестко закодирована в программе (в этом случае значе- ния всех элементов будут присвоены переменным) или читаться из файла при запуске программы или позже.
    Поскольку определения сообщений поступают в программу извне, то вместо вне- дрения информации в логику программы мы внедрили ее в данные. Данные обычно гибче программной логики: их легко изменять, если меняется формат сообщения.
    Если нужно добавить новый вид сообщений, вы можете просто добавить еще один элемент в таблицу данных.
    Вот псевдокод цикла верхнего уровня для табличного подхода:
    Первые три строки такие же, как и при логическом подходе.
    Пока есть сообщения для чтения,
    прочитать заголовок сообщения,
    декодировать идентификатор сообщения из заголовка,
    найти описание сообщения в таблице описаний сообщений,
    прочитать поля сообщения и напечатать их, основываясь на описании сообщения.
    Конец цикла Пока
    В отличие от псевдокода при логическом подходе в этом случае псевдокод не сокращен, так как логика гораздо проще. Логика более низкого уровня содержит метод, который интерпретирует сообщение на основе таблицы описаний сооб- щений, считывает данные сообщения и печатает его. Этот метод более общего вида,
    чем методы печати сообщений при логическом подходе, но он не намного слож- нее и заменяет собой все 20 методов:
    Пока не все поля напечатаны,
    получить тип поля из описания сообщения.
    Выбор ( типа поля )
    вариант: ( число с плавающей запятой )
    прочитать значение с плавающей запятой,
    напечатать метку поля,
    напечатать значение с плавающей запятой.
    вариант: ( целое число )
    прочитать целое значение,
    напечатать метку поля,
    напечатать целое значение.
    вариант: ( символьная строка )
    прочитать символьную строку,
    напечатать метку поля,
    напечатать символьную строку.
    вариант: ( время суток )
    прочитать время суток,
    напечатать метку поля,
    напечатать время суток.
    вариант: ( логическое значение )
    прочитать значение флажка,

    ГЛАВА 18 Табличные методы
    415
    напечатать метку поля,
    напечатать значение флажка.
    вариант: ( битовое поле )
    прочитать битовое поле,
    напечатать метку поля,
    напечатать битовое поле.
    Конец Выбора
    Конец цикла Пока
    Нужно признать, что этот метод с шестью вариантами выбора длиннее, чем от- дельный метод для печати температуры. Но это единственный метод, который вам необходим. Вам не нужны остальные 19 функций для остальных 19 типов сооб- щений. Данный метод обрабатывает шесть типов полей, и обслуживает все виды сообщений.
    Этот метод также иллюстрирует наиболее сложный способ реализации таблич- ного поиска, так как использует оператор
    case. Другой подход — создание абст- рактного класса
    AbstractField и последующее наследование от него подклассов для каждого типа поля. Тогда вам не понадобится оператор
    case, вы сможете вызы- вать метод-член соответствующего объектного типа.
    Вот как можно создать такие объекты на C++:
    Пример создания объектных типов (C++)
    class AbstractField {
    public:
    virtual void ReadAndPrint( string, FileStatus & ) = 0;
    }
    class FloatingPointField : public AbstractField {
    public:
    virtual void ReadAndPrint( string, FileStatus & ) {
    }
    }
    class IntegerField ...
    class StringField ...
    Этот фрагмент объявляет во всех классах метод, принимающий строковый пара- метр и параметр типа
    FileStatus.
    Следующий шаг — объявление массива для хранения набора объектов. Этот мас- сив и есть таблица для поиска. Вот как она выглядит:
    Пример создания таблицы для хранения объектов каждого типа (C++)
    AbstractField* field[ Field_Last ];
    Последний шаг в настройке таблицы объектов — заполнение массива
    Field кон- кретными объектами:

    416
    ЧАСТЬ IV Операторы
    Пример заполнения списка объектов (C++)
    field[ Field_FloatingPoint ] = new FloatingPointField();
    field[ Field_Integer ] = new IntegerField();
    field[ Field_String ] = new StringField();
    field[ Field_TimeOfDay ] = new TimeOfDayField();
    field[ Field_Boolean ] = new BooleanField();
    field[ Field_BitField ] = new BitFieldField();
    В этом коде предполагается, что
    FloatingPointField и другие идентификаторы с правой стороны выражений присваивания — это имена объектов, унаследован- ных от
    AbstractField. Присваивание объектов элементам массива означает, что вы сможете вызвать правильную версию метода
    ReadAndPrint(), обращаясь к элементу массива, а не используя конкретный тип объекта напрямую.
    Подготовив таблицу методов, можно обрабатывать поле сообщения с помощью простого обращения к таблице объектов и вызова одного из методов-членов этих объектов. Код может выглядеть так:
    Пример выбора объектов и их методов из таблицы (C++)
    Это строки — служебный код, необходимый для обработки каждого поля в сообщении.
    fieldIdx = 1;
    while ( ( fieldIdx <= numFieldsInMessage ) and ( fileStatus == OK ) ) {
    fieldType = fieldDescription[ fieldIdx ].FieldType;
    fieldName = fieldDescription[ fieldIdx ].FieldName;
    Это — обращение к таблице, в результате которого будет вызван метод, зависящий от типа поля:
    он просто выбирается в таблице объектов.
    field[ fieldType ].ReadAndPrint( fieldName, fileStatus );
    }
    Помните первоначальные 34 строки псевдокода табличного поиска, содержаще- го оператор
    case? Если вы замените оператор case таблицей объектов, то это весь код, который вам нужен для обеспечения той же функциональности. Невероят- но, но это также весь код, необходимый для замены всех 20 отдельных методов,
    применяемых при логическом подходе. Более того, если описания сообщений читаются из файла, то новые типы сообщений не потребуют изменений кода, если только не будут содержать новых типов полей.
    Вы можете использовать такой подход в любом объектно-ориентированном язы- ке. Он менее подвержен ошибкам, легче в сопровождении и эффективнее длин- ных выражений
    if, операторов case или огромного количества подклассов.
    Сам факт, что проект использует наследование и полиморфизм, не делает его хорошим проектом. Механический объектно-ориентированный дизайн, описан- ный в разделе «Объектно-ориентированный подход», потребовал бы такого же большого объема кода, как и механический функциональный дизайн, а может, и больше. Такой подход скорее усложнил бы решение, чем упростил. В данном слу- чае основная суть проектного решения не в объектной и не в функциональной ориентации, а в использовании хорошо продуманной таблицы поиска.
    >
    >

    1   ...   47   48   49   50   51   52   53   54   ...   104


    написать администратору сайта