Java设计模式|Java设计模式之概述与七大设计原则

1.设计模式目的 【Java设计模式|Java设计模式之概述与七大设计原则】? 编写软件过程中,程序员面临着来自耦合性、内聚性、可维护性、可扩展性、重用性、灵活性等多方面的挑战。而设计模式就是为了让软件具有更好的:

  • 代码重用性(相同功能的代码不用多次编写)
  • 可读性(编程规范,便于其他程序员的阅读和理解)
  • 可扩展性(当需要增加新的功能时非常方便)
  • 可靠性(当增加新的功能时,对原来的功能没有影响)
  • 使程序呈现高内聚、低耦合的特性
2.设计模式七大原则 2.1单一职责原则 2.1.1 基本介绍
? 一个类只负责一个职责。如果一个类包含两个职责,如职责A和职责B,则修改职责A时,有可能会导致职责B的执行出现错误。此时应该把该类细分,即把职责A和B分到两个不同的类。
2.1.2 例子
  • 一个类负责多个职责
package com.zipeng.principle.singleresponsibility; /** * @author: zipeng Li * 2021/4/1621:00 */ public class SingleResponsibility01 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("汽车"); vehicle.run("摩托车"); vehicle.run("飞机"); } }class Vehicle{ public void run(String name){ System.out.println(name + " 在公路上跑"); } }

汽车 在公路上跑 摩托车 在公路上跑 飞机 在公路上跑

  • 职责分解(类层次分解)
package com.zipeng.principle.singleresponsibility; /** * @author: zipeng Li * 2021/4/1621:19 */ public class SingleResponsibility02 { public static void main(String[] args) { RoleVehicle roleVehicle = new RoleVehicle(); roleVehicle.run("汽车"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); } }class RoleVehicle{ public void run(String name){ System.out.println(name + " 在公路上跑"); } }class AirVehicle{ public void run(String name){ System.out.println(name + " 在天空上飞"); } }

汽车 在公路上跑 飞机 在天空上飞

  • 职责分解(方法级单一职责)
package com.zipeng.principle.singleresponsibility; /** * @author: zipeng Li * 2021/4/1621:22 */ public class SingleResponsibility03 { public static void main(String[] args) { Vehicle03 vehicle03 = new Vehicle03(); vehicle03.runRole("汽车"); vehicle03.runAir("飞机"); } }class Vehicle03{ public void runRole(String name){ System.out.println(name + " 在公路上跑"); }public void runAir(String name){ System.out.println(name + " 在天空上飞"); } }

汽车 在公路上跑 飞机 在天空上飞

2.1.3 细节
  • 一个类只负责一个职责,可降低类复杂度
  • 提高类的可维护性和可读性
  • 降低变更引起的风险
  • 只有逻辑足够简单,才可以在代码级别违反单一职责原则;只有类中的方法足够少,才可以在方法级别上维护单一职责原则。
2.2接口隔离原则 2.2.1 基本介绍
? 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口之上
2.2.2 例子
  • 没有遵守接口隔离原则,类B、D需要额外实现自己不需要实现的方法
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

  • 代码
package com.zipeng.principle.interfacesegregation; /** * @author: zipeng Li * 2021/4/1622:39 */ public class InterfaceSegregation01 { public static void main(String[] args) { Interface1 interface1 = new B(); Interface1 interface2 = new D(); A a = new A(); C c = new C(); a.oper01(interface1); a.oper02(interface1); a.oper03(interface1); c.oper01(interface2); c.oper04(interface2); c.oper05(interface2); } }interface Interface1 { void oper01(); void oper02(); void oper03(); void oper04(); void oper05(); }class B implements Interface1 {@Override public void oper01() { System.out.println("B 实现了 oper01"); }@Override public void oper02() { System.out.println("B 实现了 oper02"); }@Override public void oper03() { System.out.println("B 实现了 oper03"); }@Override public void oper04() { System.out.println("B 实现了 oper04"); }@Override public void oper05() { System.out.println("B 实现了 oper05"); } }class D implements Interface1 {@Override public void oper01() { System.out.println("D 实现了 oper01"); }@Override public void oper02() { System.out.println("D 实现了 oper02"); }@Override public void oper03() { System.out.println("D 实现了 oper03"); }@Override public void oper04() { System.out.println("D 实现了 oper04"); }@Override public void oper05() { System.out.println("D 实现了 oper05"); } }class A { public void oper01(Interface1 inter) { inter.oper01(); }public void oper02(Interface1 inter) { inter.oper02(); }public void oper03(Interface1 inter) { inter.oper03(); } }class C { public void oper01(Interface1 inter) { inter.oper01(); }public void oper04(Interface1 inter) { inter.oper04(); }public void oper05(Interface1 inter) { inter.oper05(); } }

B 实现了 oper01 B 实现了 oper02 B 实现了 oper03 D 实现了 oper01 D 实现了 oper04 D 实现了 oper05

  • 进行接口拆分,实现接口隔离
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

package com.zipeng.principle.interfacesegregation; /** * @author: zipeng Li * 2021/4/1816:40 */ public class InterfaceSegregation02 { public static void main(String[] args) { AA aa = new AA(); CC cc = new CC(); aa.oper01(new BB()); aa.oper02(new BB()); aa.oper03(new BB()); cc.oper01(new DD()); cc.oper04(new DD()); cc.oper05(new DD()); } }interface Interface01 { void oper01(); }interface Interface02{ void oper02(); void oper03(); }interface Interface03{ void oper04(); void oper05(); }class BB implements Interface01, Interface02 {@Override public void oper01() { System.out.println("B 实现了 oper01"); }@Override public void oper02() { System.out.println("B 实现了 oper02"); }@Override public void oper03() { System.out.println("B 实现了 oper03"); }}class DD implements Interface01, Interface03 {@Override public void oper01() { System.out.println("D 实现了 oper01"); }@Override public void oper04() { System.out.println("D 实现了 oper04"); }@Override public void oper05() { System.out.println("D 实现了 oper05"); } }class AA { public void oper01(Interface01 inter) { inter.oper01(); }public void oper02(Interface02 inter) { inter.oper02(); }public void oper03(Interface02 inter) { inter.oper03(); } }class CC { public void oper01(Interface01 inter) { inter.oper01(); }public void oper04(Interface03 inter) { inter.oper04(); }public void oper05(Interface03 inter) { inter.oper05(); }}

B 实现了 oper01 B 实现了 oper02 B 实现了 oper03 D 实现了 oper01 D 实现了 oper04 D 实现了 oper05

2.3依赖倒转原则 2.3.1 基本介绍
  • 高层模块不依赖低层模块,二者都应该依赖抽象(接口 或 抽象类)
  • 抽象不依赖细节,细节依赖抽象
  • 中心思想是面向接口编程
  • 设计理念:抽象比细节更具稳定性,以抽象为基础的架构比以细节为基础的架构更能应对变化。
  • 使用接口或者抽象类的目的是制定好规范,而不涉及任何具体的操作。把所有的细节实现都交给实现类完成。
2.3.2 实例
  • 类与类之间直接依赖的情况
package com.zipeng.principle.dependenceinversionprinciple; /** * @author: zipeng Li * 2021/4/1818:13 */ public class DependenceInversionPrinciple { public static void main(String[] args) { Person person = new Person(); person.receiveMsg(new EMail()); } }class Person{ public void receiveMsg(EMail eMail){ eMail.getMsg(); } }class EMail{ public void getMsg(){ System.out.println("接收到e-mail的消息"); } }

接收到e-mail的消息

  • 通过接口实现依赖倒转
package com.zipeng.principle.dependenceinversionprinciple; /** * @author: zipeng Li * 2021/4/1818:13 */ public class DependenceInversionPrinciple { public static void main(String[] args) { Person person = new Person(); person.receiveMsg(new EMail()); person.receiveMsg(new WeiXin()); } }class Person{ public void receiveMsg(IReceive receive){ receive.getMsg(); } }interface IReceive{ void getMsg(); }class EMail implements IReceive{ @Override public void getMsg(){ System.out.println("接收到e-mail的消息"); } }class WeiXin implements IReceive{@Override public void getMsg() { System.out.println("接收到微信消息"); } }

接收到e-mail的消息 接收到微信消息

2.2.3 实现方式
  • 接口传递方式
    Java设计模式|Java设计模式之概述与七大设计原则
    文章图片

