离散特征和连续特征混合_混合蛋白和特征

本文探讨了Groovy的Mixins和Scala的Traits,它们是Java.next语言中处理类扩展和代码重用的机制。Groovy通过注解实现Mixins,允许在运行时添加方法,而Scala的Traits是静态类型,提供丰富的接口和可堆叠修改。这两种机制解决了Java多重继承带来的菱形问题和接口的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java语言的开发人员精通C ++和其他包含多重继承的语言,从而使类可以从任意数量的父级继承。 多重继承的问题之一是无法确定派生自哪个父继承功能。 这个问题称为菱形问题 (请参阅参考资料 )。 多重继承中固有的菱形问题和其他复杂性启发了Java语言设计人员选择单一继承加接口。

接口定义语义,但不定义行为。 它们很好地用于定义方法签名和数据抽象,并且所有Java.next语言都支持Java接口,而无需进行必要的更改。 但是,某些跨领域的关注点不适用于单继承+接口模型。 这种错位导致需要Java语言的外部机制,例如面向方面的编程。 两种Java.next语言-Groovy和Scala-通过使用称为mixin或trait的语言构造在另一个扩展级别上处理此类问题。 本文介绍了Groovy mixins和Scala特性,并展示了如何使用它们。 (Clojure通过协议处理了许多相同的功能,我在Java.next中介绍了该协议:没有继承的扩展,第2部分 。)

混合蛋白

一些早期的面向对象的语言在单个代码块中一起定义了类的属性和方法,从而完成了类定义。 用其他语言,开发人员可以在一个地方定义属性,但是将方法定义推迟到以后再在适当的时候将它们“混合”到类中。 随着面向对象语言的发展,mixin如何与现代语言一起工作的细节也随之发展。

在Ruby,Groovy和类似语言中,mixins扩展了现有类的层次结构,作为接口和父类之间的交叉。 像接口一样,mixin都充当instanceof检查的类型,并且遵循相同的扩展规则。 您可以将无限数量的mixin应用于一个类。 与接口不同,mixin不仅可以指定方法签名,还可以实现签名的行为。

在最早包含mixin的语言中,mixin仅包含方法,而没有诸如成员变量之类的状态。 现在,许多语言(其中包括Groovy)都包含有状态的mixin。 斯卡拉特质也表现得很庄重。

Groovy Mixins

Groovy通过metaClass.mixin()方法或@Mixin批注实现了mixins。 ( @Mixin批注依次将Groovy抽象语法树(AST)转换用于必需的元编程管道。)清单1中的示例使用metaClass.mixin()来使File类具有创建压缩ZIP文件的能力:

清单1.将zip()方法混合到File类中
class Zipper {

  def zip(dest) {
      new ZipOutputStream(new FileOutputStream(dest))
          .withStream { ZipOutputStream zos ->
            eachFileRecurse { f ->
              if (!f.isDirectory()) {
                zos.putNextEntry(new ZipEntry(f.getPath()))
                new FileInputStream(f).withStream { s ->
                    zos << s
                    zos.closeEntry()
                }
              }
            }
          }
  }

  static {
    File.metaClass.mixin(Zipper)
  }

}

在清单1中,我创建了一个Zipper类,其中包含新的zip()方法和将该方法添加到现有File类的连线。 zip()方法的(不起眼的)Groovy代码以递归方式创建一个ZIP文件。 清单的最后一部分通过使用静态初始化程序将新方法连接到现有的File类中。 与Java语言一样,静态类初始化程序在类加载时运行。 静态初始化程序是扩充代码的理想位置,因为可以确保初始化程序在依赖于增强功能的任何代码之前运行。 在清单1中mixin()方法将zip()方法添加到File

在“ 没有继承的扩展,第1部分 ”中,我介绍了两种Groovy机制ExpandoMetaClass和类别类,可用于在现有类上添加,更改或删除方法。 使用mixin()添加方法与使用ExpandoMetaClass或类别类添加方法具有相同的最终结果,但是实现方式有所不同。 考虑清单2中的mixin示例:

清单2.操作继承层次结构的Mixins
import groovy.transform.ToString

class DebugInfo {
  def getWhoAmI() {
    println "${this.class} <- ${super.class.name} 
    <<-- ${this.getClass().getSuperclass().name}"
  }
}

@ToString class Person {
  def name, age
}

@ToString class Employee extends Person {
  def id, role
}

@ToString class Manager extends Employee {
  def suiteNo
}


Person.mixin(DebugInfo)

def p = new Person(name: "Pete", age: 33)
def e = new Employee(name: "Fred", age: 25, id:"FRE", role:"Manager")
def m = new Manager(name: "Burns", id: "001", suiteNo: "1A")

