【类与包】

为了便于对硬盘上的文件进行管理,通常会将文件分目录存放。同理,在程序开发中,也需要将编码的类在项目中分目录存放,以便于文件管理。为此,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类的子类。

随着客户数量的增加,系统决定升级为Oracle数据库,因此需要增加一个新的OracleDBUtil类 来连接Oracle数据库,由于在初始设计方案中CustomerDAO和DBUtil之间是继承关系,因此在 更换数据库连接方式时需要修改CustomerDAO类的源代码,将CustomerDAO作为OracleDBUtil 的子类,这将违反开闭原则。【当然也可以修改DBUtil类的源代码,同样会违反开闭原则。】

现使用合成复用原则对其进行重构。

根据合成复用原则,我们在实现复用时应该多用关联,少用继承。因此在本实例中我们可以使用关联复用来取代继承复用。

 

心得:

如果使用继承的方式去使用mysql数据库类的方法函数,那么在将来换成oracle数据库类的时候,就需要修改CustomerDAO类的代码,将其变成oracle数据库类的子类来使用该数据库连接方法。这违背了开闭原则。

如果我们将此时暂时还只有mysql数据库的数据库类作为成员变量被CustomerDAO类使用,那么当我们需要使用oracle数据库类的时候,就只需要对原先的数据库类进行泛化。CustomerDAO类不需要做出任何修改。

 

到这里,应该就能体会这个原则的应用场景了:

通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实 现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白 箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是 静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用 (如类没有声明为不能被继承)。

本文部分参考自刘伟——《Java设计模式》

最后修改:2024 年 02 月 06 日
无需 money,加油,你一定会变得更好!