3

Let's assume in our application we want to model cars. We also want to model a car repository where we store some registered cars. How should that be modeled in scala?

Here comes my approach: First, I create a case class PlainCar. This is just a car how it exists in real world, with nothing special in it. Next I create a CarRepository. I also create a RegisteredCar. The CarRepository can now store PlainCars and return them as RegisteredCars. Both PlainCar and RegisteredCar extend the trait Car which provides all the comon methods like drive. RegisteredCar however is special - it owns an instance of PlainCar but also adds a method registrationNumber which returns the registration number; all other methods are just delegated to the PlainCar instance.

I feel however, that there are some flaws of this design. One is that I have to change RegisteredCar when I add a method/property to PlainCar which I think should not have to be done.

My question is therefore, can this be modeled better and if so, how? Are there other drawbacks that I am missing?

Some advice on naming conventions would also be appreciated because PlainCar sounds quite awkward to me.

andrej523
  • 3
  • 4
valenterry
  • 2,429

2 Answers2

3

It sounds as if registration is a state which only has meaning in the context of a repository. You have described two states, (registered with the repository, not registered with the repository). It sounds to me as if what you need is a car container (wrapper if you like). Think of it as a collection which can hold one car, in the same way that Option[Car] or Either[Car, Motorbike] can only hold one object.

So create a RepositoryItem sealed base trait (or sealed abstract class) and create case class instances (which extend RepositoryItem) for each state. The Registered variant would have a registration number. Make your Repository a container of RepositoryItems. It should not be part of the same set of case classes.

Advantages of this approach include

  • You can make it generic (RepositoryItem[A] or RepositoryItem[+A] rather than RepositoryItem[Car]). This means it could become a repository of other things (or could easily expand to hold motorbikes as well as cars).
  • Complete separation of concerns between repository implementation and car implementation. One can change (or add new features) without affecting the other.
  • You can create new subclasses of Car as often as you like. Even if you create Repository[Car] from the start, rather than making it generic, it will be able to hold Car subclasses with no modification.

Have a look at the way Option and Either are designed. You might want to implement at least map, so that you can manipulate a car (or replace it with a bike) without having to take it out of the repository slot.

EDIT:

To explain a point which may have created confusion, judging by your comment...

The Option and Either types have a map function (as does List and any other monad). The map function allows you to pass in a function which can manipulate the object contained. You get back a transformed object still wrapped in the Option.

scala> val o = Some(3)
o: Some[Int] = Some(3)

scala> o map (_ * 5)
res2: Option[Int] = Some(15)

No dependencies between Option and Int but because Option implements map you can manipulate the Int without removing it from Option wrapping.

itsbruce
  • 3,215
2

I feel like I'm missing something, but I would just make your CarRepository a Map[RegistrationNumber, PlainCar] or something similar. Usually with immutable objects, it's much easier to create this kind of association outside the original object.

My second choice would be RegisteredCar extends PlainCar, but I think this creates unnecessary coupling and copies. Both of those options would allow you to just rename PlainCar to Car.

Karl Bielefeldt
  • 148,830