Hello. I'm trying to do something like this and no...
# getting-started
a
Hello. I'm trying to do something like this and not sure what I'm doing. I want to have a Factory of, let's say, Ship. A Game has a mutable list (or whatever) of Ships so I can track all the Ships in my game (more in thread) -->
The Factory gets a reference to Game when I first create it and when building a Ship the Factory registers the Ship in its Game reference. However, the developer can still create classes of Ship outside the factory. I know I can give Ship a private constructor, but how would I allow the factory to create Ships that way? If I use a companion object I will need to provide it with the Game reference each time and it kind of loses the purpose of creating the Factory with the Game reference once. In C++land I'd probably use friend or something like that. How do you do that in Kotlin? Thanks.
Or maybe there's a smarter way to solve the "let's track all Ships" problem
n
However, the developer can still create classes of Ship outside the factory.
is this a feature or a bug
a
It's a bug as this way Game will not track the new Ship
That's why I want to centralize the creation of Ship in a Factory with a reference to a game (there may be different games at the same time :()
Maybe there's a trick with the
internal
keyword, but maybe I'm barking at the wrong tree
n
depends on whether you want people to be able to subclass Ship. if no, then saying
class Ship internal constructor(...)
should be sufficient, then making
ShipFactory.newShip(...)
public
if yes (subclassing permitted), you maybe could make
Ship
itself accept
Game
as a constructor argument that Ship registers with in the constructor, but...passing
this
to other objects from a constructor is problematic from various perspectives (security, exception-handling)
or finally, just say "hey you have to call
Game.addEntity(ship)
or it ain't gonna work"
a
Ok, cool, so two question:
I made the internal Ship class inside package
a.stuff.game
, but when I created a Ship instance in
a.stuff.somethingelse
the compiler did not complain. Is this expected as the
a.stuff
is common?
n
internal
works pretty differently from
package private
--
internal
is accessible anywhere in the entire "compilation unit", e.g. your library/engine
if your game and engine are in the same compilation unit and you're trying to keep yourself from calling the Ship constructor directly...that's going to require more thought
internal is documented here
a
Yeah I've read this, by 'module' they mean "compilation unit"?
internal
— any client inside this module who sees the declaring class sees its
internal
members;
n
yeah, module as in like...a Gradle project/subproject
a
I mistook that for package, alright. Thanks
n
I keep thinking "package" but that's a ridiculously overloaded term
I don't care much for Scala but it's ridiculously fine-grained access controls were nice sometimes 🙂
a
I didn't have the pleasure to play with Scala, not sure if I will 😅
Regarding Ship registering itself in game in a constructor, there's no real safe way to do that because the Ship's
this
is incomplete
I read about some lateinit magic, but not sure if that's a thing
And well, bottom line, I think the best way is just to register the Ship manually after creating it or inside a Factory - OR go the ECS route and be a happier person
n
lateinit is for certain var fields set outside the constructor
incidentally, the ECS route also probably requires you explicitly registering some things with the World. I'm writing an ECS for KorGE right now and running into that -- systems and components have to be manually registered with World which sucks
a
Hello KorGE, nice to meet you.
Although I'm creating something a bit different
v
Make the constructor private and make the factory method in the companion object
n
I've found
private
to be way stricter than Java's
private
when it comes to inner classes and the like
v
Even if so, Companion object can pretty well access the private constructor.
a
@Vampire can I create a companion object with a reference to a
Game
? so every call to companion's buildShip could reference that Game I initially gave?
v
You can set a property of the companion object
a
Like that!
v
Yeah, that works too. I had something in mind like (untested written on mobile)
Copy code
kt
class Ship private constructor() : Tickable {
    companion object {
        lateinit val game: Game
        fun create() = Ship().also(game::register)
    }

    override fun tick() {
        println("tock")
    }
}

...
Ship.game = Game()
Ship.create()
a
Thanks, looks cool! But this way Ship is tied to a single Game instance, right? I might have another game running in parallel which needs ships created in it and not the former.
v
Yeah, then the property approach doesn't work of course, so something like you have or like this:
Copy code
Yeah, that works too. I had something in mind like (untested written on mobile)
```kt
class Ship private constructor() : Tickable {
    companion object {
        fun producer(game: Game) : () -> Ship = {
            Ship().also(game::register)
        }
    }

    override fun tick() {
        println("tock")
    }
}

...
Ship.game = Game()
Ship.create()
a
thanks, I'll look into that fancy .also syntax too