术语:我将把语言构造interface
称为interface,并将类型或对象的接口称为Surface(因为缺少更好的术语)。
松散耦合可以通过使对象依赖于抽象而不是具体类型来实现。
正确。
这允许松散耦合,其主要原因有两个:1-抽象比具体类型更不可能更改,这意味着从属代码不易被破坏。2-在运行时可以使用不同的具体类型,因为它们都适合抽象。以后也可以添加新的具体类型,而无需更改现有的从属代码。
不太正确。当前的语言通常不会期望抽象会改变(尽管有一些设计模式可以处理这种情况)。将细节与一般事物分开是抽象。这通常是通过某些抽象层来完成的。可以将这一层更改为其他一些细节,而不会破坏基于此抽象的代码-实现了松散耦合。非OOP示例:sort
例程可能从版本1中的Quicksort更改为版本2中的Tim Sort。因此,仅依赖于要排序的结果(即基于sort
抽象的代码)与实际的排序实现脱钩。
我上面所说的表面是抽象的一般部分。现在在OOP中,一个对象有时必须支持多种抽象。一个不很理想的例子:Java java.util.LinkedList
支持List
关于“有序,可索引集合”抽象的Queue
接口,并支持(大致而言)关于“ FIFO”抽象的接口。
一个对象如何支持多个抽象?
C ++没有接口,但是它具有多个继承,虚拟方法和抽象类。然后可以将抽象定义为声明但不定义虚拟方法的抽象类(即无法立即实例化的类)。然后,实现抽象细节的类可以从该抽象类继承并实现所需的虚拟方法。
这里的问题是多重继承会导致菱形问题,即在类中搜索方法实现的顺序(MRO:方法解析顺序)会导致“矛盾”。对此有两种回应:
定义一个合理的订单并拒绝那些无法合理线性化的订单。该C3 MRO是还算懂事和行之有效的。它于1996年出版。
采取简单的方法,并始终拒绝多重继承。
Java选择了后者,并选择了单一行为继承。但是,我们仍然需要对象支持多种抽象的能力。因此,必须使用不支持方法定义,仅支持声明的接口。
结果是MRO很明显(只需按顺序查看每个超类),并且我们的对象可以具有多个表面以进行任意数量的抽象。
事实证明,这是相当不令人满意的,因为很多行为是表面的一部分。考虑一个Comparable
接口:
interface Comparable<T> {
public int cmp(T that);
public boolean lt(T that); // less than
public boolean le(T that); // less than or equal
public boolean eq(T that); // equal
public boolean ne(T that); // not equal
public boolean ge(T that); // greater than or equal
public boolean gt(T that); // greater than
}
这是非常用户友好的(具有许多便捷方法的不错的API),但是实现起来很繁琐。我们希望接口仅包含cmp
,并根据该所需方法自动实现其他方法。Mixins,但更重要的是,Traits [ 1 ],[ 2 ]解决了这个问题,而没有陷入多重继承的陷阱。
这是通过定义特征构成来完成的,从而使特征实际上不会最终参与MRO-而是将定义的方法组合到实现类中。
该Comparable
接口可以在Scala中表示为
trait Comparable[T] {
def cmp(that: T): Int
def lt(that: T): Boolean = this.cmp(that) < 0
def le(that: T): Boolean = this.cmp(that) <= 0
...
}
当一个类使用该特性时,其他方法将添加到该类定义中:
// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
override def cmp(that: Inty) = this.x - that.x
// lt etc. get added automatically
}
所以Inty(4) cmp Inty(6)
会-2
和Inty(4) lt Inty(6)
会true
。
许多语言都支持特征,任何具有“超对象协议(MOP)”的语言都可以添加特征。最近的Java 8更新添加了类似于trait的默认方法(接口中的方法可以具有后备实现,因此对于实现类来实现这些方法是可选的)。
不幸的是,特征是一个相当新的发明(2002年),因此在较大的主流语言中很少见。