package com.zipeng.principle.dependenceinversionprinciple; /** * @author: zipeng Li * 2021/4/1818:42 */ public class ImplType { public static void main(String[] args) { ChangHong changHong = new ChangHong(); IOpenAndClose openAndClose = new OpenAndClose(); openAndClose.open(changHong); MeiDi meiDi = new MeiDi(); openAndClose.open(meiDi); } } // 方式一:通过接口传递实现依赖 interface IOpenAndClose{ void open(ITV tv); } interface ITV{ void play(); } class OpenAndClose implements IOpenAndClose{@Override public void open(ITV tv) { tv.play(); } } class ChangHong implements ITV{@Override public void play() { System.out.println("长虹电视机,打开"); } } class MeiDi implements ITV{@Override public void play() { System.out.println("美的电视机打开"); } }

长虹电视机,打开 美的电视机打开

  • 构造方法传递方式
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

package com.zipeng.principle.dependenceinversionprinciple; /** * @author: zipeng Li * 2021/4/1818:42 */ public class ImplType { public static void main(String[] args) { ChangHong changHong = new ChangHong(); MeiDi meiDi = new MeiDi(); IOpenAndClose openAndClose = new OpenAndClose(changHong); openAndClose.open(); openAndClose = new OpenAndClose(meiDi); openAndClose.open(); } }// 方式二:通过构造方法传递依赖 interface IOpenAndClose{ void open(); } interface ITV{ void play(); } class OpenAndClose implements IOpenAndClose{ private ITV tv; public OpenAndClose(ITV tv) { this.tv = tv; }@Override public void open() { tv.play(); } } class ChangHong implements ITV{@Override public void play() { System.out.println("长虹电视机,打开"); } } class MeiDi implements ITV{@Override public void play() { System.out.println("美的电视机打开"); } }

长虹电视机,打开 美的电视机打开

  • setter方法传递方式
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

package com.zipeng.principle.dependenceinversionprinciple; /** * @author: zipeng Li * 2021/4/1818:42 */ public class ImplType { public static void main(String[] args) { ChangHong changHong = new ChangHong(); MeiDi meiDi = new MeiDi(); IOpenAndClose openAndClose = new OpenAndClose(); openAndClose.setITV(changHong); openAndClose.open(); openAndClose.setITV(meiDi); openAndClose.open(); } }// 方式三:通过setter方法传递依赖 interface IOpenAndClose{ void open(); void setITV(ITV tv); } interface ITV{ void play(); } class OpenAndClose implements IOpenAndClose{ private ITV tv; @Override public void open() { tv.play(); }@Override public void setITV(ITV tv) { this.tv = tv; } } class ChangHong implements ITV{@Override public void play() { System.out.println("长虹电视机,打开"); } } class MeiDi implements ITV{@Override public void play() { System.out.println("美的电视机打开"); } }

长虹电视机,打开 美的电视机打开

2.4里氏替换原则 对继承的思考:
  • 继承包含一个含义:父类中已经实现的方法实际上是在设定规范和契约,虽然它不强制要求所有子类必须遵循这些契约,但若子类对这些实现的方法进行任意的重写,就会对整个继承体系造成破坏
  • 继承有弊端,如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合。若一个类被其他类继承,则当这个类需要修改时,必须考虑到所有子类。且父类修改后,所有涉及到子类的功能都可能产生故障
  • 在编程中正确使用继承需要遵守里氏替换原则
2.4.1 里氏替换介绍
  • 所有引用基类的地方都必须能透明地使用其子类对象。而实现的方式就是在子类中不要重写基类的方法。
  • 里氏替换原则告诉我们,继承实际上让两个类耦合性增强。在适当的情况下,可以通过聚合、组合、依赖来解决问题。
2.4.2 例子
package com.zipeng.principle.lishireplace; /** * @author: zipeng Li * 2021/4/2421:22 */ public class LishiReplace { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11,3)); System.out.println("1-8=" + a.func1(1,8)); System.out.println("==================="); B b = new B(); System.out.println("11-3=" + b.func1(11,3)); System.out.println("1-8=" + b.func1(1,8)); System.out.println("11+3+9=" + b.fun2(11,3)); } }class A{ public int func1(int num1, int num2){ return num1 - num2; } }class B extends A{ @Override public int func1(int num1, int num2) { return num1 + num2; }public int fun2(int num1, int num2){ return func1(num1, num2) + 9; } }

11-3=8 1-8=-7 =================== 11-3=14 1-8=9 11+3+9=23

2.4.3 解决方法
  • 上述例子中,原来正常相减的功能发生了错误,原因是类B无意重写了父类的方法,造成原有功能出现错误。在实际编程中,常常会通过重写父类方法完成新的功能。这样虽然简单,但是整个继承体系的复用性会变得很差,特别是运行多态比较频繁的时候。
  • 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉。采用依赖、聚合、组合等关系代替。
package com.zipeng.principle.lishireplace.improve; /** * @author: zipeng Li * 2021/4/2521:28 */ public class LishiReplace { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11,3)); System.out.println("1-8=" + a.func1(1,8)); System.out.println("==================="); B b = new B(); System.out.println("11+3=" + b.func1(11,3)); System.out.println("1+8=" + b.func1(1,8)); System.out.println("11+3+9=" + b.fun2(11,3)); // 使用组合 System.out.println("11-3=" + b.func3(11,3)); } }class Base{}class A extends Base{ public int func1(int num1, int num2){ return num1 - num2; } }class B extends Base {private A a = new A(); public int func1(int num1, int num2) { return num1 + num2; }public int fun2(int num1, int num2){ return func1(num1, num2) + 9; }public int func3(int num1, int num2){ return this.a.func1(num1, num2); } }

11-3=8 1-8=-7 =================== 11+3=14 1+8=9 11+3+9=23 11-3=8

2.5开闭原则ocp 2.5.1 基本介绍
  • 开闭原则是编程中最基础、最重要的设计原则
  • 一个软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
  • 编程中遵循其它原则以及使用设计模式的目的就是要实现开闭原则
2.5.2 不遵守ocp的情况
package com.zipeng.principle.ocp; /** * @author: zipeng Li * 2021/4/2521:47 */ public class Draw { public static void main(String[] args) { Drawer drawer = new Drawer(); drawer.draw(new Rectangle()); drawer.draw(new Circle()); } }// 绘图类 class Drawer{ public void draw(Shape s){ if(s.type == 1){ drawRectangle(s); }else if(s.type == 2){ drawCircle(s); } }public void drawRectangle(Shape s){ System.out.println("绘制矩形"); }public void drawCircle(Shape s){ System.out.println("绘制圆形"); } }class Shape{ int type; }class Rectangle extends Shape{ public Rectangle() { type = 1; } }class Circle extends Shape{ public Circle() { type = 2; } }

绘制矩形 绘制圆形

分析:
优点:比较好理解,简单易操作
缺点:违反设计模式的ocp原则。如需要新增加一个图形种类时,需要修改多处代码。
2.5.3 改进
思路:把Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现。当有新的图形加入时,只需要让新的图形继承Shape,并实现draw方法。
package com.zipeng.principle.ocp.inprove; /** * @author: zipeng Li * 2021/4/2522:06 */ public class Draw { public static void main(String[] args) { Drawer drawer = new Drawer(); drawer.draw(new Rectangle()); drawer.draw(new Circle()); } }// 绘图类 class Drawer{ public void draw(Shape s){ s.draw(); } }abstract class Shape{ public abstract void draw(); }class Rectangle extends Shape {@Override public void draw() { System.out.println("绘制矩形"); } }class Circle extends Shape {@Override public void draw() { System.out.println("绘制圆形"); } }

2.6迪米特法则 2.6.1 基本介绍
  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大
  • 迪米特法则(Demeter Principle)又名最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄漏任何信息
  • 迪米特法则还有一个更简单的定义:只与直接的朋友通信
  • 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,称出现成员变量、方法参数、方法返回值中的类为直接的朋友。而出现局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
