对于不同的编程语言,我们都有一系列的概念、原则和规定。这些概念、原则和规定就被称为编程范式。从理论上来说,我们希望编程语言只遵从一个编程范式。但是实际上,一个语言往往拥有多个编程范式。Java语言所基于的编程范式,包括命令式、面向对象、声明式和函数式编程。
命令式编程
命令式编程是这样一种编程范式:用语句更改程序的状态。
面向对象编程
面向对象基于四个基本原则:
- 封装
- 抽象
- 继承
- 多态
封装
封装主要是指属性和行为的绑定。封装是面向对象语言的基本原则之一。封装有助于不同模块的分离,使得开发人员可以或多或少地独立开发和维护解耦模块。在内部更改解耦模块/类/代码而不影响其外部暴露行为的技术称为代码重构。
抽象
抽象提供了一种机制,这种机制使得对象可以公开它所做的事,而隐藏它是如何做到这些事的。
为了驾驶一辆汽车,我们并不需要知道汽车引擎盖下是什么样的,我们只需要知道它给我们暴露的数据和行为。数据显示在汽车的仪表盘上,行为就是我们可以用控制设备来驾驶汽车。
继承
继承是指对象或类基于另一个对象或类的能力。有一个父类或者基类,它为实体提供顶级行为。每一个满足“父类的属性和方法是子类的一部分”条件的子类实体或者子类都可以从父类中继承,并根据需要添加其他行为。
子类它继承并可以访问在基类中声明为protected(保护)或public(公共)的所有成员和方法。
/**
* @author: Jay Mitter
* @date: 2020-08-06 22:48
* @description:
*/
public class Vehicle {
private String name;
public Vehicle(String name) {
this.name = name;
}
}
/**
* @author: Jay Mitter
* @date: 2020-08-06 22:48
* @description: Vehicle的子类
* 如果不增加构造方法,则提示:
* There is no default constructor available in 'com.pengjs.book.review.designpattern.entity.Vehicle'
*/
public class Car extends Vehicle {
private String brand;
/**
* 如果父类中显式的定义了带参构造函数,则默认的无参构造函数就被覆盖自动失效
* 一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建,对于Java来说要一直追述到Object祖宗(Object)-> 曾爷爷 -> 爷爷 -> 父亲 -> me
* 这个思维很自然,没有祖宗,何来后代?祖宗的一些东西都没准备好,后代怎么继承去用?
* 也就是说当你用子类的无参构造函数创建子类对象时,会去先递归调用父类的无参构造方法,这时候如果某个类的父类没有无参构造方法就会出错啦~
* @param name
*/
public Car(String name) {
super(name);
}
// public Car(String name, String brand) {
// super(name);
// this.brand = brand;
// }
}
多态
有一个Shape类,拥有两个计算面积的方法。一个方法计算一个圆的面积,它接受一个整数,也就是说,输入半径并返回这个圆的面积。另一个方法计算矩形的面积,它需要两个输入——长度和宽度。编译器可以根据调用参数的数量来决定调用哪个面积方法。这是编译时多态。
有些技术人员认为,只有运行时多态才是真正的多态。运行时多态(有时也称为子类型多态)在子类继承父类并覆盖其方法时起作用。在这种情况下,编译器无法决定最终是执行子类的实现还是父类的实现,只能在运行时决定。
import lombok.Data;
/**
* @author: Jay Mitter
* @date: 2020-08-06 22:48
* @description:
*/
@Data
public class Vehicle {
private String name;
public Vehicle(String name) {
this.name = name;
}
@Override
public String toString() {
return "Vehicle: " + this.name;
}
}
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author: Jay Mitter
* @date: 2020-08-06 22:48
* @description: Vehicle的子类
* 如果不增加构造方法,则提示:
* There is no default constructor available in 'com.pengjs.book.review.designpattern.entity.Vehicle'
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class Car extends Vehicle {
// private String brand;
/**
* 如果父类中显式的定义了带参构造函数,则默认的无参构造函数就被覆盖自动失效
* 一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建,对于Java来说要一直追述到Object祖宗(Object)-> 曾爷爷 -> 爷爷 -> 父亲 -> me
* 这个思维很自然,没有祖宗,何来后代?祖宗的一些东西都没准备好,后代怎么继承去用?
* 也就是说当你用子类的无参构造函数创建子类对象时,会去先递归调用父类的无参构造方法,这时候如果某个类的父类没有无参构造方法就会出错啦~
* @param name
*/
public Car(String name) {
super(name);
}
// public Car(String name, String brand) {
// super(name);
// this.brand = brand;
// }
@Override
public String toString() {
return "Car: " + super.getName();
}
}
/**
* 有些技术人员认为,只有运行时多态才是真正的多态。运行时多态(有时也称为子类型多态)在子类继承父类并覆盖其方法时起作用。在这种情况下,
* 编译器无法决定最终是执行子类的实现还是父类的实现,只能在运行时决定。
* 我们创建一个Vehicle对象和一个Car对象,将每个对象声明为Vehicle变量类型(因为Car也是Vehicle)。然后我们为每个对象调用toString方法。
* vehicle1是Vehicle类的一个实例,它将调用Vehicle.toString()方法。vehicle2是Car类的一个实例,它将调用Car类的toString方法:
*/
@Test
public void testPolymorphic() {
Vehicle vehicle1 = new Vehicle("A Vehicle");
Vehicle vehicle2 = new Car("A Car");
System.out.println(vehicle1.toString());
System.out.println(vehicle2.toString());
}

