Skip to content

Instantly share code, notes, and snippets.

@ShahOdin
Last active October 6, 2017 08:49
Show Gist options
  • Save ShahOdin/34b884f92b937c38086795b64b41a45c to your computer and use it in GitHub Desktop.
Save ShahOdin/34b884f92b937c38086795b64b41a45c to your computer and use it in GitHub Desktop.
a demonstration of the problems with dependency injection in OO and how these problems can be avoided with trait mix-ins in Scala.
// Hidden dependency makes inheriting from implementation dangerous.
object BadObjectOrientation {
trait Interface {
protected def resourceA: Int
protected def resourceB: String
//only supposed to use A
def fooA: Int
//only supposed to use B
def fooB: String
}
class Implementation extends Interface {
override protected def resourceA: Int = 1
override protected def resourceB: String = "ssss"
//fine.
override def fooA: Int = resourceA * 2
//unintended dependency. fooB is free to use resources it shouldn't.
override def fooB: String = resourceB + resourceA.toString
def fooBB: String = fooB + resourceA.toString + fooB
}
class Inherited_Implementation extends Implementation {
override def resourceA = 2
//consequences:
// OK
// fooA: 1 => 4 (expected)
// Not OK: Hidden dependency!
// fooBB: "ssss1ssss" => "ssss2ssss"
}
}
//hard to refactor.
object MediocreObjectOrientation {
trait InterfaceA {
protected def resourceA: Int
def fooA: Int
}
trait InterfaceAB extends InterfaceA {
protected def resourceB: String
def fooB: String
//InterfaceB is free to use InterfaceA dependencies.
//assumes:
// - interfaceA will always have resourceA
// - resourceA will obey certain information about
// resourceA at the time of development which might change.
def fooC: String = resourceA.toString
}
abstract class Single_Implementation extends InterfaceAB
}
// - interfaces don't have access to irrelevant dependencies.
// bit tedious depending on the size of the class
object GoodObjectOrientation {
trait InterfaceA {
protected def resourceA: Int
//only supposed to use A
def fooA: Int
}
trait InterfaceB {
protected def resourceB: String
def fooB: String
//No way of getting this wrong.
def fooBB: String = fooB + fooB
}
// fooB has access to resourceA but this is the only implementation.
// slightly problematic, but not a huge deal. this entity is the only
//object to be refactored / maintained.
object Client1 extends InterfaceA with InterfaceB {
override protected def resourceA: Int = 1
override def fooA: Int = resourceA * 3
override protected def resourceB = "s"
override def fooB: String = "foo" + resourceB
}
}
//easy to refactor, no nasty surprises
object GreatObjectOrientation {
trait InterfaceA {
protected def resourceA: Int
def fooA: Int
}
trait InterfaceB {
protected def resourceB: String
def fooB: String
//No way of getting this wrong.
def fooBB: String = fooB + fooB
}
trait ImplementationA {
self: InterfaceA ⇒
override def fooA: Int = resourceA * 3
}
trait ImplementationB {
self: InterfaceB ⇒
override def fooB: String = "foo" + resourceB
}
object Client extends InterfaceA with InterfaceB
with ImplementationA with ImplementationB {
override protected def resourceA = 2
override protected def resourceB = "s"
}
}
// Not massively shorter, but if there is only one
// implementation of A/B concerns, the above could be
// simplified to:
object GreatEnoughObjectOrientation {
trait InterfaceA {
protected def resourceA: Int
def fooA: Int
}
trait InterfaceB {
protected def resourceB: String
def fooB: String
//No way of getting this wrong.
def fooBB: String = fooB + fooB
}
trait ImplementationA extends InterfaceA {
override def fooA: Int = resourceA * 3
}
trait ImplementationB extends InterfaceB {
override def fooB: String = "foo" + resourceB
}
object Client2 extends ImplementationA with ImplementationB {
override protected def resourceA = 2
override protected def resourceB = "s"
}
}
@ShahOdin
Copy link
Author

ShahOdin commented Oct 5, 2017

In the GreatObjectOrientation version, Scala traits are either:

  • Interface : abstract methods + abstract values
  • implementation of some of the methods in the interfaces , values still abstract.

whereas classes only implement values: instantiate services or configure parameters. basically define an environment. see this.

The trait-mixin pattern is essentially an easier way of achieving traditional OO Composition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment