Больно менять параметры функции; Вы должны изменить каждый второй вызов этой функции, чтобы избежать ошибок. Но вы можете обойти это, используя только один параметр: объект конфигурации.
На что это похоже
Вот глупый пример функции для создания робота:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
function generateRobot(arms:int, personality:String):Robot {
var robot:Robot = new Robot();
for (var i:int = 0; i < arms; i++) {
//create arm and add it to robot
}
if (personality == «evil») {
robot.commands = «Destroy mankind.»;
}
else {
robot.commands = «Bake cookies.»
}
return robot;
}
generateRobot(2, «evil»);
|
Теперь вот тот же пример с использованием объекта конфигурации:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
function generateRobot(conf:Object):Robot {
var robot:Robot = new Robot();
for (var i:int = 0; i < conf.arms; i++) {
//create arm and add it to robot
}
if (conf.personality == «evil») {
robot.commands = «Destroy mankind.»;
}
else {
robot.commands = «Bake cookies.»
}
return robot;
}
generateRobot({arms:2, personality:»evil»});
|
Я выделил строки, которые требуют изменения; Вы можете видеть, что нет большой разницы.
Зачем беспокоиться?
Так что, если нет никакой разницы, зачем нам делать это вторым способом? В конце концов, это на самом деле делает функцию немного сложнее в использовании; тогда как до того, как наша IDE сможет предоставить нам эту информацию о параметрах, ожидаемых функцией:
… теперь это может дать нам только это:
Предположим, вы хотите добавить еще пару параметров: один указывает материал для использования, а другой — цвет лазера. Это не так уж сложно, в любом случае:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function generateRobot(arms:int, personality:String, material:String, laserColor:String):Robot {
var robot:Robot = new Robot();
for (var i:int = 0; i < arms; i++) {
//create arm and add it to robot
}
if (personality == «evil») {
robot.commands = «Destroy mankind.»;
}
else {
robot.commands = «Bake cookies.»
}
switch (material) {
case «wood»:
//wooden robot
break;
case «steel»:
default:
//steel robot
break;
}
robot.laser = new Laser();
robot.laser.color = laserColor;
return robot;
}
generateRobot(2, «evil», «steel», «red»);
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function generateRobot(conf:Object):Robot {
var robot:Robot = new Robot();
for (var i:int = 0; i < conf.arms; i++) {
//create arm and add it to robot
}
if (conf.personality == «evil») {
robot.commands = «Destroy mankind.»;
}
else {
robot.commands = «Bake cookies.»
}
switch (conf.material) {
case «wood»:
//wooden robot
break;
case «steel»:
default:
//steel robot
break;
}
robot.laser = new Laser();
robot.laser.color = conf.laserColor;
return robot;
}
generateRobot({arms:2, personality:»evil», material:»steel», laserColor:»red»});
|
Пока что все еще не большая разница. Что если вы хотите, чтобы у ваших роботов по умолчанию были красные лазеры? Просто снова. Без объекта конфигурации вам просто нужно изменить сигнатуру метода (строку function
), а затем вы можете удалить последний аргумент из вызова функции:
1
2
3
4
5
|
function generateRobot(arms:int, personality:String, material:String, laserColor:String = «red»):Robot {
//this is all the same
}
generateRobot(2, true, «steel»);
|
С объектом конфигурации это немного сложнее, но не намного:
01
02
03
04
05
06
07
08
09
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
|
function generateRobot(conf:Object):Robot {
if (!conf.laserColor) {
conf.laserColor = «red»;
}
var robot:Robot = new Robot();
for (var i:int = 0; i < conf.arms; i++) {
//create arm and add it to robot
}
if (conf.personality == «evil») {
robot.commands = «Destroy mankind.»;
}
else {
robot.commands = «Bake cookies.»
}
switch (conf.material) {
case «wood»:
//wooden robot
break;
case «steel»:
default:
//steel robot
break;
}
robot.laser = new Laser();
robot.laser.color = conf.laserColor;
return robot;
}
generateRobot({arms:2, personality:»evil», material:»steel»});
|
Ладно. Теперь предположим, что вы обнаруживаете, что вы устанавливаете почти всех своих роботов как злых (я имею в виду, почему бы и нет?), Так что на самом деле довольно сложно писать «зло» в качестве параметра каждый раз. Естественно, вы хотите установить «зло» по умолчанию — но вы не хотите устанавливать материал по умолчанию.
Единственный способ сделать это с обычным набором параметров функции — это изменить порядок параметров personality
и material
:
1
|
function generateRobot(arms:int, material:String, personality:String = «evil», laserColor:String = «red»):Robot {
|
Ах, но теперь вам нужно переключать порядок аргументов при каждом вызове функции!
1
|
generateRobot(2, «evil», «steel»);
|
Объект конфигурации не дает вам этой проблемы. Проверьте это:
01
02
03
04
05
06
07
08
09
10
11
12
|
function generateRobot(conf:Object):Robot {
if (!conf.laserColor) {
conf.laserColor = «red»;
}
if (!conf.personality) {
conf.personality = «evil»
}
//this is all the same
}
generateRobot({arms:2, material:»steel»});
|
Ухоженная! Все ваши старые вызовы функции generateRobot()
будут продолжать работать, но вы можете создавать новые вызовы, которые не будут задавать personality
.
Вы даже можете решить полностью избавиться от параметра personality
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function generateRobot(conf:Object):Robot {
if (!conf.laserColor) {
conf.laserColor = «red»;
}
if (!conf.personality) {
conf.personality = «evil»
}
var robot:Robot = new Robot();
for (var i:int = 0; i < conf.arms; i++) {
//create arm and add it to robot
}
robot.commands = «Destroy mankind.»;
switch (conf.material) {
case «wood»:
//wooden robot
break;
case «steel»:
default:
//steel robot
break;
}
robot.laser = new Laser();
robot.laser.color = conf.laserColor;
return robot;
}
|
Приведенная выше версия функции вообще не относится к conf.personality
но вы не получите сообщение об ошибке, если у вас все еще есть такие вызовы:
1
|
generateRobot({arms:2, personality:»evil», material:»steel»});
|
Конечно, вы можете получить несколько запутанных пользователей, если у вас есть такие звонки:
1
|
generateRobot({arms:2, personality:»good», material:»steel»});
|
… так как все роботы теперь злые. Но по крайней мере код скомпилируется.
По той же причине вы можете изменить порядок аргументов без какого-либо значения и даже добавить новые параметры, которые еще ничего не делают:
1
|
generateRobot({material:»steel», laserColor:»green», arms:2, voice:»Mr. T»});
|
Упрощение установки значений по умолчанию
Код для установки значений по умолчанию до сих пор прост для понимания, но его будет очень раздражать, если нам понадобится много параметров:
1
2
3
4
5
6
|
if (!conf.laserColor) {
conf.laserColor = «red»;
}
if (!conf.personality) {
conf.personality = «evil»
}
|
Давайте напишем более общий код, чтобы справиться с ним:
01
02
03
04
05
06
07
08
09
10
|
var defaults:Object = {
laserColor:red,
personality: «evil»
}
for (var key:String in defaults){
if (!conf[key]) {
conf[key] = defaults[key];
}
}
|
Это for
цикла может быть немного запутанным, поэтому я разобью его. Во-первых, посмотрите на это:
1
2
3
|
for (var key:String in defaults){
trace(key);
}
|
Это цикл for...in
, который выводит имена ключей внутри объекта по default
:
1
2
|
laserColor
personality
|
Далее посмотрите на эту строку:
1
|
trace(defaults[«laserColor»]);
|
Это выведет red
— это то же самое, что написать trace(defaults.laserColor)
.
Исходя из этого, посмотрите на этот пример:
1
2
3
|
var example:Object = { demo: «test» };
trace(example[«demo»]);
trace(example[«foo»]);
|
Как вы думаете, это будет выход?
Ну, example["demo"]
такой же, как example.demo
, что равно "test"
. Но example.foo
не существует, поэтому example["foo"]
вернет null
. Это означает, что !example["foo"]
(обратите внимание на восклицательный знак) будет эквивалентно true
.
Соберите все это вместе, и вы сможете понять, почему этот код работает:
01
02
03
04
05
06
07
08
09
10
|
var defaults:Object = {
laserColor:red,
personality: «evil»
}
for (var key:String in defaults){
if (!conf[key]) {
conf[key] = defaults[key];
}
}
|
Дайте мне крик в комментариях, если вам нужна помощь!
Я хочу больше!
Для еще более быстрой версии попробуйте это:
01
02
03
04
05
06
07
08
09
10
|
function generateRobot(conf:Object = null):Robot {
var conf:Object = conf ||
var defaults:Object = {
laserColor:red,
personality: «evil»
}
for (var key:String in defaults){
conf[key] = conf[key] ||
}
|
Изменение в строке 1 (и новой строке 2) означает, что даже сам объект conf
является необязательным, поэтому вы можете просто вызвать generateRobot()
. (Конечно, вам нужно изменить код, чтобы он работал со значениями, которые в настоящее время не имеют значений по умолчанию.)
Помощь IDE поможет вам
Как я упоминал выше, IDE не может дать вам никаких подсказок о том, какие параметры ожидает функция, если эта функция использует объект конфигурации. Это серьезный недостаток, поскольку он может сделать ваш код действительно сложным в использовании; Вы должны запомнить, какие параметры conf
объект conf
, а также все их имена и типы.
Но мы все равно можем отображать эту информацию для кодера, когда она необходима; мы просто должны сделать это вручную, вот так:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
/**
* Generate a robot, based on the parameters given.
* @param conf Configuration object.
* arms (int) Number of arms robot should have.
* personality (String) Personality of robot.
* material (String) What the robot should be made out of.
* laserColor (String) Color of the robot’s laser.
* voice (String) Vocal stylings of robot.
* @return The finished robot.
*/
function generateRobot(conf:Object):Robot {
//
}
|
Теперь, если я начну писать вызов этой функции во FlashDevelop (моя IDE по выбору), я вижу это:
Конечно, немного неприятно держать это вручную обновленным, но во многих случаях оно того стоит.
Вывод
Я не утверждаю, что вы должны использовать объект конфигурации для каждой функции, которую вы создаете отныне; просто подумайте об этом как о другом полезном инструменте в вашем арсенале.
Лично я нахожу это особенно полезным паттерном всякий раз, когда я создаю первый набросок некоторого набора классов, которые все должны работать вместе. Добавленная гибкость conf
дает мне гораздо большую гибкость, позволяя мне разбираться во всех различных функциях и менять способ их вызова, не беспокоясь о нарушении кода путем вставки или удаления параметра.
Помните о преимуществах:
- Это легко добавлять и удалять параметры (с любого конца).
- Это легко установить значения по умолчанию.
- Вам не нужно беспокоиться о порядке параметров.
Однако существуют недостатки в использовании таких простых объектов, как у меня, особенно если вы делаете это в проекте, который прошел стадию прототипирования. Проверьте большие комментарии ниже для более подробной информации!