单一职责原则
基本介绍
对于类来说,一个类应该负责单一项职责。如果存在一个类A负责两个不同的职责,职责1和职责2。当职责1需求发生变化而改变A时,职责2执行错误,这个时候需要将A分解为A1和A2
代码实现
基础版本
package single.responsibility;
public class Index {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("飞机");
vehicle.run("轮船");
vehicle.run("汽车");
}
}
class Vehicle{
public Vehicle(){
}
public void run(String name){
System.out.println(name + " 运行在路上");
}
}
输出结果
飞机 运行在路上
轮船 运行在路上
汽车 运行在路上
在Vehicle
中,很明显的没有遵守单一职责原则。run方法中适配了所有的情况,不方便于修改
修改方式1
public class Index {
public static void main(String[] args) {
Vehicle aircraft = new Aircraft();
aircraft.run("飞机");
Vehicle car = new Car();
car.run("汽车");
Vehicle ship = new Ship();
ship.run("轮船");
}
}
class Vehicle {
public Vehicle() {
}
public void run(String name) {
}
}
class Car extends Vehicle {
@Override
public void run(String name) {
System.out.println(name + "运行在路上");
}
}
class Ship extends Vehicle {
@Override
public void run(String name) {
System.out.println(name + "运行在海上");
}
}
class Aircraft extends Vehicle {
@Override
public void run(String name) {
System.out.println(name + "运行在天上");
}
}
这种修改方式遵循了单一职责原则,但是缺点是代码量暴增,需要程序员维护的类也对应的变多了。
修改方式2
public class Index {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runInSea("飞机");
vehicle.runInAir("轮船");
vehicle.runInRoad("汽车");
}
}
class Vehicle {
public Vehicle() {
}
public void runInRoad(String name) {
System.out.println(name + " 运行在路上");
}
public void runInSea(String name) {
System.out.println(name + " 运行在海上");
}
public void runInAir(String name) {
System.out.println(name + " 运行在天上");
}
}
严格来说,这一种修改方式没有完全遵循单一职责原则。但是它将可能依赖在一起的代码隔离开。相对第一种方式而言,第二种需要维护的代码减少。
注意事项以及相关细节
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,我们应当遵守单一职责原则。以下两种情况例外
- 当逻辑足够简单时,可以在代码级别违反单一职责原则
- 当类中方法数量足够少,可以在方法级别保持单一职责原则
接口隔离原则
基本介绍
客户端不应该依赖它不需要的接口,即一个类对另一个依赖应该建立在最小的接口上。
其实通俗来理解就是,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加轻便灵活。或许看到接口隔离原则这样的定义很多人会觉得和单一职责原则很像,但是这两个原则还是有着很鲜明的区别。接口隔离原则和单一职责原则的审视角度是不同的,单一职责原则要求类和接口职责单一,注重的是职责,是业务逻辑上的划分,而接口隔离原则要求方法要尽可能的少,是在接口设计上的考虑。例如一个接口的职责包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,并规定了“不使用的方法不能访问”,这样的设计是不符合接口隔离原则的,接口隔离原则要求“尽量使用多个专门的接口”,这里专门的接口就是指提供给每个模块的都应该是单一接口(即每一个模块对应一个接口),而不是建立一个庞大臃肿的接口来容纳所有的客户端访问。
举个栗子
现有A,B,C,D四个类均实现interface1
。其中A只需要方法1,B只需要方法2,C只需要方法3,D只需要方法45。这个时候对于他们而言,interface1
中的除却需要的方法,其他的方法均毫无用处。
解决方案是,将接口interface1
拆分成几个接口。以上述栗子,需要拆分成四个接口,A,B,C,D分别实现对应接口即可
依赖倒转原则
基本介绍
- 中心思想是面向接口编程
- 相对于细节的多变性,抽象的东西是要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。
- 使用接口或者抽象类的目的是为了制定要规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
举个栗子
基础实现
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public void getInfo(){
System.out.print("收到邮件");
}
}
class Person{
public void receive(Email email){
email.getInfo();
}
}
在这个栗子中,需求是,用户需要接受邮件。上述代码在简单实现的同时,也带了无法拓展的问题。试想一下,如果我们有了新的需求,用户需要接受微信、QQ或者其他的信息呢?我们难道要在Person中定义很多个接受方法吗?
优化代码
public class Main {
public static void main(String[] args) {
Person person = new Person();
IReceive email = new Email();
person.receive(email);
}
}
interface IReceive{
void getInfo();
}
class Email implements IReceive{
public void getInfo(){
System.out.print("收到邮件");
}
}
class Person{
public void receive(IReceive iReceive){
iReceive.getInfo();
}
}
优化的思路很简单。我们定义一个接受接口IReceive
,如此一来即便我们有了其他的需求,只要实现IReceive
即可。
接口传递方式
- 方法参数传递
- 构造方法传递
- 定义一个接口变量,在构造方法中初始化
- setter方式传递
- 定义一个接口变量,使用setter方式初始化
注意事项
- 底层模块尽量都要有抽象类或者接口
- 变量声明进行使用抽象类或者接口,这样我们变量引用和实际对象之间存在一层缓冲层,便于程序优化和拓展。换句话说,尽量多的使用多态
- 继承时应该遵顼里氏替换原则
里氏替换原则
继承性的思考和说明
- 继承包含一层含义,父类中凡是已经实现好的方法,实际上是在设定规范和契约,所以它不强制所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个体系造成破环
- 继承在给程序设计带来了便利的同时,也带来了弊端。比如继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性。
基本概念
- 如果对每个类型为
T1
的对象o1
,都有类型为T2
的对象o2
,使得以T1
定义的所有程序P在所有的对象o1
都替换成o2
时,程序P的行为没有发生变化,那么类型T2
是类型T1
的子类型。换句话说,所有引用基类的地方必须可以透明的使用其子类对象 - 在使用继承时,遵顼里氏替换原则,在子类尽量不要重写父类的方法
- 里氏替换原则高速我们,继承实际上让两个类的耦合性增加了,在适当的情况下,可以通过聚合,组合,依赖来解决问题
开闭原则
基本介绍
- 编程中最基础,最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开发,对修改关闭。用抽象构建框架,用实现拓展细节
- 当软件需要发生变化时,尽量通过拓展软件实体的行为来实现变化,而不是通过修改已有的代码
- 编程中遵循其他原则,以及使用设计模式的目的时为了遵循开闭原则
- 面向接口编程
迪米特法则
基本介绍
- 一个对象应该对其他对象保持最小的了解
- 类与类之间的关系越密切,耦合度越大
- 迪米特法则又叫最小知道原则,即一个类对自己依赖的类知道的最少越好。
- 更简单的定义,只是与直接朋友通信
- 直接朋友:每个对象都会与其他对象存在耦合关系。只要两个对象存在耦合关系,我们称两个对象之间是朋友关系。耦合的方式有很多,依赖,关联,组合,聚合等。其中,我们称成员变量,方法参数,方法返回值中的类为直接朋友,而出现在局部变量中的类不是直接朋友
- 陌生的类最好不要以局部变量的形式出现在类的内部
举个栗子
基本代码
假设现在有一个需求,需要打印学院员工与普通员工的相关信息。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
CollegeEmployeeMgt collegeEmployeeMgt = new CollegeEmployeeMgt();
collegeEmployeeMgt.printInfo(new EmployeeMgt());
}
}
class Employee {
public Employee(int id) {
this.id = id;
}
int id;
}
class CollegeEmployee {
public CollegeEmployee(int id) {
this.id = id;
}
int id;
}
class EmployeeMgt{
public List<Employee> generateEmployee(){
List<Employee> result = new ArrayList<>();
for (int i = 0 ;i < 10 ; i ++){
result.add(new Employee(i));
}
return result;
}
}
class CollegeEmployeeMgt{
public List<CollegeEmployee> generateEmployee(){
List<CollegeEmployee> result = new ArrayList<>();
for (int i = 0 ;i < 5 ; i ++){
result.add(new CollegeEmployee(i));
}
return result;
}
public void printInfo(EmployeeMgt employeeMgt){
List<Employee> employees = employeeMgt.generateEmployee();
for (Employee employee : employees){
System.out.println("employee" + employee.id);
}
System.out.println("--------------------");
List<CollegeEmployee> collegeEmployees = generateEmployee();
for (CollegeEmployee employee : collegeEmployees){
System.out.println("collegeEmployees" + employee.id);
}
}
}
这段代码的坏味道在哪呢?在printInfo
这个方法中出现了Employee
。这个变量在CollegeEmployeeMgt
类中属于局部变量,而它的作用仅仅只是为了打印。
优化代码
public class Main {
public static void main(String[] args) {
CollegeEmployeeMgt collegeEmployeeMgt = new CollegeEmployeeMgt();
collegeEmployeeMgt.printInfo(new EmployeeMgt());
}
}
class Employee {
public Employee(int id) {
this.id = id;
}
int id;
}
class CollegeEmployee {
public CollegeEmployee(int id) {
this.id = id;
}
int id;
}
class EmployeeMgt{
public List<Employee> generateEmployee(){
List<Employee> result = new ArrayList<>();
for (int i = 0 ;i < 10 ; i ++){
result.add(new Employee(i));
}
return result;
}
public void printInfo(){
List<Employee> employees = generateEmployee();
for (Employee employee : employees){
System.out.println("employee" + employee.id);
}
}
}
class CollegeEmployeeMgt{
public List<CollegeEmployee> generateEmployee(){
List<CollegeEmployee> result = new ArrayList<>();
for (int i = 0 ;i < 5 ; i ++){
result.add(new CollegeEmployee(i));
}
return result;
}
public void printInfo(EmployeeMgt employeeMgt){
employeeMgt.printInfo();
System.out.println("--------------------");
List<CollegeEmployee> collegeEmployees = generateEmployee();
for (CollegeEmployee employee : collegeEmployees){
System.out.println("collegeEmployees" + employee.id);
}
}
}
优化思路便是新增EmployeeMgt.printInfo
方法用于隐藏相关的打印信息
注意事项
- 核心是降低类之间的耦合
- 迪米特法则只是要求降低耦合,并不是要求完全没有耦合
合成复合原则
基本介绍
- 尽量使用合成/聚合的方式,而不是继承
设计原则核心思想
- 找出应用中可能变化的代码,将他们独立起来,不要和不需要变化的代码混在一起
- 针对接口编程而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力