类与接口
概览
类与接口是Java程序设计语言的核心,也是Java语言的基本抽象单元。Java语言提供了许多强大的基本元素,供程序用来设计类和接口。
首先老生常谈的设计模式的六大原则:
- 开闭原则
对扩展开放,对修改关闭 - 里式代换
任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当派生类可以替换掉其他基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。就好像花木兰替父从军,她也能打仗,而且由于扩展了年轻的功能,她还能更好的发挥自己的特性。。同时李氏代还也是对开闭原则的补充,实现开闭原则的关键步骤就是抽象化,而基类和子类的继承关系就是抽象化的具体实现。所以李氏替换就是对实现抽象化的具体步骤的规范。 - 依赖倒置
开闭原则的基础:针对接口编程,依赖于抽象而不依赖于具体。如只是有打仗这个方法,但是花木兰如何实现不关心 - 接口隔离
使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度。 - 最少知道
一个实体应该尽量少的与其他实体之间发生作用,使得系统功能模块相对独立 - 合成复用原则
尽量使用合成/聚合的方式,而不是使用继承
使类和成员的访问最小化
区别设计良好的模块与设计不好的模块,最重要的因素在于,这个模块对于外部其他的模块而言,是否隐藏内部数据和其他实现细节。设计良好的模块会隐藏所有的实现细节。把他的API与它的实现清晰地隔离开来,然后,模块之间通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏,亦或是封装。
Java语言提供了多种机制来协助信息隐藏。如访问控制机制决定了类、接口和成员的可访问性。实体的可访问性是由该接口声明的所在的位置,以及实体生命中所出现的访问修饰符(private、protected和public)共同决定的。正确的使用此类修饰符来实现对信息的隐藏。
- 私有的(private)—只有在声明该成员的顶层类内部才可以访问这个成员
- 包级私有的(package-private)—声明该成员的包内部的任何类都可以访问这个成员,从技术上讲,被称为确定default访问级别,如果没有为成员指定访问修饰符,就是这个级别。
- 受保护的(protected)—声明该成员的类的子类可以访问这个成员,并且声明该成员的包内部的任何类也可访问这个成员
- 公有的(public)—在任何地方都可以访问该成员
在设计类时应当尽可能的降低可访问性,在设计了一个最小的公有的API之后,应该防止把任何散乱的类、接口和成员变成API的一部分、除了公有静态的final域的特殊情况外。
在公有类中访问方法而非共有域
setter和getter!!!!!!不要直接访问
public class Telipu {
private int age=53;
private String stage="美国总统";
private String name="特离谱";
private String slogan="MAGA";
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getStage() {
return stage;
}
public void setStage(String stage) {
this.stage = stage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSlogan() {
return slogan;
}
public void setSlogan(String slogan) {
this.slogan = slogan;
}
}
使可变性最小化
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候提供,并在对象的整个生命周期内固定不变,Java平台类库中包含许多不可变的类,其中有String、基本类型的包装类、BigInteger和BigDecimal。存在不可变的类有许多理由:不可变的类比可变的类更加容易设计、实现和使用。
- 不要提供任何会修改对象状态的方法
- 保证类不会被扩展。
- 使所有的域都是final的
- 使所有的域都成为私有的
- 确保对于任何可变的组件的互斥访问
复合(引用)优于继承
继承是实现代码重用的有力手段,但它并非是完成这项工作的最佳工具。使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,对于普通的具体的类进行跨包边界的继承,则是非常危险的。
与引用方法不同,继承打破了封装性。子类会依赖于其超类中特定功能的实现细节。超类的实现有可能会在后续的开发中修改,如果父类出错,子类也难逃bug,所以在某种意义上,子类会因为超类的修改而改变。
继承是强大的多态工具,但是违背了封装原则。只有当子类和超类确实存在着密切的父子关系,使用继承才是恰当的。如果子类和超类处于不同的包中,并且超类并不是为了继承而设计的,那么继承将会导致脆弱性。
对于想实现抽象功能,采用引用更为合适,依据依赖倒置原则来设计类。
要么为继承而设计,要么禁止继承
用于继承的类的文档必须精确的描述每个方法所带来的影响。换句话说:该类必须有文档说明其可覆盖的方法和自用性。对于每个共有的或者受保护的方法,或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用优势如何影响后续的处理过程的。
类必须通过某种形式提供适当的钩子(hook),以便能够进入到他的内部工作流程中,这种形式可以是精心设计的受保护的方法
接口优于抽象类
Java程序设计语言提供了两种机制,可以用来允许多个实现的类型: 接口和抽象类。抽象类允许包含某些方法的实现,但是接口则不允许。一个更为重要的区别在于为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。任何一个类,只要它定义了所有必要的方法,必须遵守通用约定,他被允许实现一个接口,不管这个类处于类层次的哪个位置。因为Java只允许单继承,所以抽象类作为类型定义受到了极大限制。
- 现有的类可以很容易被更新,以实现新的接口。
- 接口的定义是mixin(混合类型)的理想选择
- 接口允许我们构造非层次结构的数据类型
接口只用于定义类型
当类实现接口时,接口就充当了可以引用这个类的实例的类型(type),因此,类实现了接口就表明客户端可以对这个类的实例实施某些动作。
public interface PhysicalConstants {
static final double NUMBER=0.6666;
static final String name="逢坂大河";
static final String heigh="不到一米六";
static final Integer age=16;
}
接口常量模式是对接口的不良使用。最好不要这么写
类层次优于标签类
public class RolePlay {
enum Role {
逢坂大河,高须龙儿
}
final Role role;
double heigh;
double weight;
//性别!性别!性别!
boolean sex;
RolePlay(double weight){
role = Role.逢坂大河;
this.weight=weight;
}
RolePlay(double heigh,boolean sex){
role = Role.高须龙儿;
this.heigh=heigh;
this.sex=sex;
}
}
标签类不推荐使用,这种枚举类非常的复杂,且效率不高,建议使用子类型化,标签类是类层次的一种简单效仿。
public class RolePlay {
class ManOne extends RolePlay{
}
class GirlOne extends RolePlay{
}
}
用函数对象表示策略
有些函数支持函数指针(function pointer)、代理(delegate)、lambda表达式(lambda expression),或者类似的机制。允许程序把调用特殊函数的能力,存储起来并传递这种能力。
因为看不懂这段,所以直接摆烂。
优先考虑静态成员变量
嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为它的外围类提供服务。如果嵌套类将来可能会用于其他的某个环境中,它就是顶层类。嵌套类有四种:静态成员变量、非静态成员类、匿名类和局部类。除第一种之外,其余都被称为内部类。
静态内部类是最简单的一种嵌套类。最好把它看做是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员,静态成员类是外围类的一个静态成员,与其他静态成员一样也遵守同样的可访问性规则。如果他被生命为私有,就只能在外围类的内部访问。
静态成员类的一种常见用法是作为公有的辅助类,仅当与它的外部类一起使用的时候才有意义
从语法上看,静态成员类和非静态成员类之间唯一的区别是静态成员类的生命中包含修饰符static,尽管他们的语法相似,但使用上有很大不同,在内部,费静态成员类可以直接调用外围实例的方法,在外部,没有外围实例就不可能调出非静态成员类
非静态内部类的一种使用方法是定义一个Adapter,它允许外部类的实例被看做一个不相关类的实例。
如果生命的成员类不要求访问外部实例,就要始终把Satic修饰符放在它的声明中,使它称为静态成员类,而不是非静态成员类。