Это хороший способ создания объекта в классе с Kotlin? - PullRequest
2 голосов
/ 28 октября 2019

Я всегда нахожу код как код А в проекте CameraX. Он создает экземпляры, используя объект внутри companion object внутри класса.

Если я напишу тот же код, я буду использовать код B.

Это хороший способ создания экземпляра объекта вкласс с котлиным? Является ли код А лучше, чем код Б?

Кстати, я не думаю, что «Каждый класс фрагментов должен иметь пустой конструктор». Не могли бы вы рассмотреть нормальный класс вместо класса фрагментов?

Код A

class UIFragmentPhoto internal constructor() : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val args = arguments ?: return
        val resource = args.getString(FILE_NAME_KEY)?.let { File(it) } ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }

    companion object {
        private const val FILE_NAME_KEY = "file_name"

        fun create(image: File) = UIFragmentPhoto().apply {
            arguments = Bundle().apply {
                putString(FILE_NAME_KEY, image.absolutePath)
            }
        }
    }
}

Вызов A

override fun getItem(position: Int): Fragment = UIFragmentPhoto.create(mediaList[position])

Код B (измененный)

class UIFragmentPhoto internal constructor() : Fragment() {
    val FILE_NAME_KEY = "file_name"

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val args = arguments ?: return
        val resource = args.getString(FILE_NAME_KEY)?.let { File(it) } ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }


    constructor(image: File):this(){
        arguments = Bundle().apply {
            putString(FILE_NAME_KEY, image.absolutePath)
        }
    }

}

Вызов B (измененный)

override fun getItem(position: Int): Fragment = UIFragmentPhoto(mediaList[position])

Ответы [ 5 ]

4 голосов
/ 01 ноября 2019

В случае Fragment s:

В соответствии с исходным кодом класса Fragment на android.googlesource.com , где определен конструктор по умолчанию, мы видим, что:

Каждый фрагмент должен иметь пустой конструктор, чтобы его можно было создать при восстановлении состояния его активности. Настоятельно рекомендуется, чтобы у подклассов не было других конструкторов с параметрами, поскольку эти конструкторы не будут вызываться при повторном создании фрагмента; вместо этого вызывающая сторона может предоставлять аргументы с помощью setArguments и позже извлекается фрагментом с помощью getArguments .

Приложения, как правило, не должны реализовывать конструктор. Вместо этого предпочитайте onAttach (Context) . Это первое место, где код приложения может выполняться там, где фрагмент готов к использованию - точка, где фрагмент фактически связан с его контекстом. Некоторые приложения могут также захотеть реализовать onInflate для извлечения атрибутов из ресурса макета, хотя обратите внимание, что это происходит, когда фрагмент прикреплен.

Ссылаясь на вышеуказанную причину, добавление конструкторов не по умолчанию в Fragment означает ЗАПРЕЩЕНО !!!

После этого с использованием setArguments и getArgumentsМетоды - это альтернативный способ избежать добавления дополнительных конструкторов. Код B , написанный под вопросом, использует эти два подхода одновременно. Вы должны использовать один из них , то есть , как Код A . (Потому что, когда вы передаете параметр в конструктор, вы можете получить к нему доступ в классе. Поэтому шаблон [set/get]Arguments не нужен)

Однако, если мы хотим переписать код B без использования аргументов ( Отказ от ответственности: я подчеркиваю, что этот подход неверен ), мы можем сделать это следующим образом:

class UIFragmentPhoto internal constructor(private val image: File?) : Fragment() {

    constructor() : this(null)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val resource = image ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }

}

В общем:

Мы все знаем, что объект создается путем вызова метода конструктора класса в Java / Kotlin. Например:

val obj = MyClass()

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

В случае изменения объектов в соответствии с характером программы, мы должны получить помощь шаблонов проектирования. для обеспечения более общего и гибкого подхода (, например: Фабричный метод, Абстрактная фабрика, и т. д. Шаблоны).

Вывод:

  1. Когда вы имеете дело с созданием объекта из Fragment классов, Код A стиль должен быть использован. (По причине, описанной в android.googlesource.com )

  2. Когда вы имеете дело с созданием объекта из non- Fragment классы, стиль Code B лучше использовать. (Из-за отказа от дополнительного вызова функции, который не имеет никакого преимущества)

0 голосов
/ 05 ноября 2019

Фрагменты должны иметь открытый конструктор без аргументов, а должен не иметь других конструкторов, кроме этого;вместо этого любые аргументы, которые нужны фрагменту, должны быть помещены в его arguments Bundle. Это связано с тем, что когда Android требуется динамически создавать фрагменты, он вызывает это, вызывая пустой конструктор.

Таким образом, даже если вы хотите использовать код B (и это обычно не рекомендуется), ваш конструктор по умолчаниюдолжно быть общедоступным, и в этом случае ваш вторичный конструктор просто выполняет ту же роль, что и метод create в коде A.

0 голосов
/ 28 октября 2019

Хороший способ создать экземпляр объекта в классе с Kotlin?

Не совсем. Кроме того, это не имеет ничего общего с Kotlin, это не зависит от языка pattern .

Если вы инициализируете фрагмент по-своему (B), то у вас есть три проблемы:

1) Было бы легко забыть вызвать "create" в вашем экземпляре:

override fun getItem(position: Int): Fragment = UIFragmentPhoto() // Oops - bug

2) Если вы сделаете create методом экземпляра, вы можете вызывать его повторносуществующий фрагмент:

fun someFunction() {
    UIFragmentPhoto fragment = getExistingFragment()
    fragment.create() // Oops - just overwrote the fragment state
}

3) Наличие метода для «создания» уже созданного экземпляра просто сбивает с толку и является нестандартным способом инициализации фрагмента. Смысл этого статического / сопутствующего способа состоит в том, что у вас есть функция , чья работа заключается в создании и инициализации объекта.

Фабричный метод также дает вам гибкость в выполненииобработка ошибок / проверка перед возвратом объекта и возможность создания совершенно другого типа объекта, который расширяет / реализует тип возвращаемого значения, если вы выберете:

companion object {
    fun create(arg: Int): UIFragmentPhoto {
        if (arg == 0) throw IllegalStateException("Wasn't expecting 0!")
        if (arg == 1) return FragmentThatExtendsUIFragmentPhoto()
        if (arg == 2) return UIFragmentPhoto()
    }
}
0 голосов
/ 28 октября 2019

В kotlin свойство object обозначает коллекцию Singleton переменных и методов по умолчанию.

object Singleton { // or whatever you name it
   //singleton members
}

Оно лениво и поточно-ориентировано, оно инициализируется при первом вызове, так же как статические инициализаторы Java.

Вы можете объявить объект на верхнем уровне или внутри класса или другого объекта.

Таким образом, объявление вашего объекта внутри класса можно пометить ключевым словом companion:

class UIFragmentPhoto {
    companion object Factory {
        fun create(): UIFragmentPhoto = UIFragmentPhoto()
    }
}

Члены companion object можно вызывать, просто используя имя класса в качестве квалификатора:

val instance = UIFragmentPhoto.create()

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

val instance = UIFragmentPhoto.Factory.create()

В моем понимании companion означает, что этот объект является компаньоном с внешним классом. Так что, да, вы, очевидно, можете использовать instance, созданный в вашем Коде A, в Код B. Для любой путаницы дайте мне знать. Я постараюсь помочь вам в дальнейшем. Спасибо

0 голосов
/ 28 октября 2019

Нет, это неэффективно. Код B требует от вас создания одноразовой копии вашего фрагмента, чтобы вы могли вызвать фабричный метод для создания нужного фрагмента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...