2.6.2 违反迪米特法则例子
package com.zipeng.principle.demeter; import java.util.ArrayList; import java.util.List; /** * @author: zipeng Li * 2021/4/2615:42 */public class Demeter { public static void main(String[] args) { //创建了一个 SchoolManager 对象 SchoolManager schoolManager = new SchoolManager(); //输出学院的员工id 和学校总部的员工信息 schoolManager.printAllEmployee(new CollegeManager()); }}//学校总部员工类 class Employee { private String id; public void setId(String id) { this.id = id; }public String getId() { return id; } }//学院的员工类 class CollegeEmployee { private String id; public void setId(String id) { this.id = id; }public String getId() { return id; } }//管理学院员工的管理类 class CollegeManager { //返回学院的所有员工 public List getAllEmployee() { List list = new ArrayList(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id= " + i); list.add(emp); } return list; } }//学校管理类//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager //CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则 class SchoolManager { //返回学校总部的员工 public List getAllEmployee() { List list = new ArrayList(); for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list Employee emp = new Employee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; }//该方法完成输出学校总部和学院员工信息(id) void printAllEmployee(CollegeManager sub) {//分析问题 //1. 这里的 CollegeEmployee 不是SchoolManager的直接朋友 //2. CollegeEmployee 是以局部变量方式出现在 SchoolManager //3. 违反了 迪米特法则//获取到学院员工 List list1 = sub.getAllEmployee(); System.out.println("------------学院员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } //获取到学校总部员工 List list2 = this.getAllEmployee(); System.out.println("------------学校总部员工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }

------------学院员工------------ 学院员工id= 0 学院员工id= 1 学院员工id= 2 学院员工id= 3 学院员工id= 4 学院员工id= 5 学院员工id= 6 学院员工id= 7 学院员工id= 8 学院员工id= 9 ------------学校总部员工------------ 学校总部员工id= 0 学校总部员工id= 1 学校总部员工id= 2 学校总部员工id= 3 学校总部员工id= 4

2.6.3 改进
  • 上述设计的问题在于SchollManager中,CollegeEmployee类并不是SchoolManager类的直接朋友
  • 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
  • 改进如下:
//管理学院员工的管理类 class CollegeManager { //返回学院的所有员工 public List getAllEmployee() { List list = new ArrayList(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id= " + i); list.add(emp); } return list; }public void printAllEmployee(){ //获取到学院员工 List list1 = this.getAllEmployee(); System.out.println("------------学院员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } } }//学校管理类 class SchoolManager { //返回学校总部的员工 public List getAllEmployee() { List list = new ArrayList(); for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list Employee emp = new Employee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; }//该方法完成输出学校总部和学院员工信息(id) void printAllEmployee(CollegeManager sub) {sub.printAllEmployee(); // 把这里的代码段封装到CollegeManager内//获取到学校总部员工 List list2 = this.getAllEmployee(); System.out.println("------------学校总部员工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }

2.6.4 注意事项
  • 迪米特法则的核心是降低类之间的耦合
  • 迪米特法则只是要求降低类间(对象间)耦合的关系,并不是要求完全没有依赖
2.7合成复用原则 2.7.1 基本介绍
? 合成复用原则要求:尽量使用合成/聚合方式,而不是使用继承
2.7.2 图解
【不好的方式】
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

【可选择的三种解决方案】
Java设计模式|Java设计模式之概述与七大设计原则
文章图片

2.8 总结
设计原则核心思想:
  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 为了交互对象之间的松耦合设计而努力
3. 设计模式概述 3.1 掌握设计模式的层次
  • 第一层:刚开始学编程不久,听说过设计模式
  • 第二层:有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但是自己却不知道
  • 第三层:学习过设计模式,发现自己已经在使用,并且发现了一些新的模式很好用
  • 第四层:阅读了很多框架源码,在其中看到了别人的设计模式,并且能够领会设计模式的精妙和带来的好处
  • 第五层:编程过程中,自然而然运用了设计模式。
3.2 设计模式介绍
  • 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验
  • 模式不是代码,而是某类问题的通用解决方案
  • 设计模式代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的
  • 设计模式的本质是提高软件的维护性、通用性和扩展性,并降低软件的复杂度
  • 设计模式不局限于某一种编程语言
3.3 设计模式的类型 设计模式分为三种类型,共23种:
  • 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
  • 结果型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)
后序
  • 我是一名大三本科生,专业是软件工程【一本】。目前,正在准备找实习以及秋招,意向岗位是Java后端开发工程师。为此,在码云托管了一个项目,以整理我所有所学知识。涉及内容:计算机网络、操作系统、Java基础、主流Java后端框架、设计模式、Web前端框架等内容。欢迎大家访问我的开源项目编程之路
  • 码云地址:https://gitee.com/alizipeng/the-way-of-programming
  • 以上内容均记载在我的开源项目中

    推荐阅读