Interfacing in Java/Kotlin

Interfacing in Java/Kotlin

What, How & Why Interfacing in Java/Kotlin, by following Practical Example.

ยท

3 min read

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

Interface - What, Why & How?

  • What: An interface in Java/Kotlin is a blueprint of a class. It has static constants and abstract methods. The interface in Java/Kotlin is a mechanism to achieve abstraction.

  • Why:

    • To achieve abstraction.

    • To support the functionality of multiple inheritance.

    • To achieve loose coupling.

    • Easier to test.

    • Increase flexibility.

    • Dependency Injection.

  • How: An interface is declared by using the interface keyword. It provides total abstraction; means all the methods in an interface are declared with the empty body, and all the fields are public, static and final by default. A class that implements an interface must implement all the methods declared in the interface.

Practical Example

Let's think about a scenario, we have a Book Store System, where we can save a new book record and search for books by following by Author Name only.

data class Book(val id : Int, val name : String, val author : String, val serialNumber : String)
interface BookStore{
    fun addNewBook(book: Book): Boolean
    fun searchBook(authorName : String): MutableList<Book>
}

We have a model call Book which has some attributes (Id, Name, Author, Serial Number).

We have our Interface called BookStore, which expose some public APIs (or Simply, Objects Behaviour) to the client.

Here comes the benefits of Interfacing. Client can access only the interface, but not the actual implementation of the system. Which provide security benefits and according to good coding approach, client shouldn't know about the underlying implementation of a system.

Now, let's take a look at the implementation of BookStore.

class DefaultBookStore() : BookStore {
    companion object {
        private val store: MutableList<Book> = mutableListOf()
    }

    override fun addNewBook(book: Book): Boolean {
        if (!store.contains(book)) {
            store.add(book)
            return true
        }
        return false
    }

    override fun searchBook(authorName: String): MutableList<Book> {
        val filteredBooks = mutableListOf<Book>()
        store.forEach {
            if (it.author.contains(authorName, true)) {
                filteredBooks.add(it)
            }
        }
        return filteredBooks
    }

}
  • DefaultBookStore is the impl of BookStore.

  • DefaultBookStore contain a static list (MutableList), where all books are stored.

  • addNewBook(book: Book): Boolean is designed to store a new book to the list if it's not stored already in the system and return Boolean to verify if book is added or not.

  • searchBook(authorName: String): MutableList<Book> should return a list of Book, where filtered books are searched based on query (Author Name).

But we can can't expose the Implementation of the System, instead we will provide the interface BookStore to the client.

Now, lets create a Factory class who is responsible to provide an instance of BookSystem.

object BookStoreFactory {
    private val defaultBookStore: BookStore = DefaultBookStore()

    fun getDefaultBookStore(): BookStore {
        return defaultBookStore
    }
}
  • BookStoreFactory expose a public API to get the instance of BookStore.

  • getDefaultBookStore(): BookStore return an instance of BookStore, in our case its DefaultBookStore where all business logic is defined.

Unit Tests

class BookStoreSystemTest {
    private var bookStore: BookStore? = null

    @Before
    fun setup() {
        bookStore = BookStoreFactory.getDefaultBookStore()
    }

    @Test
    fun `get default book store from factory`() {
        assert(bookStore != null)
    }

    @Test
    fun `add some books to the system`() {
        assert(bookStore?.addNewBook(Book(1, "Test Book 1", "Romman Sabbir", "12")) == true)
        assert(bookStore?.addNewBook(Book(2, "Test Book 2", "Test Author", "1243")) == true)
        assert(bookStore?.addNewBook(Book(3, "Test Book 3", "Forhad An Naim", "12434")) == true)
        assert(bookStore?.addNewBook(Book(4, "Test Book 4", "Romman Sabbir", "12434re")) == true)
    }

    @Test
    fun `search books by author`() {
        val searchedBooks = bookStore?.searchBook("romman") ?: mutableListOf()
        assert(searchedBooks.isNotEmpty())
    }

    @After
    fun tearDown() {
        bookStore = null
    }
}

Result:

โœ… Tests passed: 3 of 3 tests - 20 ms

Recap

  • Interfacing helps to provide abstraction to the system.

  • Interface expose only behaviour not business logic.

  • Interface helps to implement multiple inheritance.

  • Easy to test an object behaviour.

  • Increase code readability and helps to maintain proper codebase.

Happy Coding...

ย