在Scala中使用自定义类型时如何保持单一职责?

时间:2019-01-05 06:48:24

标签: scala self-type cake-pattern

使用自类型进行依赖项注入会导致公开其他特征的公共方法,从而破坏了单一责任主体。让我来谈谈例子

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的单一职责。

如何避免这种情况,并继续使用自定义和蛋糕模式?

2 个答案:

答案 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的实现,在这种情况下,它显然将负责该方法,而不是ConsoleOutputSample with ConsoleOutput选择不更改output的实现这一事实并不意味着它不对此负责。当实现更改时,对象的职责不会更改。

Single Responsibility Principle

的说明

此原则是软件工程的五个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
}

computePicountVowels是程序功能的不同部分,因此应将它们封装在不同的类中。

第三个SOLID原理是Liskov Substitution Principle,它表示对象的功能应仅取决于类型,而不受实现的影响。您应该能够更改实现,并且仍然可以以相同的方式使用对象并获得相同的结果。

由于对象的功能完全由对象的类型定义,因此对象的职责也由类型完全定义。更改实施不会更改职责。

答案 1 :(得分:1)

提供输出的

责任仍然取决于实现Output的组件。您的课程可以访问它的事实与

类似
  class Foo(val out: Output)
  new Foo(new ConsoleOutput{}).out.output

当然,您可以在此处将out设为私有,但是如果您也不想从外部访问.output,也可以在ConsoleOutput中对其进行保护。

(另一个答案中对您的评论的回答是,如果您还想“独立”使用它,则可以对其进行子类化,并在子类中将output公开)。