图片 1

在Java 8下更好地利用枚举

在我们的云使用分析API中,返回了格式化过的分析数据(这里指生成分析图)。最近,我们添加了一个特性,允许用户选择时间段(最开始只可以按天选择)。问题是,代码中每天中的时间段部分高度耦合了……

面向对象篇


C#与Java的比较

 

摘自:

 

 

.NET(C#)

 

Java

 

基本类型

 

基本类型

C#中有无符号数,Java没有。

C#中有值类型,且可自己定义值类型的结构体(struct)。
Java中的基本类型(或叫基元类型)即为值类型,但Java没有结构体,所以不能自定义值类型。
C#中的值类型(包括所有基本类型)间接继承自Object,有自己的方法可以调用;Java中的值类型(即基本类型)不继承自Object,只是简单的数据,没有方法可以调用。

C#中int等同于System.Int32,是值类型;bool等同于System.Boolean;等。
Java中int是基本类型,是值类型,而Integer是引用类型,Integer是int的包装器,int自身没有方法,Integer有一些方法;int与Integer之间可隐式转换(导致装箱和拆箱),但当Integer值为null的时候会在运行时抛出异常。boolean等类似。

Java中的int与Integer的对应在C#中类似int和Nullable<int>的对应,它们的后者都是前者的包装,且后者可以等于null。但Nullable<int>实际上仍然是值类型的(所以仍然很轻量级),所以从内存上讲C#中int和Object的对应更接近Java的对应一些。C#中Nullable<int>到int的转换必须显式进行,因为Nullable<int>中的值为null时会引发运行时异常。
其他基本类型与之类似。

 

 

委托,事件

 

[无]

C#中的委托可以认为是方法的类型化,于是可以将方法放在变量里传递。事件是对委托做了一层包装。
Java通过接口来实现C#中委托和事件的功能,可通过匿名类来达到C#中匿名委托的作用(同样也能实现闭包的功能)。
另,C#中也有匿名类,但C#中的匿名类只有数据没有方法。

 

 

非托管

 

[无]

C#可以有非托管代码,可以有指针等。Java没有。

 

 

索引器

 

[无]

C#有索引器,可方便容器类实现类似数组的效果。Java没有,Java的容器基本上用put,get,set等方法达到同样效果。

 

 

属性

 

[无]

C#的属性通过在内部定义get/set方法,使外部使用时像是在使用变量字段,但其实是在调用get/set方法,以达到透明的封装数据的目的。
Java没有属性的概念。Java通过约定为字段XX添加getXX,setXX方法达到同样的目的。

 

 

预编译指令

 

[无]

C#有预编译指令可方便调试,且有ConditionalAttribute来描述方法。Java没有。

 

 

操作符重载

 

[无]

C#可重载操作符。Java没有。

Java自己重载了String的+和+=,但没有重载==,这是我这段时间犯的最多的错误。C#中String的==是比较值相等,Java中==是Object的默认行为:比较引用相等,要比较值相等得用equals方法。(这么多年编程以来,我似乎从来没有遇到过要比较两个字符串变量的引用相等。对于比较值相等来讲,==符号比equals方法调用看上去优雅得多,况且方法调用还得注意空指针的情况)

 

 

内部类

 

内部类

Java的内部类可以直接访问外部类的实例成员。
C#的不行。C#的内部类等同于Java的静态内部类。

 

 

goto、switch

 

[goto]、switch

C#允许用goto。Java的goto是保留关键字,不能使用。但Java允许有标签,在有嵌套循环时可以在continue、break后面跟标签名。

C#的switch可以使用long、String;Java不可以。

Java的switch中的case子句在后面没有跟break的情况下直接跳到下一个case子句;
C#中只有在前一个case没有任何代码的情况下才允许不写break直接跳到下一个case,C#中可以通过goto跳转到另一case。

 

 

enum

 

enum

C#中的枚举是值类型,且其基于数值类型(默认基于int),可设置枚举项对应的数字,不能在其中添加方法等任何其他成员。
Java中的枚举是引用类型(Java除了基本类型外,任何类型都是引用类型),不是基于数值类型。除了不能继承外,它跟普通类差别不大,可以添加成员方法和成员变量等(当然也就可以重写toString方法)。

C#和Java的枚举都可以用于switch。

可以将C#的枚举作为数值看待而直接进行位运算,因此可以在一个变量中存储多个位标记。
Java的枚举跟数值没有直接关系,因此不能直接这么用。Java用EnumSet来存储枚举标志,不需要直接使用位运算,更远离底层。

 

 

override

 

@Override

C#能被重写的方法必须添加virtual关键字声明为虚方法,派生类重写子类方法时添加override关键字。
Java默认方法都可被重写,派生类和子类方法签名一样时被认为是重写。要声明不能被重写的方法需在方法前加final关键字。重写时可以在方法前添加标注(即C#中的定制特性)@Override,这样一旦此方法找不到被重写的方法时编译器会报错,以防止拼写错误。

 

 

定制特性

 

标注

C#用中括号[]将定制特性括起来。Java用@打头,后面跟定制特性的名字。

 

 

泛型

 

泛型

Java中泛型实现使用的擦除机制,为类型参数传入类型并不导致新类型出现,即传入了类型参数后在运行时仍然完全不知道类型参数的具体类型,它的目的是为了兼容非泛型(所以可以在泛型和非泛型之间隐式转换,会有编译警告但不会有编译错误,这当然其实并不安全);这同时衍生了一系列问题:不能定义泛型类型参数的数组如T[],不能通过new
T()的方式实例化泛型,等。
Java的泛型不支持值类型(使用的话会被自动包装成引用类型)。

 

C#的泛型在类型参数传入类型后会产生一个新类型(虽然CLR的优化机制会使引用类型共享同样的代码),可以在运行时得到类型参数的类型信息。可以定义泛型数组,可以添加约束使其可以new。C#的泛型可以使用值类型(不会被装箱)。

对于Java的泛型,简单的讲,它的好处只在编译时,运行时没有任何泛型的意义。当你在使用已有的泛型类时,这通常能满足要求;但如果你要自己定义泛型类,那你得知道它有多少你觉得它应该可以但事实上不可以的事情。

 

 

 

参数引用传递

 

[无]

C#允许使用关键字out,ref显式指定参数传递方式为引用传递。
Java只有值传递。

 

 

@字符串

 

[无]

C#在写字符串时可以在引号前加个@符号来取消/的转义作用。
Java没有。

 

 

??

 

[无]

C#的??二元操作符当前面的表达式不为null时返回前面表达式的值,前面表达式为null时返回后面表达式的值。
Java没有。

 

 

using

 

import

C#可以用using为命名空间或类指定别名。(using还有Dispose的使用方式,与命名空间无关)
Java的import可以引入类或包(即C#的命名空间),static
import可以引入类的成员。

 

 

初始化

 

初始化

C#调用基类构造函数的语法为:
SubClass() : base() { }
Java调用基类构造函数的语法为:
SubClass(){
   super();
}
C#和Java都可以用类似的语法调用同一个类的其他构造函数。(分别将base和super换成this)

Java有代码块概念,会在构造函数之前执行(基类的构造函数之后)。

在成员变量声明时赋值,Java允许其赋值表达式中引用前面声明的另一个变量,如:
private int x = 1;
private int y = x + 10;
这里变量y的赋值语句有变量x。
C#不允许这样做。

 

 

interface

 

interface

Java的接口内允许有内部类、静态字段等。
C#不允许。

 

 

readonly,const

 

final

C#的const是绝对的常量,必须在声明语句中同时赋值,只有数值、枚举和String可以声明为const。const的值会内联到各个使用的地方。
C#的readonly表示变量在构造函数执行完之后是不能再变化的。它只约束变量本身,而无法约束变量引用(如果它是引用类型或者有成员是引用类型)的对象。

Java中的final(在约束变量的时候)看上去更像readonly。
但C#的readonly和const有个区别,readonly的int是不能作为switch的case语句的,const的可以。
而Java的final则是:有时候可以有时候不可以—-编译时可以得到明确值的可以,反之不可以。如:
final int x = 1;   // 这个可以
final int y = new Random().nextInt();   // 这个不可以
那么可以理解为:编译时能得到明确值的时候,final等同于C#的const;编译时无法得到明确值的时候,final等同于C#的readonly。

 

 

[无]

 

throws

Java在可能抛出异常时,除了RuntimeException(包括派生类),都要么捕获,要么在方法声明中用throws关键字声明出来表示继续抛出。
C#没有采用这种强制处理机制。

 

 

 

功能相同但语法有差异的

namespace == package (Java的package对文件结构也有要求;C#没有)

internal == [默认]
(Java中不写访问修饰符即表示访问权限是package;C#默认是private。C#的internal
protected在Java中没有。)

lock == synchronized
(Java中synchronized可以修饰方法,C#可以用定制特性[MethodImplAttribute(MethodImplOptions.Synchronized)]达到同样效果)

 : == extends,implements

base == super

is == instanceof (C#有as,Java没有)

typeof == .class

[SerializableAttribute]定制特性 == Serializable接口

[NonSerializedAttribute]定制特性 == transient

params == … (可变数目参数)

 

 

这个列表里,Java比C#更漂亮的地方基本上只有一处:枚举。Java的枚举更高层一些,更灵活。但内存代价比C#的枚举要高,这可能就是Android里仍然使用常量而不是枚举的原因吧。
所以就从这次比较来讲,C#几乎完胜Java,而C#的新特性像完美的类型推断、动态编程特性、Lambda表达式、LINQ等等这里都没有列入比较。

当然,.NET和Java两大体系的比较,语言只是一个方面,还有平台、IDE、开源等其他很多方面,这里就不说了。

图片 1

Swift语言中的面向对象特性

在现代计算机语言中,面向对象是非常重要的特性,Swift语言也提供了面向对象的支持。而且在Swift语言中,不仅类具有面向对象特性,结构体和枚举也都具有面向对象特性。

例如,下面这段代码:

面向对象概念和基本特征

面向对象(OOP)是现代流行的程序设计方法,是一种主流的程序设计规范。其基本思想是使用对象、类、继承、封装、属性、方法等基本概念来进行程序设计。从现实世界中客观存在的事物出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。

**OOP的基本特征包括:封装性、继承性和多态性。

  • 封装性
    封装性就是尽可能隐蔽对象的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部发生联系。

  • 继承性
    一些特殊类能够具有一般类的全部属性和方法,这称做特殊类对一般类的继承。通常我们称一般类为父类(或基类),特殊类为子类(或派生类)。

  • 多态性
    对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以使同一个属性或方法在父类及其各个子类中具有不同的含义,这称为多态性。例如动物都有吃饭的方法,但是老鼠的吃饭方法和猫的吃饭方法是截然不同的。


private static List<DataPoint> createListWithZerosForTimeInterval(DateTime from,
    DateTime to,
    ImmutableSet<Metric<? extends Number>> metrics) {
    List<DataPoint> points = new ArrayList<>();
    for (int i = 0; i <= Days.daysBetween(from, to).getDays(); i++) {
        points.add(new DataPoint().withDatas(createDatasWithZeroValues(metrics))
            .withDayOfYear(from.withZone(DateTimeZone.UTC)
                .plusDays(i)
                .withTimeAtStartOfDay()));
    }
    return points;
}

Swift中的面向对象类型

面向对象,在不同的计算机语言中,其具体的体现也是不同的。在C++和Java等语言中通过类实现面向对象,在Swift语言中通过类和结构体(struct)实现面向对象,在Swift语言中,枚举(enum)也具有面向对象特性。结构体和枚举在其他语言中完全没有面向对象特性,Swift语言赋予了它们面向对象生命。

提示:由于OOP中的类在Swift语言中涵盖了枚举、类和结构体。为了防止与OOP中的类发生冲突,在此把Swift中的这3种类型称为“Swift面向对象类型”。

在面向对象中,将类创建对象的过程称为实例化,因此将对象称为实例,但是在Swift中,结构体和枚举的实例不称为“对象”,因为结构体和枚举并不是彻底的面向对象类型,而是包含了一些面向对象的特点。例如,在Swift中继承只发生在类上,结构体和枚举不能继承。

在Swift中,面向对象的概念还有:属性、方法、扩展和协议等,这些概念对于枚举、类和结构体等不同类型有可能不同。

注意:Days、Minutes、Hours、Weeks
和Months一样出现在代码的后面部分。这些代码来自Joda-Time
Java时间和日期API。甚至方法的名字都没有反应出(各自的功能)。这些名字牢牢的绑定到了days的概念上。

枚举

在C和Objective-C中,枚举用于管理一组相关常量集合,通过使用枚举可以提高程序的可读性,使代码更清晰,更易于维护。而在Swift中,枚举的作用已经不仅仅是定义一组常量、提高程序的可读性了,它还具有了面向对象特性。

枚举的语法格式:

enum 枚举名

{

枚举的定义

}

“枚举名”是该枚举类型的名称。它首先应该是有效的标识符,其次应该是遵守面向对象的命名规范。它应该是一个名称,如果采用英文单词命名,首字母应该大写,尽量用一个英文单词。这个命名规范也适用于类和结构体的命名。“枚举的定义”是枚举的核心,它由一组成员值和一组相关值组成。

成员值

在枚举类型中定义一组成员,与C和Objective-C中枚举的主要作用是一样的,不同的是,在C和Objective-C中成员值是整数类型,因此在C和Objective-C中枚举类型就是整数类型。

而在Swift中,枚举的成员值默认情况下不是整数类型。

声明枚举示例:

print("--枚举示例---")

enum WeekDays {
    case Monday
    case Tuesday
    case Wednesday
    case Thursday
    case Friday
}

上述代码声明了WeekDays枚举,表示一周中的每个工作日,其中定义了5个成员值:Monday、Tuesday、Wdenesday、Thursday、Friday,这些成员值并不是整数类型。

在这些成员值前面还要加上case关键字,也可以将多个成员值放在同一行,用逗号隔开,如下:

enum WeekDays_ {
    case Monday, Tuesday, Wednesday, Thursday, Friday
}

看一个示例,代码如下:

print("---使用枚举示例----")

var day = WeekDays.Friday
day = WeekDays.Wednesday
day = .Monday

func writeGreeting(day : WeekDays) {
    switch day {
    case .Monday:
        print("星期一好!")
    case .Tuesday:
        print("星期二好!")
    case .Wednesday:
        print("星期三好!")
    case .Thursday:
        print("星期四好!")
    case .Friday:
        print("星期五好!")
    }
}

writeGreeting(day)
writeGreeting(WeekDays.Friday)

前三行代码均是给变量day赋值,可以采用完整的“枚举类型名.成员值”的形式,也可以省略枚举类型,采用“.成员值”的形式。这种省略形式能够访问的前提是,Swift能够根据上下文环境推断类型。

枚举类型与switch语句能够很好地配合使用,在switch语句中使用枚举类型可以没有default分支,这在使用其他类型时是不允许的。

需要注意,在switch中使用枚举类型时,switch语句中的case必须全面包含枚举中的所有成员,不能多也不能少,包括使用default的情况下,default也表示某个枚举成员。

使用default分支的代码:

print("---使用default分支---")

func writeGreeting_(day :WeekDays) {

    switch day {
    case .Monday:
        print("星期一好!")
    case .Tuesday:
        print("星期二好!")
    case .Wednesday:
        print("星期三好!")
    case .Thursday:
        print("星期四好!")
    default:
        print("星期五好!")
    }
}

在上面示例中,default表示的是Friday枚举成员,在这种情况下,Friday枚举成员的case分支不能再出现了。

原始值

为每个成员提供某种具体类型的默认值,可以为枚举类型提供原始值(raw
values)声明,这些原始值类型可以是:字符、字符串、整数和浮点数等。

原始值枚举的语法格式如下:

enum 枚举名 : 数据类型

{

case 成员名 = 默认值

}

在“枚举名”后面跟“:”和“数据类型”就可以声明原始值枚举的类型,然后在定义case成员的时候需要提供默认值。

以下代码是声明枚举示例:

print("---原始值---")

enum WeekDays__ : Int {
    case Monday             = 0
    case Tuesday            = 1
    case Wednesday          = 2
    case Thursday           = 3
    case Friday             = 4
}

声明的WeekDays枚举类型的原始值类型是Int,需要给每个成员赋值,只要是Int类型都可以,但是每个分支不能重复。还可以采用如下简便写法,只需要给第一个成员赋值即可,后面的成员值会依次加1。

print("--原始值--简便写法--")

enum WeekDays___ : Int {
    case Monday = 0, Tuesday, Wednesday, Thursday, Friday
}

完整示例代码:

print("---完整示例代码----")

print("---原始值---")

enum WeekDays : Int {
    case Monday             = 0
    case Tuesday            = 1
    case Wednesday          = 2
    case Thursday           = 3
    case Friday             = 4
}

var day = WeekDays.Friday
day = WeekDays.Wednesday

func writeGreeting(day : WeekDays) {
    switch day {
    case .Monday:
        print("星期一好!")
    case .Tuesday:
        print("星期二好!")
    case .Wednesday:
        print("星期三好!")
    case .Thursday:
        print("星期四好!")
    case .Friday:
        print("星期五好!")
    }
}

let friday = WeekDays.Friday.rawValue

let thursday = WeekDays(rawValue: 3)

if (WeekDays.Friday.rawValue == 4) {
    print("今天是星期五")
}

writeGreeting(day)
writeGreeting(WeekDays.Friday)

let friday =
WeekDays.Friday.rawValue该句代码是通过WeekDays.Friday的方法rawValue转换为原始值。虽然在定义的时候Friday被赋值为4,但是并不等于WeekDays.Friday就是整数4了,而是它的原始值为整数4,因此下面的比较是错误的。

if (WeekDays.Friday == 4) {
    print("今天是星期五")
}

.rawValue方法是将成员值转换为原始值,相反,WeekDays(rewValue :
3)方法是将原始值转换为成员值。

相关值

在Swift中除了定义一组成员值,还可以定义一组相关值(associated
values),它有点类似于C中的联合类型。

枚举类型的声明:

print("--相关值---")

enum Figure {
    case Rectangle(Int, Int)
    case Circle(Int)
}

枚举类型Figure(图形)有两个相关值:Rectangle(矩形)和Circle(圆形)。Rectangle和Circle是与Figure有关联的相关值,它们都是元组类型,对于一个特定的Figure实例,只能是其中一个相关值。

示例,代码如下:

func printFigure(figure :   Figure) {

    switch figure {
    case .Rectangle(let width, let height):
        print("矩形的宽:(width) 高:(height)")
    case .Circle(let radius) :
        print("圆形的半径:(radius)")
    }
}

var figure = Figure.Rectangle(1024, 768)
printFigure(figure)

figure = .Circle(600)
printFigure(figure)

如果某个相关值元组中字段类型一致,需要全部提取,则可以在相关值前面添加let或var。可以使用如下方式修改Rectangle分支:

switch figure {
case let .Rectangle(width, height):
    print("矩形的宽:(width) 高: (height)")
case .Circle(let radius):
    print("圆形的半径:(radius)")
}

我也尝试过使用不同时间段方式(比如月、周、小时)。但我看到了糟糕的switch/case鬼鬼祟祟地隐藏在代码里。

结构体与类

在面向过程的编程语言中,结构体用的比较多,但是面向对象之后,如在C++和Objective-C中,结构体已经很少使用了。这是应为结构体能够做的事情,类完全可以取而代之。

而Swift语言却非常重视结构体,把结构体作为实现面向对象的重要手段。Swift中的结构体与C++和Objective-C中的结构体有很大的差别,C++和Objective-C中的结构体只能定义一组相关的成员变量,而Swift中的结构体不仅可以定义成员变量(属性),还可以定义成员方法。因此,可以把结构体看做是一种轻量级的类。

Swift中的类和结构体非常类似,都具有定义和使用属性、方法、下标和构造器等面向对象特性,但是结构体不具有继承性,也不具备运行时强制类型转换、使用析构器和使用引用计等能力。

类和结构体定义

Swift中的类和结构体定义的语法也是非常相似的。我们可以使用class关键词定义类,使用struct关键词定义结构体,它们的语法格式如下:

class 类名 {

定义类的成员

}

struct 结构体名 {

定义结构体的成员

}

从语法格式上看,Swift中的类和结构体的定义更类似于Java语法,不需要像C++和Objective-C那样把接口部分和实现部分放到不同的文件中。
示例:

print("---类和结构体定义---")
class Employee {            //定义员工类
    var no : Int = 0        //定义员工编号属性
    var name : String = ""  //定义员工姓名属性
    var job : String?       //定义工作属性
    var salary : Double = 0 //定义薪资属性
    var dept : Department?  //定义所在部门属性
}

struct Department {         //定义部门结构体
    var no : Int = 0        //定义部门编号属性
    var name : String = ""  //定义部门名称属性
}

Employee是定义的类,Department是定义的结构体。
可以通过下列语句实例化:

var emp = Employee()
var dept = Department()

Employee()和Department()是调用它们的构造器实现实例化。

提示:实例化之后会开辟内存空间,emp和dept被称为“实例”,但只有类实例化的“实例”才能被称为“对象”。事实上,不仅仅是结构体和类可以实例化,枚举、函数类型和闭包开辟内存空间的过程也可以称为实例化,结果也可以叫“实例”,但不能叫“对象”。

再谈值类型和引用类型

数据类型可以分为:值类型和引用类型,这是由赋值或参数传递方式决定的。值类型就是在赋值或给函数传递参数时候,创建一个副本,把副本传递过去,这样在函数的调用过程中不会影响原始数据。引用类型就是在赋值或给函数传递参数的时候,把本身数据传递过去,这样在函数的调用过程中会影响原始数据。

在众多的数据类型中,只需记住:只有类是引用类型,其他类型全部是值类型。即便结构体与类非常相似,它也是值类型。值类型还包括整型、浮点型、布尔型、字符串、元组、集合和枚举。

Swift中的引用类型与Java中的引用类型是一样的,Java中的类也是引用类型。如果没有Java经验,可以把引用类型理解为C、C++和Objective-C语言中的指针类型,只不过不需要在引用类型变量或常量前面加星号(*)。

示例:

print("--值类型和引用类型--")

var dept = Department()
dept.no = 10
dept.name = "Sales"

var emp = Employee()
emp.no = 1000
emp.name = "Martin"
emp.job = "Salesman"
emp.salary = 1250
emp.dept = dept

func updateDept (inout dept : Department) {
    dept.name = "Research"
}

print("Department更新前:(dept.name)")
updateDept(&dept)

print("Department更新后:(dept.name)")

func updateEmp (emp : Employee) {
    emp.job = "Clerk"
}

print("Employee更新前:(emp.job)")
updateEmp(emp)

print("Employee更新后:(emp.job)")

对比上面代码,说明Employee类是引用类型,在调用的时候不用在变量前面添加&符号。

引用类型的比较

恒等于(===)和不恒等于(!===)关系运算符。===用于比较两个引用是否为同一个实例,!===则恰恰相反,它只能用于引用类型,也就是类的实例。

示例:

print("---引用类型的比较---")

var emp1 = Employee()       //1
emp1.no = 1000
emp1.name = "Martin"
emp1.job = "Salesman"
emp1.salary = 1250

var emp2 = Employee()       //2
emp2.no = 1000
emp2.name = "Martin"
emp2.job = "Salesman"
emp2.salary = 1250

if emp1 === emp2            //3
{
    print("emp1 === emp2")
}

if emp1 === emp1            //4
{
    print("emp1 === emp1")
}

var dept1 = Department()        //5

dept1.no = 10
dept1.name = "Sales"

var dept2 = Department()        //6
dept2.no = 10
dept2.name = "Sales"

if dept1 == dept2 //编译失败    //7
{
    print("dept1 === dept2")
}

你需要知道,switch/case=罪恶
已经深入我心了。在我大学期间的两段实习经历中就已经这么认为了。因此,我会不惜任何代价避免使用switch/case。这主要是因为它们违反了开放闭合原则。我深深地相信,遵循这个原则是写出面向对象代码的最好实践。我不是唯一一个这样想的,Robert
C. Martin曾经说:

上述代码第1行和第2行分别创建了emp1和emp2两个Employee实例。在代码第3行比较emp1和emp2两个引用是否为一个实例。可以看到,比较结果为False(第3行没有输出emp1

emp2),也就是emp1和emp2两个引用不是一个实例,即便是它们内容完全一样,结果也是False,而第4行的比较结果为True。如果对于第3行采用==比较,代码如下:

if emp1 == emp1
{
    print("emp1 === emp1")
}

答案是有编译错误。==比较要求两个实例的类型(类、结构体、枚举等)必须要在该类型中重写==运算符,定义相等规则。同样的错误也会发生在第7行代码。

第7行使用==比较dept1和dept2两个值是否相等,不仅不能比较,而且还会发生编译错误,这在上面已经解释过了。

如果采用恒等于===比较dept1和dept2,代码如下:

if dept1 === dept2
{
    print("dept1 === dept2")
}

这会发生编译错误。===不能比较值类型,而Department结构体是值类型,因此不能使用===比较。


在很多方面,开放闭合原则是面向对象设计的核心。遵循这个原则会从面向对象技术中收获巨大的好处,比如可重用性和可维护性[1](http://source.coveo.com/2014/11/18/taking-enums-to-the-next-level/#fn:footnote)

类型嵌套

Swift语言中的类、结构体和枚举可以进行嵌套,即在某一类型的{}内部定义类。这种类型嵌套在Java中称为内部类,在C#中称为嵌套类,它们的形式和设计目的都是类似的。

类型嵌套的优点是能够访问它外部的成员(包括方法、属性和其他的嵌套类型),嵌套还可以有多个层次。

示例:

print("---类型嵌套---")

class Employee {
    var no : Int = 0
    var name : String = ""
    var job : String = ""
    var salary : Double = 0
    var dept : Department = Department()

    var day : WeekDays = WeekDays.Friday

    struct Department {
        var no : Int = 10
        var name : String = "SALES"
    }

    enum WeekDays {
        case Monday
        case Tuesday
        case Wednesday
        case Thursday
        case Friday

        struct Day {
            static var message : String = "Today is ..."
        }
    }

}

var emp = Employee()
print(emp.dept.name)
print(emp.day)

let friday = Employee.WeekDays.Friday
if emp.day == friday {
    print("相等")
}
print(Employee.WeekDays.Day.message)

类型嵌套便于我们访问外部类的成员,但它会使程序结构变得不清楚,使程序的可读性变差。


我告诉自己:“我们使用Java8或许可以发现一些新的特性来避免swtich/case的危险场面出现”。使用Java8的新
functions(不是那么新,不过你知道我的意思)。我决定使用枚举代表不同的可得到时间段。

可选类型与可选链

可选类型

有时使用一个变量或常量,它保存的值可能有也可能没有。示例代码:

print("---可选类型---")

func divide(n1 : Int, n2 : Int) -> Double? {
    if n2 == 0 {
        return nil
    }

    return Double(n1) / Double(n2)

}
let result : Double? = divide(100, n2: 200)

可选绑定

可选类型可以用于判断,如下代码:

print("---可选绑定---")

if let result2 : Double? = divide(100, n2: 0) {
    print("Success.")
} else {
    print("failure.")
}

输出结果为failure.

这种可选类型在if或while语句中赋值并进行判断的写法,叫做可选绑定。

强制拆封

如果我们能确定可选类型一定有值,那么在读取它的时候,可以在可选类型的后面加一个感叹号(!)来获取该值。这种感叹号的表示方式称为可选值的强制拆封(forced
unwrapping)。如下代码:

print("---强制拆封---")

let result1 : Double? = divide(100, n2: 200)
print(result1)

print(result1!)//语句中的result1就进行了强制拆封

隐式拆封

为了能够方便地访问可选类型,可以将可选类型后面的问号(?)换成感叹号(!),这种可选类型在拆封时变量或常量后面不加感叹号(!)的表示方式成为隐式拆封。如下代码:

print("--隐私拆封---")

let result3 : Double! = divide(100, n2: 200)
print(result3)

在变量或常量声明的时候,数据类型Double后面跟的是感叹号(!)而不是问号(?),在拆封的时候,变量或常量后面不用加感叹号(!),这就是隐式拆封。隐式拆封的变量或常量使用起来就像普通变量或常量一样,也可以把它看成是普通的变量或常量。


public enum TimePeriod
{
    MINUTE(Dimension.MINUTE, 
           (from,
            to) -> Minutes.minutesBetween(from, to).getMinutes() + 1,
           Minutes::minutes, 
           from -> from.withZone(DateTimeZone.UTC)
                       .withSecondOfMinute(0)
                       .withMillisOfSecond(0)),
    HOUR(Dimension.HOUR,
         (from,
          to) -> Hours.hoursBetween(from, to).getHours() + 1,
         Hours::hours,
         from -> from.withZone(DateTimeZone.UTC)
                     .withMinuteOfHour(0)
                     .withSecondOfMinute(0)
                     .withMillisOfSecond(0)),
    DAY(Dimension.DAY,
        (from,
         to) -> Days.daysBetween(from, to).getDays() + 1,
        Days::days,
        from -> from.withZone(DateTimeZone.UTC)
                    .withTimeAtStartOfDay()),
    WEEK(Dimension.WEEK,
         (from,
          to) -> Weeks.weeksBetween(from, to).getWeeks() + 1,
         Weeks::weeks,
         from -> from.withZone(DateTimeZone.UTC)
                     .withDayOfWeek(1)
                     .withTimeAtStartOfDay()),
    MONTH(Dimension.MONTH,
          (from,
           to) -> Months.monthsBetween(from, to).getMonths() + 1,
          Months::months,
          from -> from.withZone(DateTimeZone.UTC)
                      .withDayOfMonth(1)
                      .withTimeAtStartOfDay());

    private Dimension<Timestamp> dimension;
    private BiFunction<DateTime, DateTime, Integer> getNumberOfPoints;
    private Function<Integer, ReadablePeriod> getPeriodFromNbOfInterval;
    private Function<DateTime, DateTime> getStartOfInterval;

    private TimePeriod(Dimension<Timestamp> dimension,
                       BiFunction<DateTime, DateTime, Integer> getNumberOfPoints,
                       Function<Integer, ReadablePeriod> getPeriodFromNbOfInterval,
                       Function<DateTime, DateTime> getStartOfInterval)
    {
        this.dimension = dimension;
        this.getNumberOfPoints = getNumberOfPoints;
        this.getPeriodFromNbOfInterval = getPeriodFromNbOfInterval;
        this.getStartOfInterval = getStartOfInterval;
    }

    public Dimension<Timestamp> getDimension()
    {
        return dimension;
    }

    public int getNumberOfPoints(DateTime from,
                                 DateTime to)
    {
        return getNumberOfPoints.apply(from, to);
    }

    public ReadablePeriod getPeriodFromNbOfInterval(int nbOfInterval)
    {
        return getPeriodFromNbOfInterval.apply(nbOfInterval);
    }

    public DateTime getStartOfInterval(DateTime from)
    {
        return getStartOfInterval.apply(from);
    }
}

可选链

示例代码:

print("--可选链---")

class Employee {
    var no : Int = 0
    var name : String = "Tony"
    var job : String?
    var salary : Double = 0
    var dept : Department = Department()
}

class Department {
    var no : Int = 10
    var name : String = "SALES"
    var comp : Company = Company()
}

class Company {
    var no : Int = 1000
    var name : String = "EOrient"
}

var emp = Employee()
print(emp.dept.comp.name)

Employee通过dept属性与Department关联,Department通过comp属性与Company关联。
通过代码emp.dept.comp.name可以引用到Company实例,形成一个引用的链条,但是这个“链条”任何一个环节“断裂”(为nil)都无法引用到最后的目标(Company实例)。

var dept : Department =
Department()是使用Department()构造器实例化dept属性的,这说明给定一个Employee实例,一定会有一个Department与其关联。但是现实世界并非如此,这种关联关系有可能有值,也有可能没有值,我们需要使用可选类型(Department?)声明dept实例。

修改代码如下:

class Employee {
    var no : Int = 0
    var name : String = "Tony"
    var job : String?
    var salary : Double = 0
    var dept : Department?
}


class Department {
    var no : Int = 10
    var name : String = "SALES"
    var comp : Company?
}

class Company {
    var no : Int = 1000
    var name : String = "EOrient"
}

var dept : Department?代码声明dept为可选类型,代码var comp :
Company?声明comp为可选类型,那么原来的引用方式emp.dept.comp.name已经不能应对可选类型了。之前介绍过可选类型的引用,可以使用感叹号(!)进行强制拆封,代码修改如下:

print(emp.dept!.comp!.name)

但是强制拆封有一个弊端,如果可选链中某个环节为nil,将会导致代码运行时错误。可以采用更加“温柔”的引用方式,使用问号(?)来代替原来感叹号(!)的位置。如下所示:

print(emp.dept?.comp?.name)

问号(?)表示引用的时候,如果某个环节为nil,它不会抛出错误,而是会把nil返回给引用者。这种由问号(?)引用可选类型的方式就是可选链。

可选链是一种“温柔”的引用方式,它的引用目标不仅仅是属性,还可以是方法、下标和嵌套类型等。

具有嵌套类型的示例:

class Employee {
    var no : Int = 0
    var name : String = ""
    var job : String = ""
    var salary : Double = 0
    var dept : Department?

    struct Department {
        var no : Int = 10
        var name : String = "SALES"
    }

}

var emp = Employee()
print(emp.dept?.name)

代码var dept :
Department?定义可选类型Department?的属性dept,Department是嵌套结构体类型。print(emp.dept?.name)采用可选链方式引用。输出结果为nil,这是因为emp.dept环节为nil。如果将代码var
dept : Department?修改一下:

var dept : Department? = Department()

则输出结果为SALES。这说明可选链可以到达目标name。

1.可选类型中的问号(?)

声明这个类型是可选类型,访问这种类型的变量和常量时要使用感叹号(!),下列代码是强制拆封:

let result1 : Double? = divide(100, 200)
print(result1!)

2.可选类型中的感叹号(!)

声明这个类型也是可选类型,但是访问这种类型的变量或常量时可以不使用感叹号(!),下列代码是隐式拆封:

let result3 : Double? = divide(100, 200)
print(result3)

3.可选链中的感叹号(!)

多个对象具有关联关系,当从一个对象引用另外对象的方式、属性和下标等成员时就会形成引用连,由于这个“链条”某些环节可能有值,也可能没有值,因此需要采用如下方式访问:

emp.dept!.comp!.name

4.可选链中的问号(?)

在可选链中使用感叹号(!)访问时,一旦“链条”某些环节没有值,程序就会发生异常,于是我们把感叹号(!)改为问号(?)
,代码如下所示:

emp.dept?.comp?.name

这样某些环节没有值的时候返回nil,程序不会发生异常。


通过枚举,我就能够很容易地修改代码,允许用户给图表数据点指定时间段。

访问限定

作为一种面向对象的语言封装性是不可缺少的,Swift语言在正式版中增加了访问控制,这样一来Swift语言就可以实现封装特性了。由于在Swift语言中类、结构体和枚举类型都具有面向对象的特性,因此Swift语言的封装就变得比较复杂。


访问范围

访问范围主要有两个: 模块和源文件。

模块是指一个应用程序包或一个框架。在Swift中,可以用import关键字将模块引入到自己的工程中。应用程序包是可执行的,其内部包含了很多Swift文件以及其他文件。

框架也是很多Swift文件及其他文件的集合,但是应用程序包不同的是,它编译的结果是不可以执行文件。

源文件指的是Swift中的.swift文件,编译之后它被包含在应用程序包或框架中,通常一个源文件包含一个面向对象类型(类、结构体和枚举),在这些类型中又包含函数、属性等。


原来是这样调用:

访问级别

Swift提供了3种不同访问级别,对应的访问修饰符为:public、internal和private。这些访问修饰符可以修饰类、结构体、枚举等面向对象的类型,还可以修饰变量、常量、下标、元组、函数、属性等内容。

提示:为了便于描述,我们把类、结构体、枚举、变量、常量、下标、元组、函数、属性等内容统一称为“实体”。

  • public
    可以访问自己模块中的任何public实体。如果使用import语句引入其他模块,可以访问其他模块中的public实体。

  • internal
    只能访问自己模块的任何internal实体,不能访问其他模块中的internal实体。internal可以省略,换句话说,默认访问限定是internal

  • private
    只能在当前源文件中使用的实体,称为私有实体。使用private修饰,可以用作隐藏某些功能的实现细节。

使用访问修饰符的示例代码如下:

print("--使用访问修饰符示例代码---")

public class PublicClass {}

internal class InternalClass {}

private class PrivateClass {}

public var intPublicVariable = 0
let intInternalConstant = 0//internal访问级别
private func intPrivateFunction()

for (int i = 0; i <= Days.daysBetween(from, to).getDays(); i++)

使用访问级别最佳实践

1.统一性原则

  • 原则1:如果一个类型(类、结构体、枚举)定义为internal或private,那么类型声明的变量或常量不能使用public访问级别。因为public的变量或常量可以被任何人访问,而internal或private的类型不可以。

代码:

print("--原则1--")

private class Employee {
    var no : Int = 0
    var name : String = ""
    var job : String?
    var salary : Double = 0
    var dept : Department?
}

internal struct Department {
    var no : Int = 0
    var name : String = ""
}

public let emp = Employee()//编译错误

public var dept = Department()//编译错误

2.设计原则

如果编写的是应用程序,应用程序包中的所有Swift文件和其中定义的实体,都是供本应用使用的,而不是提供其他模块使用,那么就不用设置访问级别了,即使用默认的访问级别。

如果开发的是框架,框架编译的文件不能独立运行,因此它天生就是给别人使用的,这种情况下要详细设计其中的Swift文件和实体的访问级别,让别人使用的可以设定为public,不想让别人看到的可以设定为internal或private。

3.元组类型的访问级别
元组类型的访问级别遵循元组中字段最低级的访问级别。代码如下:

print("---元组类型的访问级别---")

private class Employee {
    var no : Int = 0
    var name : String = ""
    var job : String?
    var salary : Double = 0
    var dept : Department?
}

struct Department {
    var no : Int = 0
    var name : String = ""
}

private let emp = Employee()
var dept = Department()

private var student1 = (dept, emp)

private var student1 = (dept,
emp)定义了元组student1,其中的字段dept和emp的最低访问级别是private,所以student1访问级别也是private,这也符合统一性原则。

4.枚举类型的访问级别

枚举中成员的访问级别继承自该枚举,因此不能为枚举中的成员指定访问级别。示例代码如下:

print("---枚举类型的访问级别---")

public enum WeekDays {
    case Monday
    case Tuesday
    case Wednesday
    case Thursday
    case Friday
}

由于WeekDays枚举类型是public访问级别,因而它的成员也是public级别。

变成这样调用:

for (int i = 0; i < timePeriod.getNumberOfPoints(from, to); i++)

支持getGraphDataPoints调用的Usage
Analytics服务代码已经完成了,并且支持时间段。值得一提的是,它考虑了我之前说过的开放闭合原则。

发表评论

电子邮件地址不会被公开。 必填项已用*标注