So i have been doing a little bit of investigating...
# android
l
So i have been doing a little bit of investigating about interface vs concrete implementation naming conventions and i haven't seen any consensus. Some devs use the
Impl
Imp
prefix or suffix for the concrete implementation and leave the Interface without any prefix or suffix ... mean while other devs use an
I
prefix or suffix to denote the Interface and they leave the concrete implementation without any prefix or suffix. For example:
interface UserRepository
and
class UserRepositoryImpl: UserRepository
vs
interface IUserRepository
and
class UserRepository: IUserRepostiory
which version is better or is there a better alternative? My question also applies to
LocalDataSource
and
RemoteDataSource
interface vs concrete implementation naming.
not kotlin but kotlin colored 1
s
I don't think one is strictly better, but I'd generally go for the first option, because (1) I think the
I
isn't conventionally used for interfaces in the Android world (2) We don't usually prefix anything with its type anymore. (There was a time where people would use
iNumOfItems
or
strUserName
because IDEs weren't that great yet.)
But I think there's an argument to be made if you • want to clearly differentiate your interfaces (e.g. because you write a library and want to expose them to consumers), or • when the interface is less important than the implementation (e.g.
Logger : ILogger
seems sensible if you want everyone to use
Logger
, with an option to write their own if needed.
xxImpl
implies that its an internal implementation which shouldn't be handled directly).
l
I'm basically only going to have a single concrete implementation of the interface
Repository pattern with the clean architecture
Remote and local data source
c
From what I've seen in the Kotlin standard library/in KotlinX: the interface name shouldn't have anything specific because that's what will appear everywhere in the code. For the implementation: • If it's not important for downstream users (e;g. there's only one), use the prefix
Impl
and make it private. Provide a factory named after the interface. • If it is important for downstream users, give it a proper name! Everything visible from outside your module should have a proper name.
👍 1
l
So is the interface or class marked as private?
c
The class.
l
Well if it's private than you need to add factory pattern for all of those private classes
What do you name the factory class then?
c
If you have multiple implementations, you can come up with a better name, right?
l
Also wont this quickly bloat into several more classes
c
Also we don't write factory classes
l
In my case there will only be 1 implementation for those interfaces
c
Why do you have an interface with a single implementation?
l
The interfaces are there to make it easier to test the data layer
I'm using clean architecture with the repository pattern
Which uses remote and local data sources
c
So you do expect the possibility of creating more implementations in the future, so you should name your implementation after what's specific about this one compared to the eventual future ones
l
I know there won't be future implementations just a single concrete implementation per interface
At least for my application
c
E.g.
Copy code
// domain/src/…
interface UserRepository {
    suspend fun getAll(): List<User>
}

// mongodb/src/…
class MongoUserRepository {
    override suspend fun getAll(): List<User> { … }
}
l
Yeah well that's attaching the data source to the class name
My repository has a local as well as a remote data source as a dependency
Repo(local, remote)
c
So the data source is the thing actually doing the work?
The repository implementation just delegates to the data source?
l
Exactly that's how repository pattern works
It can fetch from remote and save to local all inside the repository
You can define how you want that to work and it can be different for each repository in your application
c
Then:
Copy code
interface UserRepository {
    suspend fun getAll(): List<User>
}

private class UserRepositoryImpl(
    private val source: DataSource,
) {
    override fun getAll(): List<User> =
        source.getAllUsers()
}

fun userRepositoryFor(source: DataSource) = UserRepositoryImpl(source)
I don't see why you have an interface at all then.
If that's not what you're doing, give an example of what you want, it's not clear
l
If I want to mock dependencies in unit tests then I want to be coding to an interface not a concrete implementation, that's why I'm using interfaces.
If UserRepositoryImpl is private then how can you access it exactly? I don't see the factory pattern in your code snippet above.
Btw thanks for sharing your ideas about this topic. It's super helpful.
c
"A concrete implementation" only is a meaningful concept if there are multiple of them (or it's possible that there are multiple in the future). You probably don't create an interface for
User
for tests, you probably have just a
data class User
, right? If the repository just delegates to the data source, and the data source is the thing that has the logic, then you never need to mock the repository, you can mock the data source instead.
I don't see the factory pattern in your code snippet above.
It's the top-level
userRepositoryFor
function. There is no need to create classes for factories in Kotlin.
l
Does the function
useRepositoryFor
need to define in the same Kotlin file as
class UserRepositoryImpl
?
Oh that can be defined at the top level and then outside of this file you can reference the top level function. Gotcha ... Kotlin is pretty cool to have that language feature.
c
If the class is private, yes. In general, it's good practice to at least keep it close so it's easy to find
l
The repository doesn't always just delegate