计算机程序的思维逻辑,漫谈系列之接口

作者:新闻中心

使用场景

  • switch 语句使用 where 配合 if let 限定某些条件。
 let names = ["张三", "李四", "吴奇隆", "周六", "赵七", "吴青峰", "吴镇宇"] names.forEach { switch $0 { case let name where name.hasPrefix: print是吴家大院的人") default: print("hello, } }
  • 配合 for 循环来添加限定条件使代码更加易读。
 let names = ["张三", "李四", "吴奇隆", "周六", "赵七", "吴青峰", "吴镇宇"] for name in names where name == "吴奇隆" { print大哥") }
  • 接口扩展使用 where 进行限定(如果希望接口扩展的默认实现只在某些限定的条件下才适用)。例如这些系统的接口扩展:
extension ContiguousArray where Element : BidirectionalCollection { public func joined() -> FlattenBidirectionalCollection<ContiguousArray<Element>>}extension ContiguousArray where Element : Sequence { public func joined<Separator>(separator: Separator) -> JoinedSequence<ContiguousArray<Element>> where Separator : Sequence, Separator.Element == Element.Element}

这样如果一个 Array 中的元素是不可比较的那么 sort 方法也就不适用了。

 var sortArr: [Int] = [99, 77, 12, 4, 23, 59, 8] var unsortArr: [Any] = ["hello", 5, ["name":"Jack"]] sortArr = sortArr.sorted() unsortArr = unsortArr.sorted() //会提示报错

Tips《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。在这里第一时间翻译成中文版。供大家学习分享之用。书中的源代码地址: 9 API中的,所以JDK 最好下载 JDK 9以上的版本。但是Java 9 只是一个过渡版本,所以建议安装JDK 10。

Kotlin 君和 Swift 君在一个团队一起开发已经很久了,由于平台的差异性,他们经常会进行一些技术上的交流,「Kotlin vs. Swift」系列就是他们在互相切磋时的语录。内容会由简及深,慢慢深入。

之前章节中我们多次提到过泛型这个概念,从本节开始,我们就来详细讨论Java中的泛型,虽然泛型的基本思维和概念是比较简单的,但它有一些非常令人费解的语法、细节、以及局限性,内容比较多。

新葡京8455 1Effective Java, Third Edition

Swift:

所以我们分为三节,逐步来讨论,本节我们主要来介绍泛型的基本概念和原理,下节我们重点讨论令人费解的通配符,最后一节,我们讨论一些细节和泛型的局限性。

在几乎所有方面,枚举类型都优于本书第一版中描述的类型安全模式[Bloch01]。 从表面上看,一个例外涉及可扩展性,这在原始模式下是可能的,但不受语言结构支持。 换句话说,使用该模式,有可能使一个枚举类型扩展为另一个; 使用语言功能特性,它不能这样做。 这不是偶然的。 大多数情况下,枚举的可扩展性是一个糟糕的主意。 令人困惑的是,扩展类型的元素是基类型的实例,反之亦然。 枚举基本类型及其扩展的所有元素没有好的方法。 最后,可扩展性会使设计和实现的很多方面复杂化。

Hi, Kotlin 君, Swift 4 发布了,我们今天就基于 Swift 4 的新语法来讨论一下接口吧?

后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序。而容器类是基于泛型的,不理解泛型,我们就难以深刻理解容器类。那,泛型到底是什么呢?

也就是说,对于可扩展枚举类型至少有一个有说服力的用例,这就是操作码( operation codes),也称为opcodes。 操作码是枚举类型,其元素表示某些机器上的操作,例如条目 34中的Operation类型,它表示简单计算器上的功能。 有时需要让API的用户提供他们自己的操作,从而有效地扩展API提供的操作集。

Kotlin:

什么是泛型?

幸运的是,使用枚举类型有一个很好的方法来实现这种效果。基本思想是利用枚举类型可以通过为opcode类型定义一个接口,并实现任意接口。例如,这里是来自条目 34的Operation类型的可扩展版本:

好啊,接口对我们开发来说是个很重要的概念。设计模式中要求我们写代码要遵循依赖倒置原则,就是程序要依赖于抽象接口,不要依赖于具体实现,也就是要求我们要面向接口编程。

之前我们一直强调数据类型的概念,Java有8种基本类型,可以定义类,类相当于自定义数据类型,类之间还可以有组合和继承。不过,在第19节,我们介绍了接口,其中提到,其实,很多时候,我们关心的不是类型,而是能力,针对接口和能力编程,不仅可以复用代码,还可以降低耦合,提高灵活性。

// Emulated extensible enum using an interfacepublic interface Operation { double apply(double x, double y);}public enum BasicOperation implements Operation { PLUS { public double apply(double x, double y) { return x   y; } }, MINUS { public double apply(double x, double y) { return x - y; } }, TIMES { public double apply(double x, double y) { return x * y; } }, DIVIDE { public double apply(double x, double y) { return x / y; } }; private final String symbol; BasicOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}

Swift:

泛型将接口的概念进一步延伸,"泛型"字面意思就是广泛的类型,类、接口和方法代码可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码,可以用于多种数据类型,这样,不仅可以复用代码,降低耦合,同时,还可以提高代码的可读性和安全性。

虽然枚举类型(BasicOperation)不可扩展,但接口类型(Operation)是可以扩展的,并且它是用于表示API中的操作的接口类型。 你可以定义另一个实现此接口的枚举类型,并使用此新类型的实例来代替基本类型。 例如,假设想要定义前面所示的操作类型的扩展,包括指数运算和余数运算。 你所要做的就是编写一个实现Operation接口的枚举类型:

是的,在 Swift 中,接口被称为协议(即 Protocol ), 苹果大大强化了 Protocol 在这门语言中的地位,整个 Swift 标准库也是基于 Protocol 来设计的,可以说 Swift 是一门面向 protocol 编程的语言。

这么说可能比较抽象,接下来,我们通过一些例子逐步来说明。在Java中,类、接口、方法都可以是泛型的,我们先来看泛型类。

// Emulated extension enumpublic enum ExtendedOperation implements Operation { EXP { public double apply(double x, double y) { return Math.pow; } }, REMAINDER { public double apply(double x, double y) { return x % y; } }; private final String symbol; ExtendedOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}

Kotlin:

一个简单泛型类

只要API编写为接口类型(Operation),而不是实现(BasicOperation),现在就可以在任何可以使用基本操作的地方使用新操作。请注意,不必在枚举中声明apply抽象方法,就像您在具有实例特定方法实现的非扩展枚举中所做的那样。 这是因为抽象方法(apply)是接口(Operation)的成员。

听起来好流比,那来说说你们是怎么定义接口的?

我们通过一个简单的例子来说明泛型类的基本概念、实现原理和好处。

不仅可以在任何需要“基本枚举”的地方传递“扩展枚举”的单个实例,而且还可以传入整个扩展枚举类型,并使用其元素。 例如,这里是第163页上的一个测试程序版本,它执行之前定义的所有扩展操作:

Swift:

基本概念

public static void main(String[] args) { double x = Double.parseDouble; double y = Double.parseDouble; test(ExtendedOperation.class, x, y);}private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) { for (Operation op : opEnumType.getEnumConstants System.out.printf("%f %s %f = %f%n", x, op, y, op.apply;}

我们用 Protocol 关键字来定义接口:

我们直接来看代码:

注意,扩展的操作类型的类字面文字(ExtendedOperation.class)从main方法里传递给了test方法,用来描述扩展操作的集合。这个类的字面文字用作限定的类型令牌。opEnumType参数中复杂的声明(<T extends Enum<T> & Operation> Class<T>)确保了Class对象既是枚举又是Operation的子类,这正是遍历元素和执行每个元素相关联的操作时所需要的。

protocol SomeProtocol { func f()}

新葡京8455 2

第二种方式是传递一个Collection<? extends Operation>,这是一个限定通配符类型,而不是传递了一个class对象:

你们呢?

public class Pair<T> {

    T first;
    T second;

    public Pair(T first, T second){
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }
}
public static void main(String[] args) { double x = Double.parseDouble; double y = Double.parseDouble; test(Arrays.asList(ExtendedOperation.values;}private static void test(Collection<? extends Operation> opSet, double x, double y) { for (Operation op : opSet) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply;}

Kotlin:

新葡京8455 3

生成的代码稍微不那么复杂,test方法灵活一点:它允许调用者将多个实现类型的操作组合在一起。另一方面,也放弃了在指定操作上使用EnumSet和EnumMap的能力。

我们同 Java 一样,用 interface 关键字来定义接口:

Pair就是一个泛型类,与普通类的区别,体现在:

上面的两个程序在运行命令行输入参数4和2时生成以下输出:

interface MyInterface { fun f()}
  1. 类名后面多了一个<T>
  2. first和second的类型都是T 
4.000000 ^ 2.000000 = 16.0000004.000000 % 2.000000 = 0.000000

Swift:

T是什么呢?T表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。

使用接口来模拟可扩展枚举的一个小缺点是,实现不能从一个枚举类型继承到另一个枚举类型。如果实现代码不依赖于任何状态,则可以使用默认实现将其放置在接口中。在我们的Operation示例中,存储和检索与操作关联的符号的逻辑必须在BasicOperationExtendedOperation中重复。在这种情况下,这并不重要,因为很少的代码是冗余的。如果有更多的共享功能,可以将其封装在辅助类或静态辅助方法中,以消除代码冗余。

嗯,看起来就是关键字不一样。你们怎么实现接口呢?

怎么用这个泛型类,并传递类型参数呢?看代码:

该条目中描述的模式在Java类库中有所使用。例如,java.nio.file.LinkOption枚举类型实现了CopyOptionOpenOption接口。

Kotlin:

Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integer max = minmax.getSecond();

总之,虽然不能编写可扩展的枚举类型,但是你可以编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。这允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的,那么在任何使用基本枚举类型实例的地方,都可以使用这些枚举类型实例。

一个类要实现某个接口,需要在类型名称后加上协议名称,中间以冒号(:)分隔:

Pair<Integer>,这里Integer就是传递的实际类型参数。

class MyClass: MyInterface { override fun f() { // 具体实现 }}

Pair类的代码和它处理的数据类型不是绑定的,具体类型可以变化。上面是Integer,也可以是String,比如:

一个类或者对象可以实现一个或多个接口。实现多个接口时,各接口之间用逗号(,)分隔.

Pair<String> kv = new Pair<String>("name","老马");

Swift:

类型参数可以有多个,Pair类中的first和second可以是不同的类型,多个类型之间以逗号分隔,来看改进后的Pair类定义:

我们也是一样的,只是我们不需要写 override 关键字,只有当子类复写父类的方法或计算属性时才需要用 override 修饰。另外,我们还可以通过扩展类型来实现协议:

新葡京8455 4

class MyClass { //...类的定义}extension MyClass: SomeProtocol { func f() { // 具体实现 }}
public class Pair<U, V> {

    U first;
    V second;

    public Pair(U first, V second){
        this.first = first;
        this.second = second;
    }

    public U getFirst() {
        return first;
    }

    public V getSecond() {
        return second;
    }
}

Kotlin:

新葡京8455 5

本文由新葡京8455发布,转载请注明来源

关键词: