基本概述
一.什么是设计模式
- 在软件工程中,设计模式是对软件设计中普遍存在的各种问题所提出的一些通用解决方案,这个术语是由艾丽希·伽玛等人在1990年代从建筑设计领域引入到计算机科学来的。
- 设计模式是人们在面对同类型的软件工程设计问题时所总结出的一些有用经验,把这些经验构成了一种思维模式,注意模式不是代码,而是某类问题的通用设计解决方案。
- 我们知道软件工程方法学的三要素分别是:过程、工具和方法,而设计模式就好比是具体的方法和工具,它的主要目的是使软件工程的设计在维护性、扩展性、变化性、复杂度等方面得到一个很好的提升优化。
二.设计模式的好处
- 能够提高代码的重用性
- 能够提高代码的灵活性
- 能够提高代码的可读性
- 能够提高系统的扩展性
- 能够提高系统的健壮性
- 能够提高系统的稳定性
- 能够提高软件的可维护性
- 能够提高系统的开发效率
三.设计模式的基本原则
1.开闭原则
开闭原则就是说对扩展开放,对修改关闭,当软件的需求需要改变时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。它也是最基础、最重要的设计原则。
-
代码示例
违背开闭原则
public class A{ public static void main(String[] args){ DrawShape d = new DrawShape(); d.draw(new Triangle()); d.draw(new Circle()); } } class Shape{ int shapeType; } class Triangle extends Shape{ public Triangle(){ super.shapeType = 1; } } class Circle extends Shape{ public Triangle(){ super.shapeType = 2; } } class DrawShape{ public void draw(Shape shape){ if(shape.shapeType == 1){ drawTriangle(); }else if(shape.shapeType == 2){ drawCircle(); } } private void drawTriangle(){ System.out.println("画三角形"); } private void drawCircle(){ System.out.println("画圆形"); } }
遵循开闭原则
public class A{ public static void main(String[] args){ DrawShape d = new DrawShape(); d.draw(new Triangle()); d.draw(new Circle()); } } abstract class Shape{ public abstract void draw(){ System.out.println("画默认的图形"); } } class Triangle extends Shape{ public void draw(){ System.out.println("画三角形"); } } class Circle extends Shape{ public void draw(){ System.out.println("画圆形"); } } class DrawShape{ public void draw(Shape shape){ shape.draw(); } }
-
作用优点
- 能够使应用程序更易于维护和扩展
2.里氏替换原则
里氏替换原则就是说继承必须确保父类所拥有的性质在子类中仍然成立,即在基类出现的地方,其子类一定可以出现,子类可以扩展基类的功能,但是尽量不要重写基类的功能,不过可以通过聚合、组合、依赖等关系解决此问题。
-
代码示例
违背里氏替换
public class A{ public static void main(String[] args){ B b = new B(); System.out.println("5 + 6 =" + b.fun1()); System.out.println("5 * 6 =" + b.fun2()); } } class A{ public int fun1(int a, int b){ return a + b; } } class B extends A{ public int fun1(int a, int b){ return a - b; } public int fun2(int a, int b){ return a * b; } }
遵循里氏替换
public class A{ public static void main(String[] args){ B b = new B(); System.out.println("5 + 6 =" + b.fun3()); System.out.println("5 * 6 =" + b.fun2()); } } class Base(){ } class A extends Base{ public int fun1(int a, int b){ return a + b; } } class B extends Base{ private A a = new A(); public int fun1(int a, int b){ return a - b; } public int fun2(int a, int b){ return a * b; } public int fun3(int a, int b){ return a.fun1(a, b); } }
-
作用优点
- 可以规范我们在正确的地方使用继承,而不至于造成继承的使用泛滥
3.依赖倒置原则
-
依赖倒置原则是实现开闭原则的基础,就是说当我们在编写面向对象的应用程序时,我们需要面向接口或抽象类编程, 而不具体的依赖某个实现类,高层模块不应该依赖底层模块,抽象不应该依赖于细节。
-
代码示例
违背依赖倒置
public class A{ public static void main(String[] args){ Person p = new Person(); p.receiveMessage(new Email()); p.receiveMessage(new Phone()); } } class Email{ public String getInfo(){ return "邮件信息"; } } class Phone{ public String getInfo(){ return "手机短信"; } } class Person{ public void receiveMessage(Email email){ System.out.println(email.getInfo()); } public void receiveMessage(Phone phone){ System.out.println(phone.getInfo()); } }
遵循依赖倒置
public class A{ public static void main(String[] args){ Person p = new Person(); p.receiveMessage(new Email()); p.receiveMessage(new Phone()); } } interface Message{ String getInfo(); } class Email implements Message{ public String getInfo(){ return "邮件信息"; } } class Phone implements Message{ public String getInfo(){ return "手机短信"; } } class Person{ public void receiveMessage(Message message){ System.out.println(message.getInfo()); } }
-
作用优点
- 提高了系统的稳定性
- 提高了系统的可维护性
- 提高了系统的可扩展性
-
依赖关系传递方式
-
通过接口传递
interface Message{ void getInfo(Mechain mechain); } interface Mechain{ void do(); } class Person implements Message{ public void getInfo(Mechain mechain){ System.out.println(mechain.do()); } }
-
通过构造器传递
interface Message{ void getInfo(); } interface Mechain{ void do(); } class Person implements Message{ private Mechain mechain; public Person(Mechain mechain){ this.mechain = mechain; } public void getInfo(){ System.out.println(this.mechain.do()); } }
-
通过setter器传递
interface Message{ void getInfo(); } interface Mechain{ void do(); } class Person implements Message{ private Mechain mechain; public void getInfo(){ System.out.println(this.mechain.do()); } public setMechain(Mechain mechain){ this.mechain = mechain; } }
-
-
4.单一职责原则
单一职责原则就是说一个类应该有且仅有一个能够引起它变化的原因,也就是只负责一项职责,否则此类应该进行拆分。为什么一个类不允许承担多项职责,因为如果其中一个职责进行了修改,那么其它职责可能也会跟着修改,也就有可能导致其它职责的执行错误。
-
代码示例
违背单一职责
public class A{ public static void main(String[] args){ Animal a1 = new Animal(); a1.run("斑马"); a1.run("鲸鱼"); a1.run("老鹰"); } } class Animal{ public void run(String name){ System.out.println(name + "在地上跑"); } }
遵循单一职责
public class A{ public static void main(String[] args){ RoadAnimal a1 = new RoadAnimal(); a1.run("斑马"); AirAnimal a2 = new AirAnimal(); a2.run("老鹰"); WaterAnimal a3 = new WaterAnimal(); a3.run("鲸鱼"); } } class RoadAnimal{ public void run(String name){ System.out.println(name + "在地上跑"); } } class AirAnimal{ public void run(String name){ System.out.println(name + "在天空飞"); } } class WaterAnimal{ public void run(String name){ System.out.println(name + "在水里游"); } }
-
作用优点
- 降低了代码的复杂度
- 降低代码变更的风险
- 提高了代码的可读性
- 增强了系统的高内聚低耦合
5.接口隔离原则
接口隔离原则就是说当一个接口的功能和职责太多时,我们需要将这个大接口分割成若干小接口,每一个小接口只服务于其对应客户端的相关功能,不应该让客户端去依赖那些不需要使用的功能。
-
代码示例
违背接口隔离
public class Example{ public static void main(String[] args){ A a = new A(); // A依赖于B,但只使用了方法1、2、3,方法4和5造成浪费 a.execute1(new B()); a.execute2(new B()); a.execute3(new B()); C c = new C(); // C依赖于D,但只使用了方法1、4、5,方法2和3造成浪费 c.exceute1(new D()); c.exceute4(new D()); c.exceute5(new D()); } } interface Interface1{ void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } class B implements Interface1{ public void operation1(){} public void operation2(){} public void operation3(){} public void operation4(){} public void operation5(){} } class D implements Interface1{ public void operation1(){} public void operation2(){} public void operation3(){} public void operation4(){} public void operation5(){} } class A{ public void execute1(Interface1 i1){ i1.operation1(); } public void execute2(Interface1 i1){ i1.operation2(); } public void execute3(Interface1 i1){ i1.operation3(); } } class C{ public void execute1(Interface1 i1){ i1.operation1(); } public void execute4(Interface1 i1){ i1.operation4(); } public void execute5(Interface1 i1){ i1.operation5(); } }
遵循接口隔离
public class Example{ public static void main(String[] args){ A a = new A(); a.execute1(new B()); a.execute2(new B()); a.execute3(new B()); C c = new C(); c.exceute1(new D()); c.exceute4(new D()); c.exceute5(new D()); } } interface Interface1{ void operation1(); } interface Interface2{ void operation2(); void operation3(); } interface Interface3{ void operation4(); void operation5(); } class B implements Interface1,Interface2{ public void operation1(){} public void operation2(){} public void operation3(){} } class D implements Interface1,Interface3{ public void operation1(){} public void operation4(){} public void operation5(){} } class A{ public void execute1(Interface1 i1){ i1.operation1(); } public void execute2(Interface2 i2){ i2.operation2(); } public void execute3(Interface2 i2){ i2.operation3(); } } class C{ public void execute1(Interface1 i1){ i1.operation1(); } public void execute4(Interface3 i3){ i3.operation4(); } public void execute5(Interface3 i3){ i3.operation5(); } }
-
作用优点
- 避免一个接口中包含很多不同的职责,让每个接口的职责比较分明
- 增强了系统的高内聚低耦合
6.合成复用原则
合成复用原则就是说当我们需要复用一些系统的代码的时候,应该优先考虑组合或聚合的方式实现,其次再考虑使用继承的方式实现。 因为如果一个父类中的功能太多,而我们只想复用其中一小部分功能,那么继承就造成了不必要的麻烦。
-
代码示例
违背合成复用
public class A{ public static void main(String[] args){ Bird bird = new Bird(); bird.talk(); bird.eat(); bird.fly(); } } class Action{ public void eat(){ System.out.println("吃饭"); } public void fly(){ System.out.println("天上飞"); } public void swim(){ System.out.println("水里游"); } public void run(){ System.out.println("地上跑"); } } class Bird extends Action{ public void talk(){ System.out.println("叫"); } public void eat(){ super.eat(); } public void fly(){ super.fly(); } }
遵循合成复用
public class A{ public static void main(String[] args){ Bird bird = new Bird(); bird.talk(); bird.eat(); bird.fly(); } } class Action{ public void eat(){ System.out.println("吃饭"); } public void fly(){ System.out.println("天上飞"); } public void swim(){ System.out.println("水里游"); } public void run(){ System.out.println("地上跑"); } } class Bird{ private Action action = new Action(); public void talk(){ System.out.println("叫"); } public void eat(){ action.eat(); } public void fly(){ action.fly(); } }
-
作用优点
- 能够增强系统的维护性
- 能够提高代码的可读性
7.迪米特法则
迪米特法则又称 “最少知道原则” , 也就是说 “只与朋友交谈,不与陌生人交谈”, 一个类对自己依赖的类知道的越少越好,尽量将实现逻辑封装在类的内部,对外提供public方法而不泄露内部信息。这句话的含义是如果两个类之间无需直接通信,那么就不应当发生直接的相互调用,而是通过提供另一种方法进行转发通信。如果一个类包含有其它类对象的依赖,那么此对象以成员变量、方法参数、方法返回值的形式出现就是朋友,此对象以局部变量的形式出现就是陌生人。
-
代码示例
违背迪米特法则
public class A{ public static void main(String[] args){ School school = new School(); school.printAllName(new TeacherManager(), new StudentManager()); } } class Teacher{} class Student{} class TeacherManager{ public List<Teacher> getTeachers(){ return new ArrayList<Teacher>(); } } class StudentManager{ public List<Student> getStudents(){ return new ArrayList<Student>{}; } } class School{ public void printAllName(TeacherManager tm, StudentManager sm){ List<Teacher> teachers = tm.getTeachers(); teachers.foreach(System.out::println); List<Student> students = sm.getStudents(); students.foreach(System.out::println); } }
遵循迪米特法则
public class A{ public static void main(String[] args){ School school = new School(); school.printAllName(new TeacherManager(), new StudentManager()); } } class Teacher{} class Student{} class TeacherManager{ private List<Teacher> getTeachers(){ return new ArrayList<Teacher>(); } public void printTeachers(){ List<Teacher> teachers = this.getTeachers(); teachers.foreach(System.out::println); } } class StudentManager{ private List<Student> getStudents(){ return new ArrayList<Student>{}; } public void printStudents(){ List<Student> students = this.getStudents(); students.foreach(System.out::println); } } class School{ public void printAllName(TeacherManager tm, StudentManager sm){ tm.printTeachers(); sm.printStudents(); } }
-
作用优点
- 降低系统的耦合性
- 减少系统之间的关联程度
四.设计模式有哪些分类
1.创建型模式
创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用进行一个分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不需要清楚其中具体的实现细节,这也使得整个系统的设计更加符合单一职责原则。
名称 | 核心作用 |
---|---|
单例模式(Singleton) | 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点 |
工厂模式(Factory) | 在创建一个对象时不向用户展示内部细节,而是提供一个创建对象的通用接口 |
抽象工厂模式(Abstract Factory) | 提供一个统一的接口,用于创建相关的对象家族 |
建造者模式(Builder) | 封装一个对象的建造过程,并按照这个过程创建相应的对象 |
原型模式(Prototype) | 使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象 |
2.结构型模式
结构型模式描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
其中又可以分为类结构型模式和对象结构型模式:
类结构型模式主要关心的是类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
对象结构型模式主要关心的是类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。
名称 | 核心作用 |
---|---|
适配器模式(Adapter) | 把一个类接口转换成另一个类所需要的接口 |
桥接模式(Bridge) | 将抽象与实现分离开来,使它们可以独立变化 |
装饰器模式(Director) | 提供一种内部方法,使得能够为对象动态的添加功能 |
组合模式(Composite) | 将对象组合成树形结构来表示“整体/部分”层次关系 |
外观模式(Facade) | 提供一个统一的接口,用来访问内部的其它接口,从而让子系统更容易使用 |
享元模式(Flyweight) | 利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的 |
代理模式(Proxy) | 控制对某一类对象的直接访问,采用代理的手段间接访问 |
3.行为型模式
行为型模式是对在不同的对象之间划分责任的抽象化方法,不仅仅关注类和对象的结构,而且重点关注类和对象之间的相互作用。
其中又可以分为类行为型模式和对象行为型模式:
类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。
名称 | 核心作用 |
---|---|
命令模式(Command) | 将命令封装成对象,可以用来参数化其它对象 |
迭代器模式(Iterator) | 提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部细节 |
观察者模式(Observer) | 当一个对象状态改变时,它的所有依赖对象都会收到通知并且自动更新状态 |
中介者模式(Mediator) | 集中相关对象之间复杂的沟通和控制方式 |
备忘录模式(Memento) | 获得对象的内部状态,从而在需要时可以将对象恢复到最初状态 |
模板方法模式(Template) | 通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构 |
解释器模式(Interpreter) | 为语言创建解释器,通常由语言的语法和语法分析来定义 |
状态模式(State) | 允许对象在内部状态发生改变时改变它自身的行为 |
责任链模式(Responsibility) | 使多个对象都有机会处理同一个请求,从而避免请求的发送者和接收者之间的耦合关系 |
策略模式(Strategy) | 定义一系列实现方法,在需要的时候调用相应的实现方案 |
访问者模式(Visitor) | 为一个对象结构增加新的能力 |