Статьи

Что такое объект конфигурации и зачем его использовать?

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


Вот глупый пример функции для создания робота:

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 не может дать вам никаких подсказок о том, какие параметры ожидает функция, если эта функция использует объект конфигурации. Это серьезный недостаток, поскольку он может сделать ваш код действительно сложным в использовании; Вы должны запомнить, какие параметры 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 дает мне гораздо большую гибкость, позволяя мне разбираться во всех различных функциях и менять способ их вызова, не беспокоясь о нарушении кода путем вставки или удаления параметра.

Помните о преимуществах:

  • Это легко добавлять и удалять параметры (с любого конца).
  • Это легко установить значения по умолчанию.
  • Вам не нужно беспокоиться о порядке параметров.

Однако существуют недостатки в использовании таких простых объектов, как у меня, особенно если вы делаете это в проекте, который прошел стадию прототипирования. Проверьте большие комментарии ниже для более подробной информации!