当前位置: 首页 > news >正文

设计模式的艺术读书笔记

设计模式的艺术

  • 面向对象设计原则概述
    • 单一职责原则
    • 开闭原则
    • 里氏代换原则
    • 依赖倒转原则
    • 接口隔离原则
    • 合成复用原则
    • 迪米特法则
  • 创建的艺术
    • 创建型模式
      • 单例模式
        • 饿汉式单例与懒汉式单例的讨论
        • 通过静态内部类实现的更好办法
      • 简单工厂模式
      • 工厂方法模式
        • 重载的工厂方法
        • 工厂方法的隐藏
        • 工厂方法模式总结
      • 抽象工厂模式
        • 产品等级结构与产品族
        • 抽象工厂模式概述
      • 原型模式
        • Java语言提供的clone(​)方法
        • 浅克隆与深克隆
        • 原型管理器的引入和实现
      • 建造者模式
        • 钩子方法的引入
  • 组合的艺术
    • 结构型模式
      • 适配器模式
        • 类适配器模式
        • 双向适配器模式
        • 缺省适配器模式
        • 适用场景
      • 桥接模式
      • 装饰模式
      • 外观模式
        • 抽象外观类的引入
      • 享元模式

面向对象设计原则概述

单一职责原则

单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下:
单一职责原则(Single Responsibility Principle,SRP):一个类只负责一个功能领域中的相应职责。或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则的核心思想是:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中;如果多个职责总是同时发生改变,则可将它们封装在同一类中。

开闭原则