声明式编程
与命令式编程相反,声明式编程是这样一种编程范式:它指定程序应该做什么,而不具体说明怎么做。纯粹的声明式语言包括数据库查询语言(如SQL和XPath)以及正则表达式。与命令式编程语言相比,声明式编程语言更抽象。它们并不模拟硬件结构,因此不会改变程序的状态,而是将它们转换为新状态,并且更接近数学逻辑。
通常,非命令式的编程范式都被认为属于声明式类别。这就是为什么有许多类型的范式属于声明式类别。
函数式编程
函数式编程是声明式编程的子范式。与命令式编程相反,函数式编程不会改变程序的内部状态。
在命令式编程中,函数更多地被视为指令、例程或过程的序列。它们不仅依赖于存储在存储器中的状态,而且还可以改变该状态。这样,根据当前程序的状态,使用相同参数调用命令式函数可能会产生不同的结果,与此同时,被执行的函数更改了程序的变量。
在函数式编程术语中,函数类似于数学函数,函数的输出仅依赖于其参数,而不管程序的状态如何,完全不受函数是何时执行的影响。
矛盾的是,虽然命令式编程自计算机发明以来就存在,但函数式编程的基本概念却可以追溯到这之前。大多数函数式语言都是基于lambda演算,这是由数学家AlonzoChurch于20世纪30年代创建的一种形式化数学逻辑系统。
函数式语言在当时变得如此受欢迎的原因之一是它们可以轻松地在并行环境中运行,这与多线程不太一样。函数式语言支持并行运行的关键在于它们的基本原理:函数仅依赖于输入参数而不依赖于程序的状态。也就是说,它们可以在任何地方运行,然后将多个并行执行的结果连接起来并进一步使用。
流以及集合的使用
/**
* 流在java.util.stream包中定义,用于管理可以对其执行功能式操作的对象流。流是集合的功能对应物,并为映射归约操作提供支持。
*/
@Test
public void testStreamList() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 过滤掉奇数
List<Integer> odds = new ArrayList<>();
for (Integer val : list) {
if (val % 2 == 0) {
odds.add(val);
}
}
for (Integer val : odds) {
System.out.println(val);
}
// 从Java8开始,我们已经能够使用流在一行代码中执行相同的操作
IntStream.range(0, 10).filter(i -> i % 2 == 0).forEach(System.out::print);
}
统一建模语言
统一建模语言(Unified Modeling Language,UML)可以帮助我们表示软件的结构:不同的模块、类和对象如何相互交互,以及它们之间的关系是什么。
类之间的关系
在面向对象编程中,除了代表基本概念的继承关系之外,还有一些其他类关系可以帮助我们建模和开发复杂的软件系统:
- 泛化和实现
- 依赖
- 关联、聚合和组合
- 泛化
继承也被称为IsA关系,因为从另一个类继承而来的类能够被当成父类来使用。
当一个类表示多个类的共享特征时,这称为泛化,例如,Vehicle是Bike、Car和Truck的泛化。类似地,当一个类表示一般类的特殊实例时,这称为特化,因此Car是Vehicle的特化,在UML术语中,描述继承的关系称为泛化。
image.png
- 实现
如果说泛化是面向对象中继承概念对应的UML中的术语,则UML中的实现表示面向对象编程中类的接口实现。我们假设创建了一个名为Lockable的接口,该接口仅由可以锁定的Vehicle实现。
image.png
-
依赖
依赖是最通用的UML关系类型之一。它用于定义一个类以某种方式依赖于另一个类,而另一个类可能依赖于或不依赖于第一个类。依赖关系有时被称为UsesA关系。
image.png
-
关联
关联表示两个实体之间的关系。有两种类型的关联:组合和聚合。通常,关联关系由箭头表示。
image.png
-
聚合
聚合是一种特殊类型的关联。如果将继承看作IsA关系,则可以将聚合视为HasA关系。
聚合用于描述以下情况中两个或多个类之间的关系:一个类在逻辑上包含另一个类,但所包含的类的实例又可以独立于第一个类在其上下文之外生存,或者可以被其他的类所引用。例如,部门与教师间存在HasA关系,每位教师必须属于部门,但如果部门不再存在,教师仍然可以处于活动状态。
image.png
-
组合
顾名思义,一个类是另一个类的组成部分就称两者间存在组合关系。这有点类似于聚合,不同之处在于当主类不再存在时,依赖类不再存在。例如,房子(House)由房间(Room)组成,但如果房子被销毁,房间就不再存在。
image.png
实际上,尤其是在具有垃圾回收器的Java等语言中,组合和聚合之间的界限并不是很清晰。对象不必手动销毁,当它们不再被引用时,它们会被垃圾回收器自动销毁。因此,从编码的角度来看,我们不需要真正关心处理的是组合还是聚合关系,但是如果我们想要在UML中有一个定义良好的模型,考虑这一点就很重要了。
网友评论