p.whoAmI
e.whoAmI
m.whoAmI

在清单2中,我创建了一个名为DebugInfo的类,其中包含一个getWhoAmI属性定义。 在此属性中,我打印出该类的一些详细信息,例如当前类以及supergetClass().getSuperClass()属性对父项的观点。 接下来,我创建一个简单的类层次结构,该层次结构由PersonEmployeeManager

然后,我将DebugInfo类混合到Person类中,该类位于层次结构的顶部。 因为whoAmI属性存在于Person ,所以它也存在于其子类中。

在输出中,您可以看到(并且可能会惊讶地看到) DebugInfo类将自身暗示到继承层次结构中:

class Person <- DebugInfo <<-- java.lang.Object
class Employee <- DebugInfo <<-- Person
class Manager <- DebugInfo <<-- Employee

Mixin方法必须适合Groovy已经很复杂的关系才能进行方法解析。 清单2的父类的不同返回值反映了这些关系。 方法解析的详细信息超出了本文的范围。 但是要警惕在mixin方法中依赖thissuper (以各种形式)的值。

使用类别类或ExpandoMetaClass不会影响继承,因为您可以对类进行更改,而不是混入不同的新行为。 缺点是无法将这些更改识别为明显的分类工件。 如果我使用类别类或ExpandoMetaClass将相同的三个方法添加到多个类中,则没有特定的代码工件(例如接口或类签名)标识现在存在的通用性。 mixin的优点是Groovy会将使用mixin作为类别的所有内容都对待。

类别类的实现烦恼之一是严格的类结构。 您必须使用所有静态方法(每个方法至少接受一个参数)来表示正在扩充的类型。 当消除此类样板代码时,元编程最有用。 @Mixin批注的出现使创建类别并将其混合到类中变得更加容易。 清单3(Groovy文档的摘录)说明了类别和mixin之间的协同作用:

清单3.组合类别和混合
interface Vehicle {
    String getName()
}

@Category(Vehicle) class Flying {
    def fly() { "I'm the ${name} and I fly!" }
}

@Category(Vehicle) class Diving {
    def dive() { "I'm the ${name} and I dive!" }
}

@Mixin([Diving, Flying])
class JamesBondVehicle implements Vehicle {
    String getName() { "James Bond's vehicle" }
}

assert new JamesBondVehicle().fly() ==
       "I'm the James Bond's vehicle and I fly!"
assert new JamesBondVehicle().dive() ==
       "I'm the James Bond's vehicle and I dive!"

在清单3中,我创建了一个简单的Vehicle接口和两个类别类( FlyingDiving )。 @Category注释符合样板代码要求。 定义类别后,将它们混合到JamesBondVehicle ,从而附加两种行为。

Groovy中的类ExpandoMetaClass和mixins的交集是激进的语言发展的必然结果。 三种技术有明显的重叠,但每种技术都有其各自的最佳表现。 如果从头开始对Groovy进行重新设计,那么作者可能会将这三种技术的许多功能整合为一个机制。

斯卡拉特质

Scala通过traits实现代码重用, traits是类似于mixin的核心语言功能。 Scala的特点是有状态的-它们可以同时包含方法和字段-和他们玩相同instanceof角色界面在Java语言中播放。 特质和混合素都解决了许多相同的问题,但特质得到了更多语言严格性的支持。

在“ Groovy,Scala和Clojure的共同点,第1部分 ”中,我使用了复数类来说明Scala中的运算符重载。 我没有在该类中实现布尔比较运算符,因为Scala的内置Ordered特性使实现变得微不足道。 清单4显示了一个改进的复数类,它利用了Ordered特性:

清单4.可比复数
final class Complex(val real: Int, val imaginary: Int) extends Ordered[Complex] {
  require (real != 0 || imaginary != 0)

  def +(operand: Complex) =
      new Complex(real + operand.real, imaginary + operand.imaginary)

  def +(operand: Int) =
    new Complex(real + operand, imaginary)

  def -(operand: Complex) =
    new Complex(real - operand.real, imaginary - operand.imaginary)

  def -(operand: Int) =
    new Complex(real - operand, imaginary)


  def *(operand: Complex) =
      new Complex(real * operand.real - imaginary * operand.imaginary,
          real * operand.imaginary + imaginary * operand.real)

  override def toString() =
      real + (if (imaginary < 0) "" else "+") + imaginary + "i"

  override def equals(that: Any) = that match {
    case other : Complex => (real == other.real) && (imaginary == other.imaginary)
    case _ => false
  }