开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。开闭原则由Bertrand Meyer于1988年提出,其定义如下:
开闭原则(Open-Closed Principle,OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

里氏代换原则

其严格表述如下:如果对每个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此一般使用它的另一个通俗版定义:
里氏代换原则(Liskov Substitution Principle,LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

在运用里氏代换原则时,应该将父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法。程序运行时,子类实例替换父类实例,可以很方便地扩展系统的功能,无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,DIP):抽象不应该依赖于细节,细节应该依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

合成复用原则

合成复用原则(Composite Reuse Principle,CRP):尽量使用对象组合,而不是继承来达到复用的目的。(继承更容易引起来复杂性,而且Java单继承的也有限制)

迪米特法则

迪米特法则(Law of Demeter,LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易。这是对软件实体之间通信的限制。迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

创建的艺术

创建型模式

创建型模式(Creational Pattern)关注对象的创建过程,是一类最常用的设计模式,在软件开发中应用非常广泛。创建型模式将对象的创建和使用分离,在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。每个创建型模式都通过采用不同的解决方案来回答3个问题:创建什么(What)​,由谁创建(Who)和何时创建(When)​。

单例模式

单例模式(Singleton Pattern)​:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

饿汉式单例与懒汉式单例的讨论

饿汉式单例就是类加载的时候直接创建,由类加载的机制确保线程安全性。
懒汉式单例是用到的时候再去创建,不过要确保线程安全性:
假如某一瞬间线程A和线程B都在调用getInstance(​)方法,此时instance对象为null值,均能通过“instance==null”的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想。因此需要进行进一步改进,在synchronized锁定代码中再进行一次“instance==null”判断,这种方式称为双重检查锁定(Double-Check Locking),代码示例如下:
在这里插入图片描述

通过静态内部类实现的更好办法

饿汉式单例类不能实现延迟加载,不管将来用不用,它始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题。有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是肯定的。下面来学习这种更好的被称为**Initialization on Demand Holder(IoDH)**的技术。
实现IoDH时,需在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance(​)方法返回给外部使用,实现代码如下:
在这里插入图片描述

简单工厂模式

简单工厂模式并不属于GoF 23个经典设计模式,但通常将它作为学习其他工厂模式的基础,它的设计思想很简单。

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。

所有的工厂模式都强调一点:两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。将对象的创建和使用分离,也使得系统更加符合单一职责原则,有利于对功能的复用和系统的维护。

工厂方法模式

工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

工厂方法模式结构图中包含以下4个角色:

  • Product(抽象产品)​:它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品)​:它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
  • Factory(抽象工厂)​:在抽象工厂类中,声明了工厂方法(Factory Method)​,用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  • ConcreteFactory(具体工厂)​:它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色。抽象工厂可以是接口,也可以是抽象类。

重载的工厂方法

在抽象工厂中可以定义多个重载的工厂方法,在具体工厂中实现了这些工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同产品对象的需求。

工厂方法的隐藏

有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法。此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法。

工厂方法模式总结

工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

在以下情况下可以考虑使用工厂方法模式:

  1. 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  2. 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

抽象工厂模式

产品等级结构与产品族

在工厂方法模式中,具体工厂负责生产具体的产品,每个具体工厂对应一种具体产品,工厂方法具有唯一性。一般情况下,一个具体工厂中只有一个或者一组重载的工厂方法。但是,有时希望一个工厂可以提供多个产品对象,而不是单一的产品对象。例如一个电器工厂,它可以生产电视机、电冰箱、空调等多种电器,而不是只生产某一种电器。为了更好地理解抽象工厂模式,这里先引入如下两个概念:

  • 产品等级结构。产品等级结构即产品的继承结构,例如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
  • 产品族。在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。例如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
抽象工厂模式概述

**抽象工厂模式(Abstract Factory Pattern)**​:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。
在抽象工厂模式结构中包含以下4个角色:

  • AbstractFactory(抽象工厂)​:它声明了一组用于创建一族产品的方法,每个方法对应一种产品。
  • ConcreteFactory(具体工厂)​:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每种产品都位于某个产品等级结构中。
  • AbstractProduct(抽象产品)​:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  • ConcreteProduct(具体产品)​:它定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法。

抽象工厂模式的主要缺点是:增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则。

原型模式

原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。原型模式是一种对象创建型模式。

Java语言提供的clone(​)方法

学过Java语言的人都知道,所有的Java类都继承自java.lang.Object。事实上,Object类提供了一个clone(​)方法,可以将一个Java对象克隆一份。因此在Java中可以直接使用Object提供的clone(​)方法来实现对象的克隆,Java语言中的原型模式实现很简单。
需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone(​)方法,Java编译器将抛出一个CloneNotSupportedException异常,不过需要注意一点这是浅克隆的。

浅克隆与深克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有被复制。

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将被复制。

Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口。标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,例如是否支持克隆、是否支持序列化等。

原型管理器的引入和实现

原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得.

建造者模式

**建造者模式(Builder Pattern)**​:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。建造者模式一步一步地创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

在建造者模式结构中包含以下4个角色:

  • Builder(抽象建造者)​:它为创建一个产品Product对象的各个部件指定抽象接口。在该接口中一般声明两类方法:一类方法是buildPartX(​)​,用于创建复杂对象的各个部件;另一类方法是getResult(​)​,用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
  • ConcreteBuilder(具体建造者)​:它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
  • Product(产品角色)​:它是被构建的复杂对象,包含多个组成部件。具体建造者创建该产品的内部表示并定义其装配过程。
  • Director(指挥者)​:指挥者又称为导演类,它负责安排复杂对象的建造次序。指挥者与抽象建造者之间存在关联关系,可以在其construct(​)建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制)​,然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
钩子方法的引入

建造者模式除了逐步构建一个复杂产品对象外,还可以通过Director类来更加精细地控制产品的创建过程,例如增加一类称之为钩子方法(Hook Method)的特殊方法来控制是否调用某个buildPartX(​)方法。

钩子方法的返回类型通常为boolean类型,方法名一般为is×××(​)​。钩子方法定义在抽象建造者类中。

例如,可以在游戏角色的抽象建造者类ActorBuilder中定义一个方法isBareheaded(​)​,用于判断某个角色是否为“光头(Bareheaded)​”​,在ActorBuilder中为之提供一个默认实现,其返回值为false。

组合的艺术

结构型模式

在面向对象软件系统中,每个类/对象都承担着一定的职责,它们相互协作,可以实现一些复杂的功能。结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构。不同的结构型模式从不同的角度来组合类或对象,在尽可能满足各种面向对象设计原则的同时,为类或对象的组合提供一系列巧妙的解决方案。

