Защищенность переменных в Kotlin на примере Java

в 12:19, , рубрики: java, kotlin, Программирование, проектирование, Проектирование и рефакторинг

Совсем недавно у меня состоялся разговор с коллегой по поводу новых языков программирования. После того, как разговор зашел о Kotlin, мой друг обронил фразу «Там нулл нельзя передавать, если не указал, что переменная может быть nullable». Эта фраза сильно озадачила меня — а действительно ли, так важны киллер фичи Котлина в сравнении с уже известными языковыми возможностями Явы? Размышления на этот вопрос вылились в целый комплекс примеров, в рамках которых я хочу показать (в первую очередь самому себе, наверно), зачем же нужны все эти новые языковые возможности.

Самая скучная часть разработки на уже обкатанном языке — рутинные задачи. Например, показ картинок. Абсолютно рядовая задача, которая в любом проекте обрастает рядом условностей. Приведу в пример несколько таких:

  • загруженную картинку нужно отобразить «в кружочке» (кружочки это очень стильно)
  • после загрузки сжать изображение до размеров отображения (нечего тратить память)
  • изображение нужно сжать до нужного размера и/или пропорции
  • в случае неудачной загрузки показать какой-нибудь ресурс по умолчанию

Абсолютно бытовые условности, которые поддерживаются многими готовыми библиотеками (например, для андроида это Picasso, Glide, etc.).

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

В самом базовом виде набор таких данных будет преставлять из себя примерно следующий класс:

public class ImageSets {
    Boolean inCircle = false;
    Boolean needFit = false;
    Size size = null;
    int defaultDrawableResource = -1;
}

Если оставить его так, то работа с ним будет выглядеть примерно следующим образом:

Пример

void useCase() {
     BaseJavaClass some = new BaseJavaClass();
     some.inCircle = true;
     some.needFit = true;
     some.defaultDrawableResource = 0;
     some.size = new Size(128, 128);

     System.out.println(some.inCircle);
     System.out.println(some.needFit);
     System.out.println(some.size);
}

static ImageSets pattern(){
     BaseJavaClass some = new BaseJavaClass();
     some.inCircle = true;
     some.needFit = true;
     some.defaultDrawableResource = 0;
     some.size = new Size(128, 128);
     return some;
}

У подобного подхода есть несколько минусов, вот пара тех, которые вызывают у меня всегда большее раздражение:

  • Постоянное упоминание «some.» при вызове. В случае с множественным вызовом (а здесь он как раз актуален), код сильно раздувается.
  • Для использования шаблонов нужно каждый раз создавать новый экземпляр шаблона — иначе кто-то может залезть внутрь него и что-то внутри поправить — веселье обеспечено.

С первым недугом принято бороться с помощью сеттеров (можно конечно объявить конструктор со всем набором переменных, но если в Java их больше трех, плюс некоторые из них могут быть необязательными, мы точно получим где-то в программе вызовы, похожие на Some(0,0, null, null, null, -1, 5)):

Много кода

    public BaseJavaClass setInCircle(Boolean inCircle) {
        this.inCircle = inCircle;
        return this;
    }

    public BaseJavaClass setNeedFit(Boolean needFit) {
        this.needFit = needFit;
        return this;
    }

    public BaseJavaClass setSize(Size size) {
        this.size = size;
        return this;
    }
    
    public BaseJavaClass setDefaultDrawableResource(int defaultDrawableResource) {
        this.defaultDrawableResource = defaultDrawableResource;
        return this;
    }

    void useCase(){
      BaseJavaClass some = new BaseJavaClass()
                .setInCircle(true)
                .setNeedFit(true)
                .setDefaultDrawableResource(0)
                .setSize(new Size(128, 128));
    }

    static BaseJavaClass pattern1() {
        return new BaseJavaClass()
                .setInCircle(true)
                .setNeedFit(true)
                .setSize(new Size(128, 128));
    }

Что же, допустим. Но что делать со второй проблемой? Обычно для ее решения используют вспомогательный _Bulder класс, вызов которого будет содержать сеттеры, а вызов основного класса — только геттеры:

Очень много кода

public class ImageSets {
    protected Boolean inCircle = false;
    protected Boolean needFit = false;
    protected Size size = null;
    protected int defaultDrawableResource = -1;

    public Boolean getInCircle() {
        return inCircle;
    }

    public Boolean getNeedFit() {
        return needFit;
    }

    public Size getSize() {
        return size;
    }

    public int getDefaultDrawableResource() {
        return defaultDrawableResource;
    }

    public static final ImageSets pattern = new ImageSetsBuilder()
            .setInCircle(true)
            .setNeedFit(true)
            .setDefaultDrawableResource(0)
            .setSize( new Size(128, 128));
}

public class ImageSetsBuilder extends ImageSets {
    public ImageSetsBuilder setInCircle(Boolean inCircle) {
        this.inCircle = inCircle;
        return this;
    }

    public ImageSetsBuilder setNeedFit(Boolean needFit) {
        this.needFit = needFit;
        return this;
    }

    public ImageSetsBuilder setSize(Size size) {
        this.size = size;
        return this;
    }

    public ImageSetsBuilder setDefaultDrawableResource(int defaultDrawableResource) {
        this.defaultDrawableResource = defaultDrawableResource;
        return this;
    }
}

Существует много других способов, я привел наиболее распространенные способы решения.
Что же стало с нашими данными? Как так вышло, что класс, содержащий в себе всего 4 переменных, разросся до подобных размеров? Что произойдет при добавлении переменных?
Этот же класс, но на языке Kotlin будет выглядеть следующим образом:

class ImageSets(
        val inCircle: Boolean = false,
        val needFit: Boolean = false,
        val size: Pair<Int, Int>? = null,
        val defaultDrawableResource: Int = -1)

Все! Можем использовать:

val pattern = ImageSets(
                inCircle = true,
                needFit = true,
                defaultDrawableResource = 0,
                size = 128 to 128)

        fun useCase(){
        	val some = ImageSets(
                    inCircle = true,
                    needFit = true,
                    defaultDrawableResource = 0)
                print(some.needFit)
        }

Написанный на Kotlin пример дает нам следующие преимущества:

  • Все свойства внутри класса (обозначены с помощью val ) изменяются только в конструкторе
  • Сам шаблон тоже является свойством и не может переобозначаться
  • Объявление экземпляров класса не содержит большого количества скобок и выглядит опрятнее
  • Конструктор, который содержит в себе и свойства класса, можно вызывать с использованием конструкции arg = value, что в свою очередь позволяет нам оперировать конструкторами с любым количеством принимаемых переменных.
  • Свойства не обязательно должны быть — свойство size в последнем useCase просо проигнорировано, его не нужно заменять на null, и конструктор от этого не становится непонятным набором 0, null, -1
  • В дальнейшем обращение к значениям свойств происходит так же, как и к обычным переменным.

  • Один класс против двух, один конструктор, прозрачная работа с аргументами по умолчанию, отсутствие геттеров и сеттеров, отсутствие некрасивых возвратов return this

Автор: Денис Александров

Источник

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


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