0

When should I use an abstract class vs a regular class? More specifically, I'm trying to understand when constructor parameters should be preferred over abstract members.

For example:

sealed trait Client

abstract class BaseService {
  def client: Client
}

class Service extends BaseService {
  val client = ???
}

vs

sealed trait Client

class BaseService(client: Client) {}

class Service extends BaseService(client = ???)

As far as I can tell, both are valid.

zed917
  • 13
  • 3
  • There is room for opinion in this question, but I think in this case the second case makes more sense. - But, why? If a class needs another class to perform its work, then it should be clearly stated as a constructor argument. On the other hand, an abstract method makes more sense for defining an interface,which acts as a contract between implementers and users. Additionally, you may run into strange errors if you leave abstract vals that are used in the constructor of the abstract class, because of initialization order. – Luis Miguel Mejía Suárez Aug 13 '19 at 20:10
  • Got it. So you're drawing a contrast between dependencies and implementation detail that may vary across implementing class, correct? Could you provide an example of this? – zed917 Aug 13 '19 at 20:19
  • Not really, I am making a contrast between the interface that the class provides, that is the contract to which users of the class will refer to. And with the dependencies required to implement a class. - I assume that the code of **BaseService** needs a `client`, thus it makes sense that it should be a constructor argument. On the other hand, users of **BaseService** do not care that it has a `client` inside, thus it does not make sense to have it as a public method. – Luis Miguel Mejía Suárez Aug 13 '19 at 21:04
  • Understood. For the last point at least, one could just as easily add a `protected` access modifier to `client`, no? – zed917 Aug 13 '19 at 21:16
  • Well of course yes. But then again, it really feels weird to having to override a method instead of passing a constructor argument. Also, as I said before, you may end up with runtime erros due initialization order. – Luis Miguel Mejía Suárez Aug 13 '19 at 21:37

1 Answers1

0

As @Luis Miguel said, use abstract classes when you want to declare a common set of methods that need to be implemented by the sub-classes and/or share some limited functionality that will be used by all child classes.

Below, I'm listing some reasons why I considered you should pass your dependencies to the constructor rather than defining them in your classes/base-classes.

Use dependency injection

(IMHO) It is better to give the constructor the dependencies it needs to function properly AKA dependency injection.

Avoid tight coupling

When you declare a dependency inside your class or in your constructor, you are tightly coupling your Service with that specific implementation of the dependency, which is not ideal and is considered an antipattern.

Program to interfaces, not implementations

Injecting the dependencies gives you greater flexibility as you are not coupled to a specific implementation. This is true as long as your code relies on an interface/trait/abstract-class (consequently avoiding tight coupling).

When your class relies on an interface/trait/abstract-class can be very powerful as you can be passing a mock, a no-op, or different strategies of the client. So make sure you "Program to interfaces, not implementations".

Onema
  • 7,331
  • 12
  • 66
  • 102