初学Java的时候感觉类和接口都好简单,后续慢慢发现类和接口的设计并非是想象总的那么简单的,还有好多需要学习。
而这一章作者就阐述了一些指导原则,指导我们设计出更加有用、健壮和灵活的类和接口,很有意义。
设计良好的模块对外部而言总是 隐藏了所有的细节
模块之间中通过它们的API进行通信, 一个模块不需要知道其他模块的内部工作情况 ,这个概念被称为 信息隐藏 或 封装 ,是 软件设计的基本原则之一 (还是封装好听啊,信息隐藏好low啊)
封装可以 有效解除各个模块之间的耦合关系 ,是现在模块化开发的基础,使模块 可以独立地开发、测试、优化、使用、理解和修改 。
Java中可以通过 包 、 访问修饰符 (private protected public)控制类、接口和成员的 可访问性(accessibility)
除了应该暴露的API之外,我们应该尽可能少的开放访问权。因为一旦暴露给外界,可能会有风险,另外还需要保证一直维护与兼容。
可以想象一下,当我们使用一个第三方库的时候,如果它暴露了一个不该暴露的类(假设为BitmapUtil),而我们恰好用了它里面的方法,结果它一升级,把方法改了,或者把方法去掉了,那我们不是懵了?
作者在最后给了小结: 应该始终尽可能地降低可访问性。应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,共有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都不是可变的
假设有一个类:
public class Point{
public double x;
public double y;
}
作者的意思是不应该直接暴露 x y
,要为它们提供 getter
、 setter
方法,这样有利于添加约束条件,辅助行为。
本人表示道理我懂,但是现在一般的类都不愿意去写getter setter了,真心觉得好烦啊,虽然方法可以用AS自动生成,我还是不太愿意去写,直接public就是那么任性
不可变类:实例不能被修改的类 。 每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变 。如:String、基本类型的包装类、BigInteger和BigDecimal
不可变类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。
不可变类的五条规则:
不要求同步,无惧多线程并发访问
所以不需要保护性拷贝(如String类的拷贝构造器)也可以重复利用,如:Boolean.FALSE/TRUE
不是很懂
如FALSE TRUE ,不过如果值少,到也没什么关系但是如String这样的不可变类,我们需要注意,多用StringBuilder(可变,性能好)
除非有很好的理由要让类成为可变的类,否则就应该是不可变的。如果类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。(降低出错的可能性)
在读HeadFirstDesignPattern的时候已经看到太多次了
这里的继承是指 实现继承
(implementation inheritance)也即 extends
而不是 接口继承
复合(composition)也应该是常听到的 组合
继承打破了封装性子类依赖于超类中特定功能的实现细节 当超类发生改变,子类可能会遭到破坏
比如随着版本的发布,超类需要新增方法,但是这些方法不是所有子类需要的,那么就破坏了子类!
复合,即使用包装类(wrapper class),其实这也就是设计模式中的 装饰者模式
另外值得一提的是, 复合以及转发并不是委托(delegation)
装饰者模式的优缺点不多说了,可以看设计模式的笔记
继承功能确实强大,但它也存在很多问题,比如违背了封装原则(是不是很矛盾?),对于两个类,它们确实有 is-a
的关系时候才使用继承!
所以使用继承的时候要考虑清楚
好的API文档应该描述一个给定的方法做了什么工作,而不是描述它是如何做到的
如标题,恩,我可是连注释都懒得写的人,怎么会写文档。。。
Java提供两种机制用来定义允许多个实现的类型: 接口
和 抽象类
。
接口和抽象类的区别有很多,其中 最为明显 的区别是: 抽象类可以包含某些方法的实现,而接口不允许,即接口都是抽象方法
而另外还有一个 更重要 的区别是:为了实现由抽象类定义的类型, 类必须成为抽象类的一个子类 。而Java是单继承的,所以抽象类作为类型定义受到了极大的限制,而接口没有这个限制。
现有的类可以很容易被更新,以实现新的接口
当需要增加方法的时候只需要 implements
具体的接口即可,非常方便,而如果通过抽象类来实现,则需要在抽象类里新增方法,而这会导致其他继承该抽象类的类也被强制加上额外的方法!
接口是定义mixin(混合类型)的理想选择
mixin是指主要的类型: 类除了实现它的”基本类型”之外,还可以实现这个mixin类型(一脸懵逼!这翻译的什么玩意?) Comparable
是一个mixin接口
我的理解是: 一个类,利用实现多个接口可以达到混合类型的目的,而利用抽象类只能继承一个类,则不能达到混合类型的效果!
我们知道当类实现接口时,我们可以把该类的类型当做是接口的类型来使用,这是我们定义接口的唯一目的,也即接口 只应该用来定义类型
看到这里,可能你会跟我一样奇怪,接口不就是用来定义类型的吗,还能用来干嘛?
有一种接口被称为 常量接口 ,就是没有方法,只有常量的接口, 这常量接口模式是对接口的不良使用 ,因为它没什么卵用还会污染实现类
Java中有几个常量接口,如 java.io.ObjectStreamConstants
,千万别学啊!~
建议常量用 工具类
或者 枚举
或者 @IntDef
注解来实现
标签类,书中对它的定义说得很拗口。
我的理解是一个类,拥有多个风格,通过一个属性来区分不同的风格,类里充斥着 if else
或者 switch case
举个例子:
class Person{
boolean isMan;
String sayHi(){
if (isMan) {
return "Yo hi man!~";
}else{
return "Hello";
}
}
}
Person类,通过 isMan
属性来区分是男的还是女的, sayHi()
方法针对男女有不同的表现,这个就是一个非常简单的标签类
标签类的缺点非常明显,当你要表现的风格非常多样的时候,你需要写大量的判断语句,非常容易出错,而且当你需要修改某一个风格的时候,你需要在一大堆代码里找出你要改的地方,很有可能引入bug,非常难以维护。
这个时候,将标签类转变成类层次就非常方便了:
abstract class Person{
abstract String sayHi();
}
class Man extends Person{
@Override
String sayHi() {
return "Yo hi man!~";
}
}
class Woman extends Person{
@Override
String sayHi() {
return "Hello";
}
}
抽象出一个 Person
类,定义 Man
和 Woman
类继承它,根据自己的需求实现 sayHi()
方法,当需要修改 Man
的行为时,你不需要也不用担心会破坏 Woman
的代码,代码可读性,可维护性一下子高了很多,有木有?!
函数对象
的概念: 如果一个类仅仅导出这样的一个方法(执行其他对象(这些对象被显示传递给这些方法)上的操作),它的实例实际上就等同于一个指向该方法的指针.
这样的实例被称为函数对象( function object
).
如:
class StringLengthComparator{
public int compare(String s1,String s2){
return s1.length()-s2.length();
}
}
StringLengthComparator
类即是一个典型的函数对象,指向 StringLengthComparator
对象的引用可以被当做是一个指向该比较器的 函数指针(function pointer)
,它也是一个用于字符串比较操作的具体策略,这个 策略 为 策略模式中 的策略,并且 函数指针 的主要用途就是实现策略模式.(关于策略模式这里就不多讲,推荐看<
嵌套类
(nested class)是指 被定义在另一个类的内部的类 ,它存在的目的应该 只是 为它的外围类(enclosing class)提供服务.
嵌套类分为四种:
除了第一种之外,其他三种都被称为 内部类(inner class)
最简单的嵌套类, 最好把它看成普通的类,只是碰巧被声明在另一个类的内部而已(挺不错的解释) ,所以它可以 脱离外部类单独存在 。
它可以访问外部类的所有成员,包括 private
修饰的,也可以被访问修饰符修饰,来控制它的可见性。
作用:静态成员类一般是用来 辅助 外部类的,比如 CoordinatorLayout
类中的 Behavior
类,它定义了一系列的行为用于辅助它的外部类 CoordinatorLayout
,例子有很多,不多说了。
虽然它们只差了一个 static
修饰符,但是其实它们差别巨大。
非静态成员类的实例都 隐含持有一个外部类的实例(enclosing instance)
这不仅仅会消耗更多的空间,还可能会导致 外部类的实例泄漏,内存泄漏 ,而静态成员类并不会。
这在Android中很常见,比如我们使用 Handler
的时候,AS都会提示我们这可能造成内存泄漏,让我们使用静态成员类。
所以 通常情况更推荐静态成员类 ,书中有一句话: 如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类
匿名类,没有名字,也不是外部类的成员,它是在 使用的同时被声明和实例化
需要注意的是: 当匿名类出现在非静态的环境中时,它会持有外部类的实例 ,所以它可能引起内存泄漏。
匿名类的作用:通常用于创建 函数对象 (见21条),比如 Thread
, Runnable
局部类非常少用,自己没用过,在源码里也没留意到它的存在,就不多写了。
虽然本条推荐静态成员类,不过每个嵌套类都有自己的用途,还是得按实际情况去抉择。
本章的内容在Java中非常重要,如果要提升架构能力,那么本章的学习必不可少!