3

I have a Kotlin data class:

data class PaymentAccount(
    val accountId: Int,
    val accountNumber: String,
    val title: String
)

This is what I'd do in Java:

Create an abstract class:

public abstract class PaymentAccount {

    protected int accountId;
    protected String accountNumber;
    protected String title;

    public PaymentAccount(int accountId,
                          String accountNumber,
                          String title) {
        this.accountId = accountId;
        this.accountNumber = accountNumber;
        this.title = title;
    }
}

Create null object and extend abstract class:

public class NullPaymentAccount extends PaymentAccount {

    public NullPaymentAccount() {
        super(-1,
                "Invalid account number",
                "Invalid title");
    }
}

Create a real object and extend abstract class too:

public class RealPaymentAccount extends PaymentAccount {

    public RealPaymentAccount(int accountId,
                              String accountNumber,
                              String title) {
        super(accountId,
                accountNumber,
                title);
    }
}

How to implement Null Object pattern in Kotlin properly? Is there more than one way? If so, what is the most concise and elegant way?

7
  • 1
    Have you done the equivalent in, for example, Java? Or any other languages? Commented Aug 14, 2018 at 18:51
  • 1
    Why do you need the null object pattern when you have nullable types? Commented Aug 14, 2018 at 18:51
  • @marstran Just because there's null safety doesn't mean the null object pattern is obsolete. One of the main ideas behind the null object pattern is to have a default object in the case that you are required to have an object, but really have nothing to pass to it. Commented Aug 14, 2018 at 18:56
  • @Pelocho Java - yes. But I new to Kotlin so I expect here to be some difference in implementation or more concise or elegant ways to implement it Commented Aug 14, 2018 at 18:58
  • Cool, can you edit the question and add the implementation you'd do in Java and we'll help you do it with Kotlin. I can say in advance that probably the elvis operator will be involved Commented Aug 14, 2018 at 19:00

4 Answers 4

8

In Kotlin you can do the same, just with less lines of code:

interface Account {
    val accountId: Int
    val accountNumber: String
    val title: String
}

object EmptyAccount : Account {
        override val accountId: Int = 1
        override val accountNumber: String = ""
        override val title: String = ""
}

data class PaymentAccount(
        override val accountId: Int,
        override val accountNumber: String,
        override val title: String): Account

Notice that we also make EmptyAccount singletone for efficiency.

Sign up to request clarification or add additional context in comments.

1 Comment

Forgot about singleton. Good addition.
6

While the solution you've been given already is fine, you might not need to have an explicit class to represent a null state if your default values aren't all or nothing. I would argue that providing defaults for all of your fields is the null object pattern.

For example, you could write your class like this:

data class PaymentAccount(
    val accountId: Int = -1,
    val accountNumber: String = "Invalid Account Number",
    val title: String = "Invalid Title"
)

And when you construct them:

PaymentAccount(1, "Acct2", "Checking") // Actual data
PaymentAccount() // Default values, therefore a "null" object.

The only real problem is when you only specify some of the values, but that might be ok/desirable:

PaymentAccount(1) // accountNumber and title have defaults

1 Comment

Looks good. But there is one more (insignificant, but) problem needs to be mentioned. What if for some reason in code to which this NullObject was provided we need to check whether PaymentAccount is EmptyAccount or just an Account? I don't see any elegant way to do so.
1

Better use sealed interface or sealed class. For example:

sealed interface Account {

        val accountId: Int
        val accountNumber: String
        val title: String

        object Empty : Account {
            override val accountId: Int = 1
            override val accountNumber: String = ""
            override val title: String = ""
        }

        data class Payment(
            override val accountId: Int,
            override val accountNumber: String,
            override val title: String
        ) : Account
    }

Usage:

    val account = findAccountById(id = 1)

    when (account) {
        Account.Empty -> TODO()
        is Account.Payment -> TODO()
    }
        

Where:

 val list = listOf(
        Account.Payment(
            accountId = 1,
            accountNumber = "1",
            title = "Bob"
        )
    )

 private fun findAccountById(id: Int): Account {
        return list.firstOrNull { it.accountId == id }?:Account.Empty
    }

Comments

0

Another way could be to define a secondary constructor for this:

data class PaymentAccount(
    val accountId: Int,
    val accountNumber: String,
    val title: String
){
  constructor() : this(-1,
                  "Invalid account number",
                  "Invalid title")
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.