设计模式 Day 01
文章总结自B站尚硅谷视频
1. 什么是设计模式
设计模式(design pattern)是对软件设计中普遍存在(反复出现)的问题,题出的解决方案。
优势(目的):
- 可扩展性:添加新的功能时比较方便;
- 可读性:代码规范,方便其它程序员理解;
- 可重用性:相同功能的代码不需要多次编写;
- 可靠性:新增或者减少功能对原有的功能没有影响;
- 模块内部聚合性高,对其它模块的依赖(耦合)低。
2. 设计模式的七大原则
2.1 单一职责原则
对类来说,一个类只负责一项职责。
作用:
- 降低类的复杂度;
- 提高可读性,可维护性;
- 降低改变引起的风险;
- 一般情况下需要在类的级别上遵守单一职责,但是如果方法足够少,逻辑足够简单,可以在方法上遵守单一原则。
没有遵守单一职责原则:飞机和轮船是不能在公路上运行的。
/**
* @date 2020/8/13 14:04
* 单一职责原则01
*/
public class SingleResponsibility01 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("小汽车");
vehicle.run("轮船");
vehicle.run("飞机");
}
}
// 方式1:
// 上面调用run方法违反了单一职责原则。
// 解决方案:根据交通工具的不同分解成不同的类。
/**
* 交通工具类
*/
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+" 在公路上运行......");
}
}
遵守了单一职责原则,但是过于臃肿。
/**
* @date 2020/8/13 14:10
* 单一职责原则02
*/
public class SingleResponsibility02 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("小汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("轮船");
}
}
// 方案2:
// 优点:遵守了单一职责原则
// 缺点:改动很大,
// 改进方案:直接修改原先的Vehicle类。
/**
* 陆地交通工具类
*/
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle+" 在公路上运行......");
}
}
/**
* 天空交通工具
*/
class AirVehicle{
public void run (String vehicle){
System.out.println(vehicle+" 在天上飞......");
}
}
/**
* 水上交通工具类
*/
class WaterVehicle{
public void run(String vehicle){
System.out.println(vehicle+" 在水里走......");
}
}
在方法中遵守的单一职责的原则。
/**
* @date 2020/8/13 14:17
* 单一职责原则03
*/
public class SingleResponsibility03 {
public static void main(String[] args) {
Vehicle02 vehicle02 = new Vehicle02();
vehicle02.run("小汽车");
vehicle02.runAir("飞机");
vehicle02.runWater("轮船");
}
}
// 方式3:
// 优点:没有对原来的类做大的修改
// 缺点:这里虽然没有在类级别上遵守单一职责原则,但是在方法上仍然遵守了。
class Vehicle02{
public void run(String vehicle){
System.out.println(vehicle + "在路上跑");
}
public void runAir(String vehicle){
System.out.println(vehicle+"在天空飞");
}
public void runWater(String vehicle){
System.out.println(vehicle+"在水中中");
}
}
2.2 接口隔离原则
客户端不因该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。也就是说,如果一个类要依赖另一个类实现的接口,这个接口里面有用不到的方法,应该将这个接口分解成多个接口。
案例:有AB两个类,需要通过CD两个类的实现去调用接口interface1里面的方法,但是A只需要调用operation1、operation1、operation3
,B只需要调用operaction1、operation4、operation5
。
如果直接让CD两个类去实现interface1里面全部的方法,那么AB两个类就必须也要继承全部的方法,有违接口隔离原则。
/**
* @date 2020/8/13 14:41
* 接口隔离01
*/
public class Segregation01 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1{
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation2() {
System.out.println("B 实现了 operation2");
}
public void operation3() {
System.out.println("B 实现了 operation3");
}
public void operation4() {
System.out.println("B 实现了 operation4");
}
public void operation5() {
System.out.println("B 实现了 operation5");
}
}
class D implements Interface1{
public void operation1() {
System.out.println("D 实现了 operation1");
}
public void operation2() {
System.out.println("D 实现了 operation2");
}
public void operation3() {
System.out.println("D 实现了 operation3");
}
public void operation4() {
System.out.println("D 实现了 operation4");
}
public void operation5() {
System.out.println("D 实现了 operation5");
}
}
// A 通过interface1 引用 B类的 123 方法
class A{
public void depend1(Interface1 interface1){
interface1.operation1();
}
public void depend2(Interface1 interface1){
interface1.operation2();
}
public void depend3(Interface1 interface1){
interface1.operation3();
}
}
// C 通过interface1 引用 C类的 145 方法
class C{
public void depend1(Interface1 interface1){
interface1.operation1();
}
public void depend4(Interface1 interface1){
interface1.operation4();
}
public void depend5(Interface1 interface1){
interface1.operation5();
}
}
解决方案:interface1接口分解成三个接口,分别有operation1
方法的interface1接口,operation2、operation3
方法的interface2接口以及opercation4、operation5
方法的interface3接口。让CD分别继承interface1、interface2
和interface1、interface3
。
将接口分离的:
/**
* @date 2020/8/13 14:55
* 接口隔离原则02
*/
public class Segregation02 {
public static void main(String[] args) {
A1 a1 = new A1();
a1.depend1(new B1());
a1.depend2(new B1());
a1.depend3(new B1());
C1 c1 = new C1();
c1.depend1(new D1());
c1.depend4(new D1());
c1.depend5(new D1());
}
}
interface Interface01{
void operation1();
}
interface Interface02{
void operation2();
void operation3();
}
interface Interface03{
void operation4();
void operation5();
}
class B1 implements Interface01,Interface02{
public void operation1() {
System.out.println("B1 实现 operation1");
}
public void operation2() {
System.out.println("B1 实现 operation2");
}
public void operation3() {
System.out.println("B1 实现 operation3");
}
}
class D1 implements Interface01,Interface03{
public void operation1() {
System.out.println("D1 实现 operation1");
}
public void operation4() {
System.out.println("D1 实现 operation4");
}
public void operation5() {
System.out.println("D1 实现 operation5");
}
}
class A1{
public void depend1(Interface01 interface1){
interface1.operation1();
}
public void depend2(Interface02 interface1){
interface1.operation2();
}
public void depend3(Interface02 interface1){
interface1.operation3();
}
}
class C1{
public void depend1(Interface01 interface1){
interface1.operation1();
}
public void depend4(Interface03 interface1){
interface1.operation4();
}
public void depend5(Interface03 interface1){
interface1.operation5();
}
}
优势:
- 可读性高,可维护性高;
- 符合高内聚,低耦合。
2.3 依赖倒转(倒置)原则
- 高层模块不依赖低层模块,二者都应该依赖其抽象;。
- 抽象不应该依赖细节,细节依赖抽象。
- 核心思想是面向接口编程。
- 设计理念:相较于细节的多变性,抽象的东西要相对稳定的多。在Java中以接口或者抽象类搭建的架构要比使用实现类为基础搭建的架构要稳定的多。
- 使用接口和抽象类的目的是指定规范,价值在于设计,具体的操作让实现类去完成。
案例:使用person
类接收发来的信息。
方法1:
/**
* @date 2020/8/13 15:26
* 依赖倒置
*/
public class DependencyInversion01 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "电子邮件信息:Hello";
}
}
// 方式1:
// 优势:简单
// 缺点:现在只能接收电子邮件,如果有其它的短信就需要增加类,person也增加相应的方法。
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
方式2:
/**
* @date 2020/8/13 15:36
* 依赖倒置
*/
public class DependencyInversion02 {
public static void main(String[] args) {
Person1 person1 = new Person1();
person1.receiver(new Email2());
person1.receiver(new WeiChat());
}
}
// 接收者接口
interface IReceiver{
String getInfo();
}
class Email2 implements IReceiver{
public String getInfo() {
return "电子邮件:Hello";
}
}
class WeiChat implements IReceiver{
public String getInfo() {
return "微信消息:Hello";
}
}
class Person1{
public void receiver(IReceiver iReceiver){
System.out.println(iReceiver.getInfo());
}
}
显而易见,如果使用方式1,有新的业务就必须添加新的业务的类以及person类对应的方法。在方式2中,如果有新的业务就只需要添加业务类继承接口。
依赖传递的三种方式:
- 接口
- 构造方法
- setter
注意事项:底层模块尽量都要有抽象类或接口,变量的生命类型尽量是抽象类或接口,这样变量引用和实际对象之间有一个缓冲层,利于程序的扩展和优化。在继承时要遵守里氏替换原则。
2.4 里氏替换原则
引用基类的地方必须能够透明的引用其子类的对象。在子类中尽量不要重写父类的方法。在适当的情况下可以通过聚合,组合,依赖来解决问题。
/**
* @date 2020/8/14 9:54
* 里氏替换原则
*/
public class Liskov01 {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3 = "+a.fun1(11, 3));
System.out.println("1-8 = "+a.fun1(1, 8));
System.out.println("-------------------------------");
B b = new B();
System.out.println("11-3 = "+b.fun1(11,3));
System.out.println("11+4+9 = "+b.fun2(11,4));
}
}
// A
class A{
public int fun1(int num1, int num2){
return num1-num2;
}
}
// B
class B extends A{
@Override
public int fun1(int num1, int num2){
return num1+num2;
}
public int fun2(int a, int b){
return fun1(a,b)+9;
}
}
上方的代码中B继承了A类,同时也复写了A类中fun1的方法,导致想要的结果与预期不符。
/**
* @date 2020/8/14 10:03
* 里氏替换原则02
*/
public class Liskov02 {
public static void main(String[] args) {
A1 a = new A1();
System.out.println("11-3 = "+a.fun1(11, 3));
System.out.println("1-8 = "+a.fun1(1, 8));
System.out.println("-------------------------------");
B1 b = new B1();
System.out.println("11-3 = "+b.fun1(11,3));
System.out.println("11+4+9 = "+b.fun2(11,4));
}
}
// 创建一个更加基础的类
class Base{
// 将基础的方法都写在这个类中
}
// A1
class A1 extends Base{
public int fun1(int num1, int num2){
return num1-num2;
}
}
// B1
class B1 extends Base {
// B1需要使用A1的方法:
// 1. 组合
private A1 a = new A1();
// 调用A的方法
public int fun3(int num1, int num2){
return a.fun1(num1,num2);
}
public int fun1(int num1, int num2){
return num1+num2;
}
public int fun2(int a, int b){
return fun1(a,b)+9;
}
}
解决方案是编写一个更加基础的类Base来添加基础的方法,让AB都继承基础类Base。如果B中要使用A的方法就可以使用组合的方式:实例化A,使用实例化的对象去调用A中的方法。
2.5 开闭原则
是编程中最基础最重要的原则,一个软件实体应该对扩展开放(对提供方而言),对修改关闭(对使用方而言)。用抽象构建框架,用实现扩展细节。当软件变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码实现。遵循其它原则以及使用设计模式的目的就是遵循开闭原则。
案例:有一个绘制图形的类,根据类型绘制不同的图形。
/**
* @date 2020/8/14 14:34
* ocp 开闭原则
*/
public class Ocp01 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
// 绘图类
class GraphicEditor{
public void drawShape(Shape shape){
// 接收Shape 根据类型 绘制不同的形状
if (shape.m_type == 1){
drawRectangle(shape);
}
else if(shape.m_type == 2){
drawCircle(shape);
}else if (shape.m_type == 3){
drawTirRectangle(shape);
}
}
public void drawRectangle(Shape r){
System.out.println("矩形");
}
public void drawCircle(Shape r){
System.out.println("圆形");
}
public void drawTirRectangle(Shape shape){
System.out.println("三角形");
}
}
class Shape{
int m_type = 0;
}
class Rectangle extends Shape{
Rectangle(){
super.m_type = 1;
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
}
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
}
根据上面代码来说,如果想要绘制圆柱,那么就需要添加一个圆柱类来基础Shape类,而且要修改绘制方法里面的代码。有违ocp的对提供方可扩展,对调用方不能修改的原则。
改进:
/**
* @date 2020/8/14 14:48
*/
public class Ocp02 {
public static void main(String[] args) {
GraphicEditor1 graphicEditor = new GraphicEditor1();
graphicEditor.drawShape(new Rectangle1());
graphicEditor.drawShape(new Circle1());
graphicEditor.drawShape(new Triangle1());
graphicEditor.drawShape(new OtherGraphic());
}
}
// 绘图类
class GraphicEditor1{
public void drawShape(Shape1 shape){
shape.draw();
}
}
abstract class Shape1{
// 抽象方法
public abstract void draw();
}
class Rectangle1 extends Shape1{
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle1 extends Shape1{
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Triangle1 extends Shape1{
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
class OtherGraphic extends Shape1{
@Override
public void draw() {
System.out.println("绘制其它图形");
}
}
这样改进之后如果要新增图像,只需要将图形类继承shape类复写draw方法即可。遵循了ocp原则。
2.6 迪米特法则
核心是降低类之间的耦合!一个对象应该与其它对象保持最少的了解。 将实现写在自己的类中,不要将逻辑一类的写在其它的类中。
没有遵循迪米特法则:
/**
* @date 2020/8/14 19:28
* 迪米特法则
*/
public class Demeter01 {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
// 学校总部员工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 学院员
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工
class CollegeManager{
public List<CollegeEmployee> getAllEmployee(){
ArrayList<CollegeEmployee> collegeEmployees = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("学院员工id:"+i);
collegeEmployees.add(collegeEmployee);
}
return collegeEmployees;
}
}
// 管理学校总部员工
class SchoolManager{
public List<Employee> getAllEmployee(){
ArrayList<Employee> collegeEmployees = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
Employee employee = new Employee();
employee.setId("学校总部员工id:"+i);
collegeEmployees.add(employee);
}
return collegeEmployees;
}
// 完成输出学院和学校员工。
public void printAllEmployee(CollegeManager collegeManager){
// 这里的CollegeEmployee类并不是直接朋友,而是以成员变量的形式出现在这里。
List<CollegeEmployee> allEmployee = collegeManager.getAllEmployee();
System.out.println("-------学院员工-----");
for (CollegeEmployee collegeEmployee : allEmployee) {
System.out.println(collegeEmployee.getId());
}
List<Employee> allEmployee1 = this.getAllEmployee();
System.out.println("----学校总部员工-----");
for (Employee employee : allEmployee1) {
System.out.println(employee.getId());
}
}
}
遵循迪米特法则:
/**
* @date 2020/8/14 19:43
* 迪米特法则02
*/
public class Demeter02 {
public static void main(String[] args) {
SchoolManager1 schoolManager = new SchoolManager1();
schoolManager.printSchoolEmployee(new CollegeManager1());
}
}
// 学校总部员工
class Employee1{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 学院员
class CollegeEmployee1{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工
class CollegeManager1{
public List<CollegeEmployee1> getAllEmployee(){
ArrayList<CollegeEmployee1> collegeEmployees = new ArrayList<CollegeEmployee1>();
for (int i = 0; i < 10; i++) {
CollegeEmployee1 collegeEmployee = new CollegeEmployee1();
collegeEmployee.setId("学院员工id:"+i);
collegeEmployees.add(collegeEmployee);
}
return collegeEmployees;
}
public void printCollegeEmployee(){
List<CollegeEmployee1> allEmployee = this.getAllEmployee();
System.out.println("-------学院员工-----");
for (CollegeEmployee1 collegeEmployee : allEmployee) {
System.out.println(collegeEmployee.getId());
}
}
}
// 管理学校总部员工
class SchoolManager1{
public List<Employee1> getAllEmployee(){
ArrayList<Employee1> collegeEmployees = new ArrayList<Employee1>();
for (int i = 0; i < 5; i++) {
Employee1 employee = new Employee1();
employee.setId("学校总部员工id:"+i);
collegeEmployees.add(employee);
}
return collegeEmployees;
}
// 完成输出学院和学校员工。
public void printSchoolEmployee(CollegeManager1 collegeManager){
collegeManager.printCollegeEmployee();
List<Employee1> allEmployee1 = this.getAllEmployee();
System.out.println("----学校总部员工-----");
for (Employee1 employee : allEmployee1) {
System.out.println(employee.getId());
}
}
}
2.7 合成复用原则
尽量使用合成/聚合的方式,而不是继承。
案例:B类需要使用A类的方法。
解决方案:
- 继承:可以直接复写和使用A类方法,但是B对A的耦合高!
- 依赖:在B类中的方法里面传递A的参数
public void getA(A a){ System.out.println("依赖"); }
。 - 聚合:在B中添加A类型的属性
private A a;
。 - 合成: 在B中添加A的对象实例
A a = new A();
。
to do…
2.8 总结
设计原则的核心思想:
- 找出应用之间可能需要变化的地方,把他们与不需要变化的地方分开;
- 针对接口编程;
- 解耦合!