使用自类型进行依赖项注入会导致公开其他特征的公共方法,从而破坏了单一责任主体。让我来谈谈例子
trait Output {
def output(str: String): Unit
}
trait ConsoleOutput extends Output {
override def output(str: String): Unit = println(str)
}
class Sample {
self: Output =>
def doSomething() = {
// I do something stupid here!
output("Output goes here!")
}
}
val obj = new Sample with ConsoleOutput
obj.output("Hey there")
我的Sample
类依赖于Output
特质,当然,我想在我的Output
类中使用Sample
特质方法。但是在上面的代码示例中,我的Sample
类公开了output
方法,该方法并非源于其功能,并且打破了Sample
的单一职责。
如何避免这种情况,并继续使用自定义和蛋糕模式?
答案 0 :(得分:1)
自我类型在这里并不重要。从另一个类继承会暴露该类的公共方法,而不管其自身类型如何。因此,从具有公共方法的类继承任何内容都可以说违反了单一责任原则。
如果打算将trait
用于依赖项注入,则应使其方法protected
使其不暴露。
trait Output {
protected def output(str: String): Unit
}
trait ConsoleOutput extends Output {
protected override def output(str: String): Unit = println(str)
}
被接受的答案声称“提供输出的责任仍然在于实现Output
的组件”。这是不正确的,并且显示了类型和实现之间的混淆。
对象的行为由其类型而不是其实现指定(Liskov替换原理)。类型是合同,它告诉用户对象可以做什么。因此,类型指定了职责,而不是实现。
类型Sample with ConsoleOutput
具有output
类型的Object
方法和doSomething
类型的Sample
方法。因此,它有责任提供这两种方法的实现。 output
的实现是在ConsoleOuput
中的事实与类型无关,因此与负责它的人无关。
Sample with ConsoleOutput
对象可以轻松地覆盖output
的实现,在这种情况下,它显然将负责该方法,而不是ConsoleOutput
。 Sample with ConsoleOutput
选择不更改output
的实现这一事实并不意味着它不对此负责。当实现更改时,对象的职责不会更改。
此原则是软件工程的五个SOLID原则中的第一个。 正如Wikipedia解释的那样,“单一责任原则[]规定,每个模块或类都应对软件提供的功能的一部分负责,而责任应由类完全封装。”
换句话说,不要这样做:
class MultipleResponsibilities {
def computePi(places: Int): List[Int]
def countVowels(text: String): Int
}
但是请改为这样做:
class PiComputer {
def computePi(places: Int): List[Int]
}
class VowelCounter {
def countVowels(text: String): Int
}
computePi
和countVowels
是程序功能的不同部分,因此应将它们封装在不同的类中。
第三个SOLID原理是Liskov Substitution Principle,它表示对象的功能应仅取决于类型,而不受实现的影响。您应该能够更改实现,并且仍然可以以相同的方式使用对象并获得相同的结果。
由于对象的功能完全由对象的类型定义,因此对象的职责也由类型完全定义。更改实施不会更改职责。
答案 1 :(得分:1)
责任仍然取决于实现Output
的组件。您的课程可以访问它的事实与
class Foo(val out: Output)
new Foo(new ConsoleOutput{}).out.output
当然,您可以在此处将out
设为私有,但是如果您也不想从外部访问.output
,也可以在ConsoleOutput
中对其进行保护。
(另一个答案中对您的评论的回答是,如果您还想“独立”使用它,则可以对其进行子类化,并在子类中将output
公开)。