BadDesign: именуем параметры в Java

в 10:36, , рубрики: clean code, java, java named parameters, refactoring, Проектирование и рефакторинг

В некоторых языках программирования (например, в Scala) есть именованные параметры. Именованные параметры позволяют контролировать присвоение значений, дополнительным бонусом выступает возможность использовать свой порядок указания параметров, нежели описано в сигнатуре функции. В рамках цикла статей BadDesign, предложу урезанный механизм именованных параметров для Java.

Следующий пример демонстрирует описанный механизм именованных параметров.

Пусть есть функция, которая печатает имя и фамилию.

def printName(first:String, last:String) = {
  println(first + " " + last)
}

Вызов функции без использования именованных параметров:

printName("John","Smith")
// Prints "John Smith"

Вызов функции с именованными параметрами:

printName(first = "John",last = "Smith")
// Prints "John Smith"

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

printName(last = "Smith",first = "John")
// Prints "John Smith"

Рассмотрим класс прямоугольник с двумя полями: ширина и высота. Дополнительно предположим, что прямоугольники используются где-то в приложении и имеют дефолтные значения для ширины и высоты.

Простой пример реализации мог бы выглядеть так:

public class Rectangle {

    public static final int DEFAULT_HEIGHT = 50;

    public static final int DEFAULT_WIDTH = 200;

    private int height;

    private int width;

    public Rectangle() {
        this(DEFAULT_HEIGHT, DEFAULT_WIDTH);
    }

    public Rectangle(int height, int width) {
        this.height = height;

        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }
}

Далее рассмотрим несколько примеров.

// Создание прямоугольника с дефолтными значениями для высоты и ширины.
Rectangle rectangle = new Rectangle();

// Создание прямоугольника с указанными значениями для высоты и ширины.
Rectangle rectangle = new Rectangle(27, 109);

// Создание прямоугольника с дефолтным значением для ширины и указанным значением для высоты.
Rectangle rectangle = new Rectangle();
rectangle.setHeight(27);

// Создание прямоугольника с дефолтным значением для высоты и указанным значением для ширины.
Rectangle rectangle = new Rectangle();
rectangle.setWidth(109);

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

public class Rectangle {
    ...

    public Rectangle(int height) {
        this(height, DEFAULT_WIDTH);
    }

    public Rectangle(int width) {
        this(DEFAULT_HEIGHT, width);
    }

    ...
}

Однако, такой код не компилируется в Java (что, в общем-то, логично).

Обойти эту проблему можно создав маленькие обертки над высотой и шириной.

Обертка над высотой:

public class RectangleHeight {

    private int height;

    private RectangleHeight(int height) {
        this.height = height;
    }

    public static RectangleHeight rectangleHeight(int height) {
        return new RectangleHeight(height);
    }

    public int getValue() {
        return height;
    }
}

Обертка над шириной:

class RectangleWidth {

    private int width;

    private RectangleWidth(int width) {
        this.width = width;
    }

    public static RectangleWidth rectangleWidth(int width) {
        return new RectangleWidth(width);
    }

    public int getValue() {
        return width;
    }
}

Теперь добавим в класс Rectangle два конструктора:

public class Rectangle {
    ...

    public Rectangle(RectangleHeight rectangleHeight) {
        this(rectangleHeight.getValue(), DEFAULT_WIDTH);
    }

    public Rectangle(RectangleWidth rectangleWidth) {
        this(DEFAULT_HEIGHT, rectangleWidth.getValue());
    }


    ...
}

Теперь есть некая имитация именованных параметров.

Вместо

// Создание прямоугольника с дефолтным значением для ширины и указанным значением для высоты.
Rectangle rectangle = new Rectangle();
rectangle.setHeight(27);

теперь можно сделать так:

// Создание прямоугольника с дефолтным значением для ширины и указанным значением для высоты.
Rectangle rectangle = new Rectangle(rectangleHeight(27));

Вместо:

// Создание прямоугольника с дефолтным значением для высоты и указанным значением для ширины:
Rectangle rectangle = new Rectangle();
rectangle.setWidth(109);

теперь можно сделать так:

// Создание прямоугольника с дефолтным значением для высоты и указанным значением для ширины:
Rectangle rectangle = new Rectangle(rectangleWidth(100));

Вместо:

// Создание прямоугольника с указанными значениями для высоты и ширины.
Rectangle rectangle = new Rectangle(27, 109);

теперь можно сделать так:

// Создание прямоугольника с указанными значениями для высоты и ширины.
Rectangle rectangle = new Rectangle(rectangleHeight(27), rectangleWidth(109));
Плюсы

  • Нельзя ошибиться с указанием высоты и ширины, если использовать конструктор с обертками.
Минусы

  • Слишком много классов.
  • Многословно.
  • Всё еще нельзя менять порядок указания значений параметров.

Автор: jxcoder

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js