设计原则、设计模式
考试题型:
简答:设计原则本身间的关系、设计模式之间的关系、设计原则与模式之间的关系
设计:设计问题-》设计方案 出题时问题描述和前提条件锁死,只能推导出一个答案,复习时重点了解适应环境和后果
设计原则
考在简答
设计原则之间的关系
如果要实现一个目标,哪些原则是必须的,相互的关系
设计模式提现了哪些原则
模式与模式之间的变体,策略和状态有什么相同和不同
UML-认识六种箭头,轻松读懂UML图_uml图六种箭头的含义-CSDN博客
设计原则之间的关系
目标:开闭原则
指导:迪米特法则(最小知识原则)
基础:单一职责原则、可变性封装原则
实现:依赖倒转原则、合成复用原则、里氏代换原则、接口隔离原则
单一职责原则
Single Responsibility Principle
⼀个对象应该只包含单⼀的职责,并且该职责被完整地封装在⼀个类中;
就一个类而言,应该仅有一个引起它变化的原因。
单一职责原则实例
某基于 Java 的 C/S 系统的“登录功能”通过如下登录类(Login)实现:
- init:用来初始化界面控件,如按钮等
- diaplay:显示提示窗口,添加控件
- validata:是供界面检查使用的
- getConnection:用来连接数据库
- findUser:用来查询用户
- main:主函数
这样的设计是不好的,这个类包含了过多的职责
现使用单一职责原则对其进行重构:
- 我们将 login 类拆分成了三个类
- 使用分层的方式修改
软件设计的复杂性不可消解但是可以分解。
开闭原则:对可变性封装原则
(Open-Closed Principle, OCP)
一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
对可变性封装原则(Principle of Encapsulation of Variation, EVP)要求找到系统的可变因素并将其封装起来。
示例
某图形界面系统提供了各种不同形状的按钮,客户端代码可针对这些按钮进行编程,用户可能会改变需求要求使用不同的按钮,原始设计方案如图所示:
现对该系统进行重构,使之满足开闭原则的要求:尝试将代码变为数据(配置文件,Config.xml),结合 Java 的反射机制来完成。
里氏代换原则
里氏代换原则(Liskov Substitution Principle, LSP)
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常。反过来则不成立:如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
示例
某系统需要实现对重要数据(如用户密码)的加密处理,在数据操作类(DataOperator)中需要调用加密类中定义的加密算法,系统提供了两个不同的加密类,CipherA 和 CipherB,它们实现不同的加密方法,在 DataOperator 中可以选择其中的一个实现加密操作。如图所示:
也可以为 CipherA 和 CipherB 设计一个共同的父类,下面是指 CipherB 是在 CipherA 的基础上加密。
依赖倒转
依赖倒转原则(Dependence Inversion Principle, DIP)的定义如下:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
要针对接口编程,不要针对实现编程。
依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件
-
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
-
代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。
-
依赖注入
-
构造注入(Constructor Injection):通过构造函数注入实例变量。
-
设值注入(Setter Injection):通过 Setter 方法注入实例变量
-
接口注入(Interface Injection):通过接口方法注入实例变量。
把高层模块和低层模块交互行为抽象出来,做到互不依赖,都依赖于提取出来的抽象。
即:无论高层模块或低层模块实现的细节,都依赖于抽象。
高层模块和低层模块彻底解耦,都很容易实现扩展;
而抽象模块具有很高的稳定性、可重用性,对高/低层模块来说才是真正"可依赖的"
增加了一层抽象层,增加实现难度;
对一些简单的调用关系来说,可能是得不偿失的。
示例
某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如可以转换来自数据库的数据(DatabaseSource)、也可以转换来自文本文件的数据(TextSource),转换后的格式可以是 XML 文件(XMLTransformer)、也可以是 XLS 文件(XLSTransformer)等。
由于需求的变化,该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类 MainClass 都需要修改源代码,以便使用新的类,但违背了开闭原则。现使用依赖倒转原则对其进行重构。
接口隔离
- 接口隔离原则(Interface Segregation Principle, ISP)的定义如下:客户端不应该依赖那些它不需要的接口。
- 一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做“角色隔离原则”。
- 使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
示例
- 下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口(胖接口)AbstractService 来服务所有的客户类。可以使用接口隔离原则对其进行重构。
合成复用
-
合成复用原则(Composite Reuse Principle, CRP)又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)
-
尽量使用对象组合,而不是继承来达到复用的目的。
-
聚合和组合的区别:
-
聚合是独立的,组合局部不能离开总体(往往只属于一个组合对象,组合关系更强)。
-
聚合是空心菱形箭头,组合是实心菱形箭头
-
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
-
继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用)
-
组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。(“黑箱”复用)
-
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
示例
- 某教学管理系统部分数据库访问类设计如图所示:
- 如果需要更换数据库连接方式,如原来采用 JDBC 连接数据库,现在采用数据库连接池连接,则需要修改 DBUtil 类源代码。如果 StudentDAO 采用 JDBC 连接,但是 TeacherDAO 采用连接池连接,则需要增加一个新的 DBUtil 类,并修改 StudentDAO 或 TeacherDAO 的源代码,使之继承新的数据库连接类,这将违背开闭原则,系统扩展性较差。
- 现使用合成复用原则对其进行重构。
迪米特法则
最少知识原则(Least Knowledge Principle, LKP)
-
不要和“陌生人”说话。英文定义为:Don’t talk to strangers.
-
只与你的直接朋友通信。英文定义为:Talk only to your immediate friends.
-
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
-
简单地说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。
-
在迪米特法则中,对于一个对象,其朋友包括以下几类:
-
当前对象本身(this)
-
以参数形式传入到当前对象方法中的对象
-
当前对象的成员对象
-
如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
-
当前对象所创建的对象。
-
迪米特法则可分为狭义法则和广义法则。在狭义的迪米特法则中,如果两个类之间不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。下图中,只允许 A 调用 B 对象的方法,但是不能调用 C 对象的方法(但是我们可以通过在 B 中添加一个 Wrapper 方法来间接调用 C)
-
狭义的迪米特法则:可以降低类之间的耦合,但是会在系统中增加大量的小方法并散落在系统的各个角落,它可以使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联,但是也会造成系统的不同模块之间的通信效率降低,使得系统的不同模块之间不容易协调。
-
广义的迪米特法则:指对对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发、优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。
-
迪米特法则的主要用途在于控制信息的过载:
-
在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及
-
在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限
-
在类的设计上,只要有可能,一个类型应当设计成不变类
-
在对其他类的引用上,一个对象对其他对象的引用应当降到最低
示例
某系统界面类(如 Form1、Form2 等类)与数据访问类(如 DAO1、DAO2 等类)之间的调用关系较为复杂,如图所示:
设计模式
定义: Design Pattern 是⼀套被反复使用、多数人知晓的、经过分类编目 的、代码设计经验的总结
目的:可重用代码、更好被他人理解,更好可靠性
-
根据其目的 ( 模式是用来做什么的 ) 可分为创建型 (Creational),结构型 (Structural) 和行为型 (Behavioral) 三种:
-
创建型模式主要用于创建对象。
-
结构型模式主要用于处理类或对象的组合。
-
行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。
-
根据范围,即模式主要是用于处理类之间关系还是处理对象之间的关系,可分为类模式和对象模式两种:
-
类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的。
-
对象模式处理对象间的关系,这些关系在运行时刻变化,更具动态性。
策略模式 :行为型模式、对象模式
Strategy,也叫 Policy
- 策略接口(Strategy):定义所有支持的算法或操作的接口。
- 具体策略(Concrete Strategy):实现策略接口的类。
- 上下文(Context):使用策略对象的类,它可以根据需要切换策略。
优点
- 松耦合:策略模式将不同的策略封装在独立的类中,与上下文对象解耦,增加了代码的灵活性和可维护性。
- 易于扩展:可以通过添加新的策略类来扩展系统的功能,无需修改现有代码。
- 符合开闭原则:对于新的策略,无需修改上下文对象,只需要实现新的策略接口即可。
符合原则
- 单一职责
- 依赖倒转
- 合成复用
缺点:
- 客户端必须知道所有的策略类:并自行决定使用哪一个策略类。
- 策略类数量增多:每个策略都是一个类,增加了系统的复杂性。
示例:
假设我们有一个购物车应用,需要根据不同的促销策略计算总价格。
策略接口(DiscountStrategy):
1 | public interface DiscountStrategy { |
具体策略(NoDiscount、SeasonalDiscount、BulkPurchaseDiscount):
1 | public class NoDiscount implements DiscountStrategy { |
上下文(ShoppingCart):
1 | public class ShoppingCart { |
使用示例:
1 | public class Main { |
状态模式:行为型对象模式
不需要清楚状态的改变,它只用调用状态的行为方法就行。状态的改变是在状态内部发生的
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
主要角色:
- 环境(Context):维护一个当前状态类的实例,这个实例定义了当前的状态。
- 状态接口(State):定义所有具体状态的共同接口。
- 具体状态(Concrete State):实现状态接口,定义了对象在特定状态下的行为。
优点:
- 单一职责原则:每个状态类负责自己的行为,易于维护。
- 开闭原则:容易添加新的状态类。
- 封装性:状态转换逻辑被封装在状态类中,外部不需要知道状态转换的细节。
原则
- 依赖倒转原则
- 单一职责原则:把不同状态下的行为封装到单一状态类里
缺点:
- 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
- 类数量增加:每个状态都需要一个具体的状态类,可能导致类的数量增加。
- 状态之间的依赖关系:状态类之间可能存在依赖关系,这可能导致复杂的状态转换逻辑。
示例:
业务场景 :
视频播放器 , 有 : 暂停 , 播放 , 快进 , 停止 , 四个状态 ;
在 停止 状态下 , 无法快进 , 如果当前是 停止 状态 , 此时要转为 快进 状态 , 需要进行校验 ;
如果不使用 状态模式 , 则需要进行 if else 判断 ;
如果使用 状态模式 , 就很容易实现 ;
状态类 :
定义状态父类抽象类 , 抽象方法是各个状态对应的方法 ;
定义状态子类 , 每个状态对应一个子类对象 ;
上下文类 :
在该类中封装 所有的状态实例 , 以及定义 状态改变方法 ;
封装当前状态类 , 状态改变方法 调用时 , 实际上调用的是 当前状态类的 对应方法 ;
原文链接:https://blog.csdn.net/shulianghan/article/details/119684989
状态
1 | package state; |
具体状态类
1 | package state; |
环境上下文
1 | package state; |
示例调用代码
1 | package state; |
简单工厂模式:类创建型模式
简单工厂模式包含如下角色:
Factory:工厂角色
Product:抽象产品角色
ConcreteProduct:具体产品角色
优点:
- 封装创建逻辑:客户端不需要知道创建对象的细节。
- 易于理解和使用:相对于其他复杂的工厂模式,简单工厂更容易实现。
缺点:
- 违反开闭原则:添加新产品时需要修改工厂类的逻辑,增加了维护的难度。
工厂方法:类创建型模式
称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
- 抽象工厂(Abstract Factory):声明一个工厂方法,该方法返回一个产品类型的对象。
- 具体工厂(Concrete Factory):实现抽象工厂中定义的工厂方法,创建并返回具体产品对象。
- 抽象产品(Product):定义产品的接口。
- 具体产品(Concrete Product):实现产品接口的类。
优点:
- 扩展性好:新增产品类时不需要修改现有系统。
- 遵循开闭原则:新增产品类时只需新增相应的具体工厂类。
- **解耦:**客户端和产品实现类之间解耦。用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
缺点:
- 类的数量增多:每增加一个产品类,都需要增加一个具体工厂类和一个具体产品类。
示例:
假设我们有一个工厂,可以生产不同类型的交通工具(汽车、自行车)。
抽象产品(Vehicle):
1 | public interface Vehicle { |
具体产品(Car、Bicycle):
1 | public class Car implements Vehicle { |
抽象工厂(VehicleFactory):
1 | public abstract class VehicleFactory { |
具体工厂(CarFactory、BicycleFactory):
1 | public class CarFactory extends VehicleFactory { |
使用示例:
1 | public class FactoryMethodDemo { |
当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体产品对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好地符合了“开闭原则”。
为了提高系统的可扩展性和灵活性,在定义工厂和产品时都必须使用抽象层,如果需要更换产品类,只需要更换对应的工厂即可,其他代码不需要进行任何修改。
适用环境
在以下情况下可以使用工厂方法模式:
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
- 希望能够推迟创建的时候
抽象工厂模式:创建对象
- 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL 电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
- 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品
示例
抽象工厂类的典型代码如下:
1 | public abstract class AbstractFactory{ |
具体工厂类的典型代码如下:
1 | public class ConcreteFactory1 extends AbstractFactory{ |
优点:
- 隔离了具体类的生成:客户端不需要知道具体产品的类名。
- 易于交换产品系列:只需改变具体工厂即可改变产品系列。
- 有利于产品的一致性:当一个产品族的对象被设计成一起工作时,一个应用一次只能使用同一个产品族的产品对象。
原则
- 依赖倒转原则:有抽象工厂类和产品类
- 单一职责原则:每个具体工厂只生产一个具体产品族
缺点:
-
增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
-
增加新的产品族符合开闭原则
-
增加新的产品等级结构麻烦
-
增加系统的抽象性和理解难度。
在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)
工厂模式一个工厂类就负责创建一个类,创建的对象类型是确定的,所以是类创建模式
抽象工厂模式一个工厂类可以创建多个相关的类,创建的对象类型是不确定的,所以是对象创建模式
建造者模式:创建对象
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 产品(Product):要构建的复杂对象。
- 建造者(Builder):为创建一个产品对象的各个部件提供接口。
- 具体建造者(Concrete Builder):实现建造者接口以构造和装配产品的各个部分。
- 导演(Director):构造一个使用建造者接口的对象。
- 客户端(Client):使用建造者创建对象。
优点:
- 封装性:客户端不需要知道产品内部组成的细节。
- 建造和表示分离:建造者独立于产品的表示,使得相同的建造过程可以创建不同的表示。
- 扩展性好:可以很容易地增加新的具体建造者。
缺点:
- 产生多余的Builder对象:如果产品内部变化复杂,可能会导致需要创建多个具体建造者类,增加了系统的复杂度。
示例:
假设我们正在构建一个复杂的对象——电脑。电脑由多个部分组成,如CPU、内存、硬盘等。
不同品牌的cpu ram hdd可以组成不同品牌的电脑
产品(Computer):
1 | public class Computer { |
建造者(ComputerBuilder):
1 | public abstract class ComputerBuilder { |
具体建造者(DellComputerBuilder、HpComputerBuilder):
1 | public class DellComputerBuilder extends ComputerBuilder { |
导演(ComputerDirector):
1 | public class ComputerDirector { |
使用示例:
1 | public class BuilderPatternDemo { |
原型模式:创建对象
在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。
原型模式包含如下角色:
Prototype:抽象原型类
ConcretePrototype:具体原型类
Client:客户类
- 在原型模式结构中定义了一个抽象原型类,所有的 Java 类都继承自 java.lang.Object,而 Object 类提供一个 clone() 方法,可以将一个 Java 对象复制一份。因此在 Java 中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆,Java 语言中的原型模式实现很简单。
- 能够实现克隆的 Java 类必须实现一个标识接口 Cloneable,表示这个 Java 类支持复制。如果一个类没有实现这个接口但是调用了 clone() 方法,Java 编译器将抛出一个 CloneNotSupportedException 异常。
- 实例代码:
1 | public class PrototypeDemo implements Cloneable { |
-
通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。
-
Java 语言提供的 clone() 方法将对象复制了一份并返回给调用者。一般而言,clone() 方法满足:
-
对任何的对象 x,都有 x.clone() != x,即克隆对象与原对象不是同一个对象。
-
对任何的对象 x,都有 x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
-
如果对象 x 的 equals() 方法定义恰当,那么 x.clone().equals(x) 应该成立。
示例
由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。在本实例中使用浅克隆实现邮件复制,即复制邮件(Email)的同时不复制附件(Attachment)。
优点:
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
- 可以动态增加或减少产品类。
- 原型模式提供了简化的创建结构。
- 可以使用深克隆的方式保存对象的状态。
缺点:
- 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码。
命令模式:行为对象
将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
命令模式包含如下角色:
- Command:抽象命令类
- ConcreteCommand:具体命令类
- Invoker:调用者
- Receiver:接收者
- Client:客户类
优点:
- 降低系统的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个命令队列和宏命令(组合命令)。
- 可以方便地实现对请求的 Undo 和 Redo。
缺点:
- 类数量增加:使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
原则
- 单一职责原则:解耦触发和执行命令的类
- 开闭原则:可以在不修改客户端原有代码的情况下创建新的命令
示例:
假设我们有一个遥控器,可以控制电灯的开和关。
命令接口(Command):
1 | public interface Command { |
具体命令(LightOnCommand、LightOffCommand):
1 | public class LightOnCommand implements Command { |
接收者(Light):
1 | public class Light { |
请求者(RemoteControl):
1 | public class RemoteControl { |
客户端(Client):
1 | public class CommandPatternDemo { |
撤销命令的实现
1 | public interface Command { |
观察者模式:行为对象
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
主要角色:
- 主题(Subject):定义了注册观察者对象和注销观察者对象的方法,以及通知所有观察者对象的方法。
- 具体主题(Concrete Subject):实现主题接口,并通常维护一个观察者对象的集合。
- 观察者(Observer):定义了更新观察者的接口,通常会定义一个更新方法,用于接收主题的通知。
- 具体观察者(Concrete Observer):实现观察者接口,通常会维护一个与主题对象关联的状态,并在接收到通知时更新自己的状态。
优点:
- 低耦合:观察者和主题之间的依赖关系被最小化,观察者可以独立于主题被复用。
- 观察者模式符合开闭原则的要求。
- 动态管理:可以动态地添加或移除观察者,主题对象不需要知道具体有哪些观察者。
- 事件驱动:符合事件驱动编程思想,可以很容易地与事件机制结合。
原则
- 最小知识原则:实现了观察者和目标对象的松耦合
- 单一职责原则:观察目标集中管理数据,观察者只有行为职责
- 合成复用原则:变更管理器中使用。
开闭原则:支持好
缺点:
- 观察者模式会使得观察者对象的数量变得庞大,特别是当观察者数量很多时,可能会导致代码难以维护。
- 如果观察者之间存在循环依赖,可能会导致系统进入无限循环。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
示例:
主题(MailSystem):
1 | public interface MailSystem { |
具体主题(ConcreteMailSystem):
1 | public class ConcreteMailSystem implements MailSystem { |
观察者(Observer):
1 | public interface Observer { |
具体观察者(ConcreteObserver):
1 | public class User implements Observer { |
使用示例:
1 | public class ObserverPatternDemo { |
中介者模式:行为对象
为了解决以下问题:
- 系统结构复杂:对象之间存在大量的相互关联和调用,若有一个对象发生变化,则需要跟踪和该对象关联的其他所有对象,并进行适当处理。
- 对象可重用性差:由于一个对象和其他对象具有很强的关联,若没有其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象表现出来更像一个不可分割的整体,职责较为混乱。
- 系统扩展性低:增加一个新的对象需要在原有相关对象上增加引用,增加新的引用关系也需要调整原有对象,系统耦合度很高,对象操作很不灵活,扩展性差。
为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,这就是中介者模式的模式动机
在以下情况下可以使用中介者模式:
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的中介者类。
定义
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
主要角色:
- 中介者(Mediator):定义了所有同事对象之间的通信接口,通常包含一个方法,用于处理同事对象之间的交互。
- 具体中介者(Concrete Mediator):实现中介者接口,通常维护对所有同事对象的引用,并负责协调它们之间的交互。
- 同事(Colleague):定义了与中介者进行通信的方法,并通常包含一个对中介者的引用。
- 具体同事(Concrete Colleague):实现同事接口,并通常包含一个对中介者的引用。
优点:
- 降低耦合:通过中介者对象,降低了同事对象之间的耦合度。
- 可扩展性:添加新的同事对象时,不需要修改现有系统,只需创建新的具体同事类并将其添加到中介者对象中。
- 易于维护:当系统中的同事对象增多时,中介者模式可以很好地管理它们之间的交互。
缺点:
- 中介者可能过于复杂:如果系统中有很多同事对象,中介者可能会变得非常复杂,导致维护困难。
- 中介者模式可能会隐藏一些细节:客户端可能需要通过中介者来与同事对象交互,这可能会增加客户端的复杂性。
中介者的职责
- 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。该中转作用属于中介者在结构上的支持。
- 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。
示例
1 | public abstract class Mediator { |
某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息,钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息。该聊天室可以对不雅字符进行过滤,如“日”等字符;还可以对发送的图片大小进行控制。用中介者模式设计该虚拟聊天室。
chatroom have n member
member have a chatroom
模板方法模式:类行为
- 模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
- 在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。
定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种类行为型模式
-
模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系。
-
抽象方法(Abstract Method)
-
具体方法(Concrete Method)
-
钩子方法(Hook Method):“挂钩”方法和空方法
-
钩子方法的使用
-
钩子方法的引入使得子类可以控制父类的行为。
-
最简单的钩子方法就是空方法,也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。
-
比较复杂一点的钩子方法可以对其他方法进行约束,这种钩子方法通常返回一个 boolean 类型,即返回 true 或 false,用来判断是否执行某一个基本方法。
优点:
- 提高代码复用性 : 将 相同部分代码 , 放在抽象的父类中 ;
- 提高扩展型 : 将 不同的代码 , 放在不同的子类中 , 通过对子类的扩展 , 增加新的行为 ;
- 符合开闭原则 : 通过 父类 调用 子类的操作 , 通过 对子类的扩展 来 增加新的行为 ;
缺点:
- 每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。
示例
1 | public abstract class AbstractClass { |
在银行办理业务时,一般都包含几个基本步骤,首先需要取号排队,然后办理具体业务,最后需要对银行工作人员进行评分。无论具体业务是取款、存款还是转账,其基本流程都一样。现使用模板方法模式模拟银行业务办理流程。
外观模式:对象结构
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
使用场景
-
当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。
-
客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
-
在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
-
接口相关模式:适配器模式、外观模式,变化决定了要使用哪个模式
-
外观模式包含如下角色:
-
Facade:外观角色
-
SubSystem:子系统角色
-
使用的设计思想:迪米特法则
- 根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
- 外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
- 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
- 外观模式的目的在于降低系统的复杂程度。
- 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
优点:
- 简化接口:客户端通过外观类来访问子系统组件,不需要了解子系统内部的复杂性。
- 解耦:子系统组件和客户端之间的耦合度降低,子系统组件的变化不会影响客户端。
- 可扩展性:新的子系统组件可以很容易地添加到外观模式中。
缺点:
- 增加系统复杂性:外观模式可能会增加系统的复杂性,因为需要维护一个外观类。
- 依赖性:客户端依赖于外观类,如果外观类的接口发生变化,客户端可能需要修改。
示例
子系统(Printer、Scanner、FileSystem):
1 | public class Printer { |
外观(OfficeFacade):
1 | public class OfficeFacade { |
使用
1 | public class FacadePatternDemo { |
现在考察一个电源总开关的例子,以便进一步说明外观模式。为了使用方便,一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。通过该电源总开关可以同时控制上述所有电器设备,使用外观模式设计该系统。
抽象外观类的引入
外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
适配器模式:结构/对象
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
系统需要使用现有的类,而这些类的接口不符合系统的需要。
想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
符合原则
对象适配器
类适配器
适配器模式包含如下角色:
- Target:目标抽象类
- Adapter:适配器类
- Adaptee:适配者类
- Client:客户类
类适配器模式还具有如下优点
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
类适配器模式的缺点如下
对于 Java、C# 等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
对象适配器模式还具有如下优点
一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
对象适配器模式的缺点如下
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
示例
现需要设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列方法,如机器人叫喊方法 cry()、机器人移动方法 move() 等。如果希望在不修改已有代码的基础上使得机器人能够像狗一样叫,像狗一样跑,使用适配器模式进行系统设计。
1 | // 机器人接口 |
某系统需要提供一个加密模块,将用户信息(如密码等机密信息)加密之后再存储在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。使用适配器模式设计该加密模块,实现在不修改现有类的基础上重用第三方加密方法。
组合模式:对象结构
组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
组合模式又可以称为整体-部分(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系。
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
- 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。
-
组合模式包含如下角色:
-
Component:抽象构件
-
Leaf:叶子构件
-
Composite:容器构件
-
Client:客户类
优点
- 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
使用原则
- 依赖倒转:使用抽象的componet接口,实现递归结构
- 开闭原则支持好:无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象
树的一部分。 - 违反最小接口原则:leaf和composite都会继承一些不属于自己的方法
缺点
- 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性
- 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制
示例
1 | public abstract class Component { |
在水果盘(Plate)中有一些水果,如苹果(Apple)、香蕉(Banana)、梨子(Pear),当然大水果盘中还可以有小水果盘,现需要对盘中的水果进行遍历(吃),当然如果对一个水果盘执行"吃"方法,实际上就是吃其中的水果。使用组合模式模拟该场景。
文件有不同类型,不同类型的文件其浏览方式有所区别,如文本文件和图片文件的浏览方式就不相同。对文件夹的浏览实际上就是对其中所包含文件的浏览,而客户端可以一致地对文件和文件夹进行操作,无须关心它们的区别。使用组合模式来模拟文件的浏览操作。
装饰模式:结构对象
动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。
装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如 final 类)。
主要角色:
- 抽象构件(Component):定义了对象接口,可以包含一个或多个方法。
- 具体构件(ConcreteComponent):实现了抽象构件接口,通常由具体的对象实现。
- 抽象装饰(Decorator):继承自抽象构件,用于定义装饰器接口。
- 具体装饰(ConcreteDecorator):继承自抽象装饰,并实现抽象装饰接口。具体装饰通常包含一个具体构件对象,并添加额外的功能。
优点:
- 增强性:允许增加新的功能,同时保持类的核心职责不变。
- 可替换性:允许用不同的装饰类来替换,从而实现不同的行为组合。
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 符合开闭原则
缺点:
- 类膨胀:可能会导致大量的装饰类,使得系统变得复杂。
- 功能扩散:装饰类可能会承担过多的职责,导致功能扩散。
符合原则
- 开闭原则(Open/Closed Principle):
- 装饰器模式允许对象在不改变其结构的情况下,通过添加新的装饰器来增加新的功能。这意味着原始的类是开放的扩展,但关闭修改,因为不需要修改现有代码来添加新功能。
- 单一职责原则(Single Responsibility Principle):
- 每个装饰器都负责一个特定的功能,它们将功能细分为单独的类。这样,每个类都有一个且只有一个改变的理由,从而遵循单一职责原则。
- 依赖倒置原则(Dependency Inversion Principle):
- 装饰器模式通常使用抽象组件和装饰器接口来实现,这有助于高层模块(如客户端代码)依赖于抽象,而不是具体实现。
- 里氏替换原则(Liskov Substitution Principle):
- 装饰器模式要求装饰器类和被装饰的类实现相同的接口,这样可以确保装饰器对象可以被替换为被装饰的对象,而不影响程序的正确性。
- 接口隔离原则(Interface Segregation Principle):
- 装饰器模式通常要求有一个小的、专注的接口,装饰器和被装饰的对象都实现这个接口。这样可以避免客户端依赖它们不需要的方法。
示例
变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔。
- Q:为什么 Changer 在继承自 Transform 的同时还要有一个 Transform?
- A:因为我们希望 Changer 能够装饰 Car 或其他的 Transform (如果有,例如 Bicycle),因此需要组合一个 transform。
每个模式的复习
模式名称
问题:约束和适应环境
解决方案:角色、关系、如何映射到解决问题的领域
效果:可以作为题干约束条件的一部分
根据目的划分为:
创建型
结构型
行为型
根据范围:
类模式:继承,静态
对象关系:对象组合,动态
教学立方上会更新设计模式的分类
要关注哪些模式容易组合
创建型经常跟结构型组合,创建型内部不容易
-
【2019】【2023】策略模式和状态模式的区别?
-
在状态模式中,具体状态类的方法参数中包含上下文对象,需要在状态处理完成后完成状态切换。
-
在策略模式中,直接对上下文类调用 set 方法设置策略即可,不涉及到策略的切换。
-
【2019】最小知识原则在设计模式中的应用?
-
中介者模式
-
外观模式
-
【2023】装饰者模式为什么比用子类扩展功能有更好的灵活性?
与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
- 【2023】为什么最小知识原则可以帮助构建高内聚、低耦合的系统?用代码举一个违背的例子
最小知识原则(Law of Demeter),也被称为最少知识原则或迪米特法则,是指一个对象应该对其他对象有最少的了解。具体来说,一个对象只能直接调用它直接拥有的对象的方法,而不应该调用其他对象的方法,也不应该访问其他对象的内部数据。这个原则旨在减少对象之间的耦合,提高模块的内聚性。
最小知识原则帮助构建高内聚、低耦合的系统,因为它:
- 限制对象之间的交互:
- 通过限制对象之间的直接交互,减少了对象之间的依赖关系,从而降低了耦合度。
- 提高模块独立性:
- 每个对象只关心自己的职责和与自己直接交互的对象,这使得每个模块更加独立和内聚。
- 增强系统的可维护性和可扩展性:
- 由于对象之间的依赖关系减少,修改一个对象对其他对象的影响也会减少,这使得系统更易于维护和扩展。
- 降低系统的复杂性:
- 最小知识原则鼓励将复杂的交互分解为简单的交互,这有助于降低整个系统的复杂性。
1 | class Order { |
-
【2022】【2023】What is the benefit of decoupling the Receiver from the Invoker in the Command Pattern?
-
灵活性和可扩展性:
- 命令模式允许在不修改调用者的情况下扩展其功能。你可以添加新的命令或者修改现有命令,而不必影响调用者的实现。这使得系统更加灵活,更容易扩展。
- 松耦合:
- 调用者不需要知道接收者的具体实现。它只需要知道如何执行命令。这种松耦合减少了组件之间的依赖,使得系统更易于维护和更改。
- 封装:
- 命令对象封装了请求和接收者。这种封装隐藏了接收者的实现细节,使得调用者和其他组件不需要了解接收者的具体实现。它还允许通过组合简单的命令来实现复杂的操作。
- 支持可撤销的操作:
- 命令模式使得实现可撤销操作变得简单。你可以存储命令,并按照相反的顺序执行它们来实现撤销。这在需要支持撤销和重做操作的场景中特别有用。
- 【2023】设计一个系统,有很多的机器人,他们有不同的功能,在未来会拓展这些功能或功能的复杂性,同时有一批保留了旧接口的老机器人,也希望能加入到这个系统中。系统还能将所有的机器人看作一个 unit 来统一的控制。1. 列出使用的设计模式和类图 2.说明你的设计如何有利于未来拓展功能
组合 + 适配器 + 装饰器(拓展)
- 【2019】一个游戏,有几种人类角色:骑士、骑兵、步兵,持有不同的武器 ( 矛、剑、斧 ),拥有 fight 方法;有一个非人类角色巨魔,可以持有武器,但攻击方法不同 (beat)。现在希望让巨魔和其他人类角色一起进行游戏,并且要求有角色死亡时其他活着的角色要收到通知。运用设计模式进行设计,并画出类图。
适配器 + 中介者
- 【2022】【2023】观察者设计模式中有两种方法用于向观察者传播数据:推模型(Push Model)和拉模型(Pull Model)。为什么有些情况下一个模型会比另一个模型更可取?每个模型的权衡是什么?
推模型和拉模型的选择取决于应用程序的需求和设计考虑。下面是每个模型的优势和权衡:
-
推模型:
-
优势:
-
简单直接:数据由主题(被观察者)直接推送给观察者,观察者不需要主动请求数据。
-
即时性:数据推送是实时的,观察者可以立即收到最新的数据更新。
-
权衡:
-
无法控制数据量:在推模型中,主题通常将所有数据推送给所有观察者,无论观察者是否需要这些数据。这可能会导致数据传输过程中的浪费。
-
安全性和隐私问题:如果数据包含敏感信息,推模型可能会引发安全和隐私问题,因为数据在推送过程中可能会被截获或访问到。
-
拉模型:
-
优势:
-
灵活性:观察者可以根据需要主动拉取所需的数据,可以减少不必要的数据传输。
-
数据控制:观察者可以根据具体情况控制拉取数据的频率和量,从而减少网络带宽和资源消耗。
-
权衡:
-
延迟性:拉模型需要观察者主动发起请求才能获取数据,可能会引入一定的延迟。
-
实时性限制:观察者只能获取其主动请求的数据更新,无法立即获知所有最新的数据。
-
【2021】软件模式是什么? 能提供架构吗?
软件模式是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件生存期的每一个阶段都存在着一些被认同的模式
软件模式可以认为是对软件开发这一特定“问题”的“解法”的某种统一表示,软件模式等于一定条件下的出现的问题以及解法。软件模式的基础结构由4个部分构成:问题描述、前提条件(环境或约束条件)、解法和效果。