类与包+四大特征+类之间六大关系+七大设计原则
【类与包】
为了便于对硬盘上的文件进行管理,通常会将文件分目录存放。同理,在程序开发中,也需要将编码的类在项目中分目录存放,以便于文件管理。为此,Java引入了包(package)机制,程序可以通过声明包的方式对Java类分目录管理。
Java中的包是专门用来存放目录的,通常功能相同的类存放在同一个包中。包通过package关键字声明,示例代码如下:
package cn.itcast.chapter01; //使用package关键字声明包
public class Example01{...}
需要注意的是,包的声明只能位于Java源文件的第一行。
在使用Eclipse开发Java程序时,定义的类都是含有包名的,如果没有显示声明包的package语句,则创建的类处于默认包下。但是,在实际开发中,这种情况是不应该出现的。本书的示例代码主要展现的是功能部分的代码,所以在大多数示例代码中没有为类指定包名,但是在提供的源代码中,都已使用包名。
在开发时,一个项目中可能会使用很多包,当一个包中的类需要调用另一个包中的类时,需要使用import关键字引入需要的类。使用import关键字可以在程序中导入某个指定包下的类,这样就不必在每次用到该类时都书写完整的类名,简化了代码量。使用import关键字导入某个包中类的具体格式如下:
import 包名.类名;
需要注意的是,import通常出现在package语句之后,类定义之前。如果需要用到一个包中的多个类,则可以使用“import 包名.*;”导入该包下所有的类。
【四大特征】
封装:
封装的概念:把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。
封装的好处:
(1):隐藏实现细节。好比你买了台电视机,你只需要怎么使用,并不用了解其实现原理。
(2):安全性。比如你在程序中私有化了age属性,并提供了对外的get和set方法,当外界 使用set方法为属性设值的时候 你可以在set方法里面做个if判断,把值设值在0-80岁,那样他就不能随意赋值了。
(3):增加代码的复用性。好比在工具类中封装的各种方法,你可以在任意地方重复调用,而不用再每处都去实现其细节。
(4):模块化。封装分为属性封装,方法封装,类封装,插件封装,模块封装,系统封装等等。有利于程序的协助分工,互不干扰,方便了模块之间的相互组合与分解,也有利于代码的调试和维护。比如人体由各个器官所组成,如果有个器官出现问题,你只要去对这个器官进行医治就行了。
继承:
继承的概念:从已知的一个类中派生出新的一个类,叫子类。子类实现了父类所有非私有化属性和方法,
并能根据自己的实际需求扩展出新的行为。
继承的好处:
(1):继承是传递的,容易在其基础上构造,建立和扩充出新的类。
(2):简化了人们对事物的认识和描述,能清晰体现相关类之间的层次结构关系。
(3):能减少数据和代码的冗余度。
(4):大大增加了代码的维护性。
多态:
用泛化来降低依赖方对被依赖方的耦合性,
举个例子:
人开车,车有好几种。
不使用多态:
人:
class people{
public String name;
public DZ dz;
public Benz benz;
public void drive(DZ dz){
this.dz=dz;
dz.run();
}
public void drive(Benz benz){
this.benz=benz;
benz.run();
}
}
车:
class DZ{
public String name ;
public DZ(String name){
this.name=name;
}
public void run(){
System.out.print(name+"已在路上飞快的奔跑");
}
}
class Benz{
public String name;
public Benz(String name){
this.name=name;
}
public void run(){
System.out.print(name+"已在路上飞快的奔跑");
}
}
每增加一个车类,就需要修改people类,在people类里面新增加一个具体的函数。
那么多态怎么实现呢?
人:
class people{
public String name;
public Car car;
public void drive(Car car){
this.car=car;
car.run();
}
}
车的父类:
class Car{
public String name;
public Car(String name){
this.name=name;
}
public void run(){
System.out.println(name+"已在路上飞快的奔跑");
}
}
泛化:
class DZ extends Car{
public DZ(String name){
super(name);
}
}
class Benz extends Car{
public Benz(String name){
super(name);
}
}
class BMW extends Car{
public BMW(String name){
super(name);
}
}
这体现了开闭原则。
抽象:
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象方法:
1.定义
新建一个方法
起一个名字
在方法体内不写任何方法体
2.特点
关键字:abstract
抽象方法中没有方法体
3.语法结构
public abstract void 方法名();
抽象类——有抽象方法的类
1.特点
抽象类不可以实例化
继承一个抽象类,必须实现抽象类中的抽象方法,除非子类也是抽象类。
抽象类的好处
抽象方法是一个模板或约束,避免了子类的随意性,需要实现它的类必须重写它的抽象那个方法。
例子:
package com.lenovo.entity;
public abstract class Fruit {
public abstract void eat();
public abstract void wash();
}
package com.lenovo.entity;
public class Banana extends Fruit {
@Override
public void eat() {
System.out.println("香蕉剥皮吃");
}
@Override
public void wash() {
System.out.println("香蕉不用洗");
}
}
package com.lenovo.entity;
public class Apple extends Fruit{
@Override
public void eat() {
System.out.println("苹果削皮吃");
}
@Override
public void wash() {
System.out.println("苹果要先洗");
}
}
接口也是一种抽象类:
interface ShowMessage {
void 显示商标(String s);
}
class TV implements ShowMessage {
public void 显示商标(String s) {
System.out.println(s);
}
}
class PC implements ShowMessage {
public void 显示商标(String s) {
System.out.println(s);
}
}
public class Example {
public static void main(String args[]) {
ShowMessage sm; //声明接口变量
sm=new TV(); //接口变量中存放对象的引用
sm.显示商标("长城牌电视机"); //接口回调。
sm=new PC(); //接口变量中存放对象的引用
sm.显示商标("联想奔月5008PC机"); //接口回调
}
}
【六大关系】
依赖:
假设有两个类,类A和类B,类A的某个成员方法的参数有类B,说明类A使用了类B,类A依赖类B,依赖关系即uses-a关系,依赖关系除了被依赖方作为依赖方的方法参数,还可能作为依赖方的方法返回值存在,这些都是依赖关系的表现形式。如下图所示:
依赖关系的例子有很多,比如:LocalDateTime的now方法根据时区ID创建LocalDateTime,这里说明LocalDateTime依赖ZoneId。
public static LocalDateTime now(ZoneId zone) {
return now(Clock.system(zone));
}
在UML中依赖关系使用虚线箭头表示,依赖方指向被依赖方:
关联:
关联关系是一种强依赖的关系,假设有两个类,类A和类B,类B作为类A的成员变量存在,类A也可为类B的成员变量存在,如果互为成员变量则为双向依赖,否则为单向依赖。
关联关系与依赖关系的区别在于,依赖关系是一种临时的关系,依赖关系主要体现在方法参数,当调用方法时才有关系,关联关系是一种长期的关系,主体现在成员变量,无论是否调用方法这种关系都存在。
比如:ZonedDateTime与LocalDateTime关联,ZonedDateTime是带时区的日期时间,ZonedDateTime关联LocalDateTime。
在UML中双向关联关系一条实线表示,单向关联为单向实线箭头表示 。
聚合:
聚合关系是一种has-a关系,假设有两个类,类A和类B,类A包含类B,类B是类A的成员变量,聚合关系和关联关系都体现在成员变量,它们的区别在于:关联关系双方是平级的,是个体和个体的关系,聚合关系双方不是平级的,是整体和部分的关系。
比如:LocalDateTime类中包括LocalDate date和LocalTime time,这是一种聚合关系。
在UML中聚合关系用下边的符号表示 聚合关系:
菱形连接整体,实线连接部分。LocalDateTime类中包括LocalDate date和LocalTime time聚合关系如下图:
组合:
组合关系是一种强聚合的关系,组合关系与聚合关系的区别在于:聚合关系中部分离开整体仍可存活,组合关系中部分离开整体没有意义,比如:人由身体、四肢等部分组成 ,它们的关系为组合关系。
在UML中组合关系使用下边的符号表示:
人与身体、四肢的关系表示如下:
泛化:
实线空心三角箭头,描述一种特殊与一般的关系,例如,人类与科学家,科学家是人类的一种。
泛化(generalization)关系时指一个类(子类、子接口)继承另外一个类(称为父类、父接口)的功能,并可以增加它自己新功能的能力,继承是类与类或者接口与接口最常见的关系,在Java中通过关键字extends来表示。
实现:
虚线空心三角箭头,描述一种具有关系,例如,飞翔之于鸟,鸟具有飞翔能力。
实现(realization)是指一个class实现interface接口(一个或者多个),表示类具备了某种能力,实现是类与接口中最常见的关系,在Java中通过implements关键字来表示。
【七大原则】
合成复用原则:
下面通过一个简单实例来加深对合成复用原则的理解:
Sunny软件公司开发人员在初期的CRM系统设计中,考虑到客户数量不多,系统采用MySQL 作为数据库,与数据库操作有关的类如CustomerDAO类等都需要连接数据库,连接数据库的 方法getConnection()封装在DBUtil类中,由于需要重用DBUtil类的getConnection()方法,设计人 员将CustomerDAO作为DBUtil类的子类,初始设计方案结构如图1所示:
随着客户数量的增加,系统决定升级为Oracle数据库,因此需要增加一个新的OracleDBUtil类 来连接Oracle数据库,由于在初始设计方案中CustomerDAO和DBUtil之间是继承关系,因此在 更换数据库连接方式时需要修改CustomerDAO类的源代码,将CustomerDAO作为OracleDBUtil 的子类,这将违反开闭原则。【当然也可以修改DBUtil类的源代码,同样会违反开闭原 则。】
现使用合成复用原则对其进行重构。
根据合成复用原则,我们在实现复用时应该多用关联,少用继承。因此在本实例中我们可以 使用关联复用来取代继承复用,重构后的结构如图2所示:
心得:
如果使用继承的方式去使用mysql数据库类的方法函数,那么在将来换成oracle数据库类的时候,就需要修改CustomerDAO类的代码,将其变成oracle数据库类的子类来使用该数据库连接方法。这违背了开闭原则。
如果我们将此时暂时还只有mysql数据库的数据库类作为成员变量被CustomerDAO类使用,那么当我们需要使用oracle数据库类的时候,就只需要对原先的数据库类进行泛化。CustomerDAO类不需要做出任何修改。
到这里,应该就能体会这个原则的应用场景了:
通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实 现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白 箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是 静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用 (如类没有声明为不能被继承)。