设计可复用的类
- 封装和信息隐藏:即表示独立性等。
- 继承和重写
- 多态,子类型和重载(对方法名的复用)
- 泛型
- 行为子类型和 Liskov Substitution Principle (LSP):
行为子类型是指父类型可以做的事情子类型都可以做,而且可能比父类型做得更好。行为子类型的要求:
a. 子类型需要实现抽象类型中的所有未实现的方法,并且子类型可以增加方法,但不可删。
b. 返回值:子类型中重写的方法的返回值的类型需要和父类型中的相同或者是父类型的返回值的子类型,即符合co-variance。
c. 参数:子类型中重写的方法的接收的参数的类型需要和父类型中的相同或者是父类型的返回值的父类型,即符合contra-variance。
(因为子类型中的方法需要比父类型有更弱的前置条件(可接收的参数范围更广)和更强的后置条件(返回值更具体)。)
d. 异常:子类型中重写的方法不能抛出额外的异常。
(以上四个为静态类型检查可以完成的)
e. 子类型需要有更强的不变量。在子类型的属性中,如果是继承的父类型的,则保持原来的不变量,如果是子类型中的新加的属性,则需要增加该表示不变量。
子类型中的方法需要比父类型有更弱的前置条件和更强的后置条件。
Liskov Substitution Principle (LSP):
协变:在父类型到子类型变得越来越具体时,返回值和异常的类型不变或者也变得越来越具体。例如,数组就是协变的,即某类型的数组元素可以为定义的类型或者其子类型。
反协变(逆变):在父类型到子类型变得越来越具体时,参数类型要相反的变化,要不变或越来越抽象。但是java不支持反协变,编译器会认为在子类型中参数变化了的函数为重载不是重写。
泛型中的LSP:在泛型类中,只有当两个泛型的类型一致,才有可能是其子类型,因为有类型擦出(运行时将所有类型参数替换为其界限或Object),否则将会在编译阶段报错。例如,
所以,当使用像List这样的类时,在某些方法中, 对泛型的所有类型操作都相同时,像List.size or List.clear,或者需要使用Object类型中提供的方法时,不能用List为参数,因为它们之间并没有父类型子类型的关系,此时可以使用通配符List<?>代表类型不明确的所有列表。< ? super A>代表下界,< ? extends A>代表上界。
6. 委托和组合
委托是建立在“对象”层面的,而继承是建立在“类”层面上的。在几个类中绝大部分相同但有一两个方法不同时,因为这几个不同的方法可能会造成复杂的继承树,采用委托会更方便。另外,若一个类中不同对象可能有不同的行为(即在对象实例方面),就只能采用委托的方法。CRP原则:
委托的类型:
a. 使用(A使用B): 临时性的委托。
b. 关联association:永久性的委托,直接把要委托的类作为要设计的类的属性。
c. 组合composition:更强的association,难以变化,由该类直接指定委托给哪种具体实现,实际上是把委托弱化到了class的层面上。
d. 聚合aggregation:更弱的association,可动态变化,即可以由客户端设置,是object层面的。
以上所有委托类型都支持一对多。