适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,**其别名为包装器(Wrapper)**​。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

注:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者一组方法的集合。

类适配器模式

除了对象适配器模式之外,适配器模式还有一种形式,那就是类适配器模式。类适配器模式与对象适配器模式最大的区别在于其适配器和适配者之间的关系是继承关系。

由于Java、C#等语言不支持多重类继承,因此类适配器模式的使用受到很多限制。例如,如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器模式。此外,如果适配者Adaptee为最终(Final)类,也无法使用类适配器模式。在Java等面向对象编程语言中,大部分情况下使用的是对象适配器模式,类适配器模式较少使用。

双向适配器模式

在对象适配器模式的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器

缺省适配器模式

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法)​,那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求。它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。

适用场景

在以下情况下可以考虑使用适配器模式:

  • 系统需要使用一些现有的类,而这些类的接口(例如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。

桥接模式

桥接模式用一种巧妙的方式处理多层继承存在的问题。桥接模式采用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。桥接模式定义如下:
桥接模式(Bridge Pattern):将抽象部分与其实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

桥接模式是一个非常有用的模式,在桥接模式中体现了很多面向对象设计原则的思想,包括单一职责原则、开闭原则、合成复用原则、里氏代换原则、依赖倒转原则等。熟悉桥接模式有助于深入理解这些设计原则,也有助于形成正确的设计思想和培养良好的设计风格。

桥接模式和适配器模式用于设计的不同阶段。桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象类和实现类两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及大量第三方应用接口的情况。

桥接模式是设计Java虚拟机和实现JDBC等驱动程序的核心模式之一,应用较为广泛。在软件开发中,如果一个类或一个系统有多个变化维度时,都可以尝试使用桥接模式对其进行设计。桥接模式为具有多维度变化的系统提供了一套完整的解决方案,并且降低了系统的复杂度。

装饰模式

装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。装饰模式定义如下:
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在装饰模式结构图中包含以下4个角色:

  • Component(抽象构件)​:它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法。它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
  • ConcreteComponent(具体构件)​:它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)​。
  • Decorator(抽象装饰类)​:它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
  • ConcreteDecorator(具体装饰类)​:它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

外观模式

外观模式(Facade Pattern):外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口。外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。(常说的门户就是类似的概念)

抽象外观类的引入

在标准的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则。因此,可以通过引入抽象外观类来对系统进行改进,在一定程度上解决该问题。在引入抽象外观类之后,客户端可以针对抽象外观类进行编程,对于新的业务需求,不需要修改原有外观类,而对应增加一个新的具体外观类。由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改任何源代码并更换外观类的目的。

享元模式


http://www.mrgr.cn/news/79739.html

相关文章:

  • 计算机网络-Wireshark探索ARP
  • 如何创建一个基本的Spring Boot应用程序
  • 新能源汽车 “能量侠”:移动充电机器人开启便捷补电新征程
  • tensorflow相关
  • Swing1.计算器案例
  • HarmonyOS(65) ArkUI FrameNode详解
  • AWK报告生成器
  • Kudu 1.17.1版本编译-aarch
  • SAP Ariba Buying _Managing PO
  • 设计模式:19、桥接模式
  • OpenCV相机标定与3D重建(14)用于组合两个旋转和平移(R|T)变换函数composeRT()的使用
  • 5G中的随机接入过程可以不用收RAR?
  • UE4 骨骼网格体合并及规范
  • 【伪代码】数据结构-期末复习 线性表
  • 【git】git回退到之前版本+拓展git命令
  • TCP/IP杂记
  • 本地搭建MQTT服务器
  • Onedrive技巧与问题
  • v-for遍历多个el-popover;el-popover通过visible控制显隐;点击其他隐藏el-popover
  • 【实操GPT-SoVits】声音克隆模型图文版教程
  • Python爬虫之urllib库使用总结
  • Mac软件推荐
  • maxscript中BoundingBox求一个模型的高度
  • C#-WPF 常见类型转换方法(持续更新)
  • 5G Multi-TRP R16~R18演进历程
  • Linux网络基础知识————网络编程