  override def hashCode(): Int =
    41 * ((41 + real) + imaginary)

  def compare(that: Complex) : Int = {
    def myMagnitude = Math.sqrt(this.real ^ 2 + this.imaginary ^ 2)
    def thatMagnitude = Math.sqrt(that.real ^ 2 + that.imaginary ^ 2)
    (myMagnitude - thatMagnitude).round.toInt
  }
}

我没有实现清单4中的><<=>=运算符,但是我可以在清单5所示的复数实例上调用它们:

清单5.测试比较
class ComplexTest extends FunSuite {

  test("comparison") {
    assert(new Complex(1, 2) >= new Complex(3, 4))
    assert(new Complex(1, 1) < new Complex(2,2))
    assert(new Complex(-10, -10) > new Complex(1, 1))
    assert(new Complex(1, 2) >= new Complex(1, 2))
    assert(new Complex(1, 2) <= new Complex(1, 2))
  }

}

没有用于比较复数的数学定义方法,因此在清单4中,我使用了一种公认的算法来比较数的大小。 我使用Ordered[Complex]特征extend了类定义,该特征混合了参数化类的布尔运算符。 为了使特征发挥作用,注入的运算符必须比较两个复数,这是compare()方法的目的。 如果您尝试extend Ordered特性但不提供所需的方法,则编译器消息会通知您,由于缺少必需的方法,您的类必须被声明为abstract

特性在Scala中有两个明确定义的角色:丰富接口和执行可堆叠的修改 。

丰富的界面

当Java开发人员设计接口时,他们面临着取决于便利性的难题:您应该创建一个包含许多方法的富接口,还是创建仅包含几个方法的瘦接口? 丰富的界面对其用户来说更为方便,因为它提供了广泛的方法选项,但是大量的方法使该界面更难以实现。 精简接口存在相反的问题。

性状解决了富人与瘦者之间的难题。 您可以在瘦界面中创建核心功能,然后使用特征对其进行扩充以提供更丰富的功能。 例如,在Scala中,“ Set特征实现集合的共享功能,而您选择的子特征( mutableimmutable )确定集合是否可变。

可堆叠修改

在Scala中,特征的另一个常见用法是可堆叠的修改 。 使用特征,您可以更改现有方法并添加新方法,而super提供了将链返回到先前特征的实现的访问权限。

清单6展示了带有数字队列的可堆叠修改:

清单6.构建可堆叠的修改
abstract class IntQueue {
  def get(): Int
  def put(x: Int)
}

import scala.collection.mutable.ArrayBuffer

class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) { buf += x }
}

trait Squaring extends IntQueue {
  abstract override def put(x: Int) { super.put(x * x) }
}

在清单6中,我创建了一个简单的IntQueue类。 然后,我构建一个包含ArrayBuffer的可变版本。 Squaring特征扩展任何IntQueue并在将值插入队列时自动平方值。 对Squaring特征中的super的调用提供了对堆栈中先前特征的访问。 只要第一个被覆盖的方法(第一个方法除外)都调用super ,这些修改就会彼此堆叠,如清单7所示:

清单7.构建堆叠的实例
object Test {
  def main(args: Array[String]) {
    val queue = (new BasicIntQueue with Squaring)
    queue.put(10)
    queue.put(20)
    println(queue.get()) // 100
    println(queue.get()) // 400
  }
}

清单6super的使用说明了特性和mixin之间的重要区别。 由于在原始类创建之后(从字面上看)将它们混合在一起,因此,Mixins必须解决类层次结构中当前位置的潜在歧义。 特性在创建类时线性化; 编译器毫无疑问地解决了什么是super 。 复杂,严格定义的规则(不在本文的讨论范围内)控制着Scala中线性化的工作方式。 特性还解决了Scala的钻石问题。 当Scala跟踪方法的来源和解决方案时,就不会有任何歧义,因为该语言定义了明确的规则来处理解决方案。

结论

在本期中,我探讨了混合(在Groovy中)和特征(在Scala中)之间的异同。 Mixins和trait提供许多相似的功能,但是实现细节在说明不同语言哲学的重要方式上有所不同。 在Groovy中,mixins作为注释存在,并使用AST转换提供的强大的元编程功能。 Mixins,类别类和ExpandoMetaClass在功能上都有重叠,但有细微(但很重要)的差异。 Scala中的诸如Ordered构成了一种核心语言功能,Scala中的许多内置功能都依赖于此。

在下一期中,我将介绍Java.next语言中的currying和部分应用程序。


翻译自: https://www.ibm.com/developerworks/java/library/j-jn8/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值