Last active
October 6, 2017 08:49
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In the
GreatObjectOrientation
version, Scalatrait
s are either:Interface
: abstract methods + abstract valuesimplementation
of some of the methods in the interfaces , values still abstract.whereas
classes
only implement values: instantiate services or configure parameters. basically define anenvironment
. see this.The trait-mixin pattern is essentially an easier way of achieving traditional OO Composition.