В чем разница между миксинами и наследованием?

Я пытаюсь понять Mixins в контексте scala. В частности, я хотел знать разницу между концепциями наследования и Mixins. Wiki говорит, что существует важная разница между понятиями mixins и наследования, и поэтому я хотел ее понять.

В определении Mixin в wiki говорится:

Класс mixin действует как родительский класс, содержащий требуемые функциональные возможности. Подкласс может затем наследовать или просто повторно использовать эту функциональность , но не как средство специализации. Как правило, mixin будет экспортировать желаемую функциональность в дочерний класс, не создавая жесткую, единственную связь "есть". Здесь важное различие между понятиями mixins и inheritance заключается в том, что дочерний класс все еще может наследовать все функции родительского класса, но семантика о ребенке "является своего рода" родителем не обязательно обязательно применяется.

В приведенном выше определении я не могу понять утверждения, выделенные жирным шрифтом. что это означает, что

  • Подкласс может наследовать функциональность в mixin, но не как средство специализации
  • В mixins ребенок наследует все функции родительского класса, но семантика о том, что ребенок "является видом", не обязательно должен применяться родитель. - Как ребенок может продлить родителя, а не обязательно своего рода Родитель? Есть ли такой пример.

Заранее благодарим за любые разъяснения по поводу вышеизложенного.

Ответ 1

Я не уверен, что правильно понял ваш вопрос, но если бы я это сделал, вы спрашиваете, как что-то может наследовать, не имея в действительности ничего общего с наследованием.

Микшины, однако, не наследуются - на самом деле это больше похоже на динамическое добавление набора методов в объект. В то время как наследование говорит: "Это нечто другое", миксины говорят: "Этот объект имеет некоторые черты этого другого". Вы можете видеть это в ключевом слове, используемом для объявления mixins: trait.

Чтобы наглядно украсть пример с домашней страницы Scala:

abstract class Spacecraft {
  def engage(): Unit
}
trait CommandoBridge extends Spacecraft {
  def engage(): Unit = {
    for (_ <- 1 to 3)
      speedUp()
  }
  def speedUp(): Unit
}
trait PulseEngine extends Spacecraft {
  val maxPulse: Int
  var currentPulse: Int = 0
  def speedUp(): Unit = {
    if (currentPulse < maxPulse)
      currentPulse += 1
  }
}
class StarCruiser extends Spacecraft
                     with CommandoBridge
                     with PulseEngine {
  val maxPulse = 200
}

В этом случае StarCruiser не является CommandoBridge или PulseEngine; он имеет их, однако, и получает методы, определенные в этих чертах. Это Spacecraft, как вы можете видеть, потому что он наследует от этого класса.

Стоит отметить, что когда trait расширяет class, если вы хотите сделать что-то with, это свойство, оно должно расширить этот класс. Например, если у меня был class Dog, я не мог бы иметь Dog with PulseEngine, если только Dog extended Spacecraft. Таким образом, это не совсем похоже на добавление методов; однако он все еще схож.

Ответ 2

Признак (который называется mixin при смешивании с классом) похож на интерфейс в Java (хотя существует много различий), где вы может добавить дополнительные функции в класс, не обязательно имея отношение "является". Или вы можете сказать, что в целом черты объединяют функции, которые могут использоваться несколькими независимыми классами.

Чтобы предоставить вам пример из библиотеки Scala, Заказ [A] - это trait, который обеспечивает реализацию для некоторых базовых операций сравнения (например, <, <=, >, >=) для классов, которые могут иметь данные с естественным упорядочением.

Например, скажем, у вас есть свой класс Number и подклассы EvenNumber и OddNumber, как показано ниже.

class Number(val num : Int) extends Ordered[Number] {
  override def compare(that : Number) = this.num - that.num
}

trait Half extends Number {
  def half() = num / 2
}

trait Increment extends Number {
  def increment() = num + 1
}

class EvenNumber(val evenNum : Int) extends Number(evenNum) with Half

class OddNumber(val oddNum : Int) extends Number(oddNum) with Increment

В приведенном выше примере классы EvenNumber и OddNumber share являются отношениями с Number, но EvenNumber не имеет отношения "is" с Half ни OddNumber share "является" отношением с Increment.

Еще один важный момент - хотя класс Number использует синтаксис extends Ordered, это означает, что Number имеет неявное отношение с суперклассом Ordered, то есть Any.

Ответ 3

Я думаю, что это очень зависит от использования. Scala, являющийся языком мультипарадигмы, делает его мощным, а также немного запутанным. Я думаю, что Mixins очень эффективны при правильном использовании. Микшины следует использовать для введения поведения и уменьшения болиров.

Признак в Scala может иметь реализации, и возникает соблазн расширить их и использовать их.

Черты могут использоваться для наследования. Его также можно назвать mixins, однако, на мой взгляд, это не лучший способ использования поведения mixin. В этом случае вы можете думать о чертах как Java Abstract Classes. В результате вы получаете подклассы, которые являются "типом" суперкласса (черта).

Однако черты могут использоваться как proper mixins. Теперь использование признака как mixin зависит от реализации, которая "как вы ее смешиваете". В основном это простой вопрос, чтобы спросить себя. Это "Является ли подкласс признака действительно kind признака или является поведением в поведении признаков, которое уменьшает шаблон". Как правило, это лучше всего реализовать путем смешивания признаков с объектами, а не расширения признака для создания новых классов.

Например, рассмотрим следующий пример:

    //All future versions of DAO will extend this
trait AbstractDAO{
  def getRecords:String
  def updateRecords(records:String):Unit
}
//One concrete version
trait concreteDAO extends AbstractDAO{
  override def getRecords={"Here are records"}
  override def updateRecords(records:String){
    println("Updated "+records)
  }
}
//One concrete version
trait concreteDAO1 extends AbstractDAO{
  override def getRecords={"Records returned from DAO2"}
  override def updateRecords(records:String){
    println("Updated via DAO2"+records)
  }
}
//This trait just defines dependencies (in this case an instance of AbstractDAO) and defines operations based over that
trait service{
  this:AbstractDAO =>

  def updateRecordsViaDAO(record:String)={  
  updateRecords(record) 
  }
  def getRecordsViaDAO={
  getRecords
  }
}


object DI extends App{
  val wiredObject = new service with concreteDAO //injecting concrete DAO to the service and calling methods
  wiredObject.updateRecords("RECORD1")
  println(wiredObject.getRecords)

  val wiredObject1 = new service with concreteDAO1
  wiredObject1.updateRecords("RECORD2")
  println(wiredObject1.getRecords)

}

concreteDAO - это признак, который распространяется на AbstractDAO - это наследование

val wiredObject = new service with concreteDAO - Это правильное поведение смесителя Так как служебная черта задает mixin для AbstractDAO. Было бы просто неправильно Service продлить concreteDAO в любом случае, потому что Service требуется AbstractDAO, это не тип AbstractDAO. Вместо этого вы создаете экземпляры Service с разными миксинами.

Ответ 4

Я думаю, что речь идет о фактической иерархии классов. Например, Dog является типом Animal, если он распространяется из класса (наследование). Его можно использовать везде, где применим параметр Animal.