可维护的软件系统的关键在于良好的模块划分。Java中的包就是最小的模块聚合单元。
- 包——最基本的聚合单元
- 包名冲突
- 自定义包名与文件夹名约定
- 访问控制
- Java Bean约定
- 抽象工厂模式
- builder模式
- Java模块系统
面向对象的三大特征:
- 封装
- 继承
- 多态
1. 封装及其必要性
封装可以隐藏内部实现细节,只对外暴露出接口。
如果不进行封装,直接暴露细节,那么当需求更改的时候,高耦合会造成所有使用到的地方都需要更改,而封装后,只需要修改内部实现,但暴露的接口依然不变。
2. 封装的实现
包之间是没有嵌套/包含关系的。包的作用:
- 区分不同的类
- 访问控制
JavaBean 命名约定:
- getter
- 非 boolean 属性:
get+驼峰
- boolean 属性:
is+驼峰
- 非 boolean 属性:
- setter 都是
set+驼峰
3. 属性与方法的访问控制符
-
public
任何⼈都能访问 -
protected
只有⼦类和同⼀个包的可以访问 -
package private
包级私有,只有同⼀个包的类可以访问,不用实际声明,默认就是 -
private
只有⾃⼰可以访问(通常需要封装数据时使用)
4. 设计模式实战:工厂方法模式
就像使用 getter/setter 封装属性读写一样,工厂方法也是进行封装,暴露接口,返回定制的对象。
- 使用静态工厂方法代替直接暴露构造器
- 将构造器私有化
public class Cat {
/** 猫咪的名字 */
private String name;
private boolean cute;
// 创建一只可爱的猫
public static Cat newCuteCat(String name) {
return new Cat(name, true);
}
// 创建一只不可爱的猫
public static Cat newUncuteCat(String name) {
return new Cat(name, false);
}
private Cat(String name, boolean cute) {
this.name = name;
this.cute = cute;
}
}
5. 类的访问控制符
-
public
任何类都可以访问 -
package private(默认)
包级私有,同一个包的类可以访问- 包之间是没有父子关系的
-
private inner class
只能在同一个类中访问,用于创建一个更小粒度的类方便进行交互,而不必处于两个不同文件中:
public class OuterClass {
private static class InnerClass { // 或者不加 static
}
}
-
Java Platform Module System
了解 JDK9 的模块化系统,比包更大的访问控制
很显然,属性和方法(不管静态的还是实例上的)包含于类和类所创建的实例中,所以类的访问控制符是更高维度的控制。类尚且无法访问,何谈访问类中的东西,故其可否访问的边界是以包来划分。换言之,其他包中能否访问这个包中的类就取决于这个类的访问控制符。
比如在外部包中访问内置类 ProcessEnvironment 会报错'java.lang.ProcessEnvironment' is not public in 'java.lang'. Cannot be accessed from outside package
,这就是因为这个内置类是包级私有的。
6. 用中间包绕过包级私有限制
在 JVM 看来包级私有限制都只是根据包名来判断,所以对于一些第三方包中私有类,可以通过创建同名的包,写一个 pulic class Bridge {}
,然后使用工厂方法暴露出用做new
的接口,以便作为桥梁(实际项目中这样做会破坏封装,不要这样!)。
package com.github.someUser.extensions; // 构造相同的包名
public class Bridge {
public static Object newInstance() {
return new Extension();
}
}
尽管如此,但是禁止自定义一个包名以java
开头的包,因为这是 java 内置的保留包(比如 java.lang
、java.util
等),会得到报错Prohibited package name: java.lang
。
7. 再谈封装的必要性
向外界暴露内部实现类是有风险的。
JDK9 模块化系统出现之前,包是最大的访问限制粒度,但如果一个项目中,不得已需要在不同的包中定义类,为了保证自己其它包中的类能访问到,那么先前定义的只能是 public
,那么问题来了,封装性被破坏,全宇宙都能访问到这个 public class
。
因此,JDK9 之前无法从技术上阻止一个原本意图是内部使用的类(放在不同包中)被外部所访问。只能通过约定,比如 包名取为internal
。
8. 设计模式实战:builder 模式
当构造器有很多参数时,可以考虑 builder 模式。
先定义出一个和目标类参数相同的 Builder 类,然后通过静态工厂方法(可选 )创建出这个 Builder 实例,接着通过链式调用为 Builder 实例丰富属性,最后再根据这些实例属性创造出最初的目标实例。
实现代码如下:
public class Main {
public static void main(String[] args) {
Person person = PersonBuilder.aPerson()
.withFirstName("qian")
.withLastName("xiao")
.withJob("FE")
.withPhone("25917758")
.build();
System.out.println(person.getClass());
}
}
public class Person {
private String firstName;
private String lastName;
private String job;
private String phone;
public Person(String firstName,
String lastName,
String job,
String phone) {
this.firstName = firstName;
this.lastName = lastName;
this.job = job;
this.phone = phone;
}
}
public final class PersonBuilder {
private String firstName;
private String lastName;
private String job;
private String phone;
private PersonBuilder() {
}
public static PersonBuilder aPerson() {
return new PersonBuilder();
}
public PersonBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public PersonBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
}
public PersonBuilder withJob(String job) {
this.job = job;
return this;
}
public PersonBuilder withPhone(String phone) {
this.phone = phone;
return this;
}
public Person build() {
return new Person(firstName, lastName, job, phone);
}
}
builder 模式有些繁琐,可以通过安装 IDEA 插件 Builder Generator
,然后搜索快捷键 Generate
来快速生成。