Главная - Литература

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [47] 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

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

Предпочитайте полиморфизм, а не крупномасштабную проверку типов

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

Пример кода, который следовало бы заменить вызовом полиморфного метода (С++)

switch ( shape.type ) { case Shape Circle:

shape.DrawCircle();

break; case Shape Square:

shape.DrawSquare();

break;

Здесь методы shape.DrawCircleQ и shape.DrawSquareQ следует заменить на единственный метод shape.DrawQ, поддерживающий рисование и окружностей, и прямоугольников.

С другой стороны, иногда блоки case служат для разделения по-настоящему разных видов объектов или форм поведения. Так, следующий фрагмент вполне уместен в объектно-ориентированной программе:

Пример кода, который, пожалуй, не следует заменять вызовом полиморфного метода (С++)

switch ( ui.Commancl() ) { case Connancl OpenFile:

OpenFile();

break; case Commancl Print:

PrintO;

break; case Conmancl Save;

SaveO;

break; case Comnfiancl Exit:

ShutDownO;

break;

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



ции это неуместно: имя метода DoCommandQ было бы настолько туманным, что почти утратило бы всякий смысл, тогда как блоки case довольно информативны.

Делайте все данные закрытыми, а не защищенными Как говорит Джошуа Блох, «наследование нарушает инкапсуляцию» (Bloch, 2001). Выполняя наследование от класса, вы получаете привилегированный доступ к его защищенным методам и данным. Если производному классу на самом деле нужен доступ к атрибутам базового класса, включите в базовый класс защищенные методы доступа.

Множественное наследование

Наследование - мощный и... довольно опасный инструмент.

С множественным наследование

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

единичном нашдоши».

Если наследование - цепная пила, то множественное на-

следование - это старинная цепная пила с барахлящим Meyers)

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

Некоторые эксперты рекомендуют широкое применение множественного наследования (Meyer, 1997), но по опыту могу сказать, что оно полезно главным образом только при создании «миксинов» - простых классов, позволяющих добавить ряд свойств в другой класс. Миксины называются так потому, что они позволяют «подмешать (mix in)» свойства в производные классы. Миксинами могут быть классы вроде Displayable, Persistent, Serializable или Sortable. Миксины почти всегда являются абстрактными и не поддерживают создания экземпляров независимо от других объектов.

Миксины требуют множественного наследования, но пока все миксины по-настоящему независимы друг от друга, вы можете не бояться классической проблемы, связанной с ромбовидной схемой наследования. Кроме того, «объединяя» атрибуты, они делают проект системы понятнее. Программисту легче разобраться с объектом, использующим миксины Displayable и Persistent, а не 11 более конкретных методов, которые понадобились бы для реализации этих двух свойств в противном случае.

Похоже, разработчики Java и Visual Basic понимали ценность миксинов, разрешив множественное наследование интерфейсов, но только единичное наследование классов. С++ поддерживает множественное наследование и интерфейсов, и реализации. Используйте множественное наследование, только тщательно рассмотрев все альтернативные варианты и проанализировав влияние выбранного подхода на сложность и понятность системы.

Почему правил наследования так много?

в этом разделе были описаны многие правила избавления от проблем, связанных с наследованием. Все эти правила подразумевают, что наследование часто противоречит главному техническому императиву програм-



жирования - управлению сложностью. Ради управления сложностью относитесь к наследованию с подозрением. Вот как использовать наследование и включение:

1 если несколько классов имеют общие данные, но не фор-

нош ем шдраадбй «Гшемый поведения, создайте общий объект, который можно было

Технический «мператйв Разра- ь! включить во все эти классы;

боткй ПО: управление сложно- если несколько классов имеют общие формы поведения, стыо» раздела $.2. данные, сделайте эти классы производными от общего

базового класса, определяющего общие методы;

если несколько классов имеют общие данные и формы поведения, сделайте эти классы производными от общего базового класса, определяющего общие данные и методы;

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

Методы-члены и данные-члены

Ниже я даю несколько советов по эффективной реализации Першиаяшша О методах

ш общем см. ттщ 7- методов-членов и данных-членов.

Включайте в класс как можно меньше методов

В одном исследовании программ на С++ было обнаружено, что большему числу методов в расчете на один класс соответствует большее число изъянов (Basili, Briand, and Melo, 1996). Однако важнее оказались другие конкурирующие факторы, в том числе многоуровневые иерархии наследования, большое число методов, вызываемых из класса, и сильное сопряжение между классами. Разрабатывая класс, стремитесь к оптимальному соответствию между этими факторами и минимальным числом методов.

Блокируйте неявно сгенерированные методы и операторы, которые вам не нужны Иногда некоторые возможности, такие как создание объекта или присваивание, целесообразно блокировать. Вам может показаться, что сделать это невозможно, потому что компилятор генерирует эти операции автоматически. Однако вы можете запретить их использование в клиентском коде, объявив конструктор, оператор присваивания или другой метод или оператор как private. (Создание закрытого конструктора - стандартный способ определения класса-одиночки, о чем см. ниже.)

Минимизируйте число разных методов, вызываемых классом Одно исследование показало, что число дефектов в коде класса статистически коррелирует с общим числом методов, вызываемых классом (Basili, Briand, and Melo, 1996). To же исследование показало, что число дефектов в коде класса повышается и при увеличении числа используемых в нем классов. Эти концепции иногда называют «коэффициентом разветвления по выходу (fan out)».

Избегайте опосредованных вызовов методов других классов Непосредственные связи довольно опасны. Опосредованные связи, такие как account.Con-tactPersonO.DaytimeContactlnpOPboneNumberO, опасны еще больше. В связи с этим ученые сформулировали «Правило Деметры (Law of Demeter)» (Lieberherr and Holland, 1989), которое гласит, что Объект А может вызывать любые из собственных методов. Если он создает Объект В, он может вызывать любые методы Объекта



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [47] 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294



0.0057