23种设计模式——前言
1.设计模式简介
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
2.什么是 GOF
在1994年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
-
对接口编程而不是对实现编程
-
优先使用对象组合而不是继承。
3.设计模式目的
设计模式的目的是为了提高代码重用性、可读性、可扩展性、可靠性,使得程序呈现出高内聚、低耦合的特性。
代码重用性:相同功能的代码无需多次重复编写
可读性:编程按照一定规范,便于其他程序员的阅读和理解
可扩展性:当我们可以非常方便简单地增加新功能
可靠性:我们增加或删除部分功能时,对原有系统其他功能没有影响
高内聚、低耦合:
4.设计模式的使用
设计模式在软件开发中的两个主要用途
1.开发人员的共同平台
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。
2.最佳的实践
设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
5.设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
1.创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
2.结构型模式
这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。
-
适配器模式(Adapter Pattern)
-
桥接模式(Bridge Pattern)
-
过滤器模式(Filter、Criteria Pattern)
-
组合模式(Composite Pattern)
-
装饰器模式(Decorator Pattern)
-
外观模式(Facade Pattern)
-
享元模式(Flyweight Pattern)
-
代理模式(Proxy Pattern)
3.行为型模式
这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。
-
责任链模式(Chain of Responsibility Pattern)
-
命令模式(Command Pattern)
-
解释器模式(Interpreter Pattern)
-
迭代器模式(Iterator Pattern)
-
中介者模式(Mediator Pattern)
-
备忘录模式(Memento Pattern)
-
观察者模式(Observer Pattern)
-
状态模式(State Pattern)
-
空对象模式(Null Object Pattern)
-
策略模式(Strategy Pattern)
-
模板模式(Template Pattern)
-
访问者模式(Visitor Pattern)
4.J2EE 模式
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
-
MVC 模式(MVC Pattern)
-
业务代表模式(Business Delegate Pattern)
-
组合实体模式(Composite Entity Pattern)
-
数据访问对象模式(Data Access Object Pattern)
-
前端控制器模式(Front Controller Pattern)
-
拦截过滤器模式(Intercepting Filter Pattern)
-
服务定位器模式(Service Locator Pattern)
-
传输对象模式(Transfer Object Pattern)
6.关系图
7.设计模式的优点
- 提供了一种共享的设计词汇和概念,使开发人员能够更好地沟通和理解彼此的设计意图。
- 提供了经过验证的解决方案,可以提高软件的可维护性、可复用性和灵活性。
- 促进了代码的重用,避免了重复的设计和实现。
- 通过遵循设计模式,可以减少系统中的错误和问题,提高代码质量。
8.设计模式的七大原则
1、单一职责原则
对于类来说,就是一个类应该只负责一项职责。
问题:交通工具
public class Main{
public static void main(String args[]){
Vehicle.run("飞机");//飞机在公路上跑
Vehicle.run("汽车");//飞机在公路上跑
Vehicle.run("人");//人在公路上跑
}
}
//交通工具
class Vehicle{
public static void run(String vehicle){
System.out.println(vehicle+"在公路上跑");
}
}
方案1:将交通工具类分解为陆地交通工具和空中交通工具(类级别)**
public class Main{
public static void main(String args[]){
Vehicle.run("飞机");//飞机在公路上跑
Vehicle.run("汽车");//飞机在天空上飞
Vehicle.run("人");//人在操场上跑
}
}
//交通工具
class RoadVehicle{
public static void run(String vehicle){
System.out.println(vehicle+"在公路上跑");
}
}
class FlyVehicle{
public static void run(String vehicle){
System.out.println(vehicle+"在天空上飞");
}
}
class Vehicle{
public static void run(String vehicle){
System.out.println(vehicle+"在操场上跑");
}
}
方案二:这种是在方法级别上遵守单一职责。(方法级别)
class Vehicle{
public void runAir(String vehicle){
System.out.println(vehicle+"在天空上运行");
}
public void runRoad(String vehicle){
System.out.println(vehicle+"在陆地上运行");
}
public void runPlayground(String vehicle){
System.out.println(vehicle+"在操场上运行");
}
}
2、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
问题:绘制三角形(新增图形会对源码进行修改)
public class Main{
public static void mian(Strings args[]){
new GraphicEditor().drawShape(new Rectangle());
new GraphicEditor().drawShape(new Circle());
}
}
class GraphicEditor{//绘图类
public void darwShape(Shap shap){
if(shap.type==rectange){
drawRectange();
}else if(shap.type==circle){
drawCircle();
}else if(){
//新添图形
}
}
public void drawRectange(){
System.out.println("绘制一个矩形");
}
public void drawCircle(){
System.out.println("绘制一个三角形");
}
}
class Shape{//基类
String type;
}
class Rectangle extends Shape{//矩形
Rectangle(){
super.type = rectange;
}
}
class Circle extends Shape{//圆形
Circle(){
super.type = circle;
}
}
方案:对基类进行抽象,绘制方法抽象(抽象)
public class Main{
public static void mian(Strings args[]){
new GraphicEditor().drawShape(new Rectangle());
new GraphicEditor().drawShape(new Circle());
}
}
class GraphicEditor{//绘制方法由子类实现
public void drawShape(Shape shape){
shape.draw();
}
}
abstract class Shape{//变为抽象类或接口
String type;
public abstract void draw();
}
class Rectangle extends Shape {
Rectangle(){
super.type = rectange;
}
@Override
public void draw() {
System.out.println("绘制一个矩形");
}
}
class Circle extends Shape {
Circle(){
super.type = circle;
}
@Override
public void draw() {
System.out.println("绘制一个三角形");
}
}
3、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而**基类与子类的继承关系**就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 所有引用基类的地方,必须能透明的使用其子类。
- 在继承时,子类尽量不要重写父类的方法。
- 继承会让两个类的耦合性增强,因此适当情况下,可以通过聚合、组合、依赖来解决问题。
4、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
-
高层模块不应该依赖底层模块,二者都应该依赖于抽象
-
抽象不应该依赖于细节,细节要依赖于抽象
-
抽象在Java中就是指接口或抽象类,细节就是具体的实现类
-
依赖倒转的中心思想就是面向接口编程
-
使用接口或抽象类的目的是为了制定好规范
方案一:通过接口实现依赖传递(具体类和具体类之间通过抽象类实现依赖)
public class Main{
public static void mian(Strings args[]){
Sony sony = new Sony();
Controller controller= new Controller();
controller.open(changHong);
}
}
interface OpenTV{
public void open(TV tv)
}
interface TV{
public void play();
}
class Controller implements OpenTV{
@Override
public void open(TV tv) {
tv.play();
}
}
class Sony implements TV{
@Override
public void play() {
System.out.println("欢迎收看《大耳朵图图》");
}
}
方案二:通过构造方法实现依赖传递(其他类保持不变)
public class Main{
public static void mian(Strings args[]){
Controller controller= new Controller(new Sony());
controller.open();
}
}
class Controller implements OpenTV{
private TV tv;
Controller(TV tv){
this.tv = tv;
}
public void open(){
tv.play();
}
}
方案三:通过setter实现依赖传递(其他类保持不变)
public class Main{
public static void mian(Strings args[]){
Controller controller= new Controller();
controller.setTV(new Sony());
controller.open();
}
}
class Controller implements OpenTV{
private TV tv;
setTV(TV tv){
this.tv = tv;
}
public void open(){
tv.play();
}
}
依赖倒转小结:
- 底层模块尽量都要有抽象类或接口,这样程序稳定性更好!
- 变量的声明类型尽量都是抽象类或接口,这样我们的变量引用和实际对象间就有一个缓冲,利于程序扩展和优化
5、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。(接口单一职责)
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
问题:一个接口有三个方法,类A、类B实现该接口,类C、类D依赖于该接口。
interface Interface1{
void method1();
void method2();
void method3();
}
//A B 实现 Interface1
class A implements Interface1{
@Override
public void method1() {}
@Override
public void method2() {}
@Override
public void method3() {}
}
class B implements Interface1{
@Override
public void method1() {}
@Override
public void method2() {}
@Override
public void method3() {}
}
//C D 依赖 Interface11
class C{
public void c_method1(Interface1 interface1){
interface1.method1();
}
public void c_method2(Interface1 interface1){
interface1.method2();
}
}
class D{
public void d_method3(Interface1 interface1){
interface1.method3();
}
}
public class Main {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
c.c_method1(a);
D d = new D();
d.d_method3(b);
}
}
这样的设计明显是有问题的,因为我们的类C只用到接口的方法一和方法二,而类D只用到了接口的方法三,因此我们采用接口隔离,将接口分开。
方案:Interface1拆分为接口Interface1和Interface2,C和类D依赖两个不同接口。
interface Interface1{
void method1();
void method2();
}
interface Interface2{
void method3();
}
class A implements Interface1{
@Override
public void method1() {}
@Override
public void method2() {}
}
class B implements Interface2{
@Override
public void method3() {}
}
class C{
public void c_method1(Interface1 interface1){
interface1.method1();
}
public void c_method2(Interface1 interface1){
interface1.method2();
}
}
class D{
public void d_method3(Interface2 interface2){
interface2.method3();
}
}
6、迪米特法则(最少知道原则)(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则也叫最少知道原则,即一个类对自己依赖的类知道的越少越好。对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法外,不要泄露任何信息。
- 迪米特法则还有个更简单的定义:只与直接朋友通信。
- 直接朋友:只要两个对象间有耦合关系,那么它们就是朋友。耦合包括依赖、关联、组合、聚合等等。其中,我们将成员变量、方法参数、方法返回值中的类称为直接朋友,而出现在局部变量中的类(例如class A的一个方法里有class B对象)不是直接朋友。换句话说,陌生的类最好不要以局部变量的形式出现在类的内部。
注意:迪米特法则的核心只要求降低类之间的耦合,并不是完全没有依赖。
7、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。