2

I've bumped into this code and I'm not sure why would anyone do this. Basically the author decided for making the class constructor private so that it cannot be instantiated outside the file, and added a public method to a companion object in the class that creates a new instance of this class. What is the benefit of this approach?

This is what I found:

class Foo private constructor(private val arg1: Any) {
    //more code here..
    companion object {
        fun newFoo(arg1: Any) = Foo(arg1 = arg1)
    }
}

Why is it better than this?

class Foo(private val arg1: Any) {
    //more code here..
}
Thiago Saraiva
  • 181
  • 1
  • 10
  • In general, it lets you change the implementation later, whether it's to a new subtype or if you're restructuring the class's constructors later or whatever. It also lets you define a more detailed _name_ for constructing the object that way, that's more detailed than just the name of the type. – Louis Wasserman Feb 13 '19 at 21:00
  • 2
    Look up "static factory method". For example, https://stackoverflow.com/questions/929021/what-are-static-factory-methods – laalto Feb 13 '19 at 21:06

4 Answers4

2

There are several benefits to providing a factory method instead of a public constructor, including:

  • It can do lots of processing before calling the construstor. (This can be important if the superclass constructor takes parameters that need to be calculated.)

  • It can return cached values instead of new instances where appropriate.

  • It can return a subclass. (This allows you to make the top class an interface, as noted in another answer.) The exact class can differ between calls, and can even be an anonymous type.

  • It can have a name (as noted in another answer). This is especially important if you need multiple methods taking the same parameters. (E.g. a Point object which could be constructed from rectangular or polar co-ordinates.) However, a factory method doesn't need a specific name; if you implement the invoke() method in the companion object, you can call it in exactly the same way as a constructor.

  • It makes it easier to change the implementation of the class without affecting its public interface.

It also has an important drawback:

  • It can't be used by subclass constructors.

Factory methods seem to be less used in Kotlin than Java, perhaps due to Kotlin's simpler syntax for primary constructors and properties. But they're still worth considering — especially as Kotlin companion objects can inherit.

For much deeper info, see this article, which looks at the recommendation in Effective Java and how it applies to Kotlin.

gidds
  • 16,558
  • 2
  • 19
  • 26
  • For readers that want to dig deeper: [Effective Java in Kotlin, item 1: Consider static factory methods instead of constructors](https://blog.kotlin-academy.com/effective-java-in-kotlin-item-1-consider-static-factory-methods-instead-of-constructors-8d0d7b5814b2) – Frank Neblung Feb 14 '19 at 13:35
  • @FrankNeblung Good reference — and far more authoritative than my own half-remembered ideas!  I'll add it in, thanks. – gidds Feb 14 '19 at 16:59
1

If you want to change Foo into an interface in the future the code based on the method will keep working, since you can return a concrete class which still implements Foo, unlike the constructor which no longer exists.

Kiskae
  • 24,655
  • 2
  • 77
  • 74
0

An example specific to android is, that Fragments should be constructed with an empty constructed, and any data you'd like to pass through to them should be put in a bundle.

We can create a static/companion function, which takes in the arguments we need for that fragment, and this method would construct the fragment using the empty constructor and pass in the data using a bundle.

TomH
  • 2,581
  • 1
  • 15
  • 30
0

There are many useful cases, for example what Kiskae described. Another good one would be to be able to "give your constructors names":

class Foo<S: Any, T: Any> private constructor(private val a: S, private val b: T) {
    //more code here...
    companion object {
        fun <S: Any> createForPurposeX(a: S) = Foo(a = a, b = "Default value")
        fun createForPurposeY() = Foo(a = 1, b = 2)
    }
}

Call site:

Foo.createForPurposeX("Hey")
Foo.createForPurposeY()

Note: You should use generic types instead of Any.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121