【设计模式】设计模式原则、单例模式、工厂模式、模板模式、策略模式
- 1、什么是设计模式?有什么好处?
- 2、设计模式的7大基本原则有哪些?
- 3、单例模式
-
- [3.1 单例模式的多种写法](#3.1 单例模式的多种写法)
- [3.2 如何破坏单例模式?](#3.2 如何破坏单例模式?)
- [3.3 如何避免单例被破坏?](#3.3 如何避免单例被破坏?)
- [3.4 为什么说枚举是实现单例最好的方式?](#3.4 为什么说枚举是实现单例最好的方式?)
- 4、工厂模式
-
- [4.1 简单工厂模式](#4.1 简单工厂模式)
-
- [4.1.1 简单工厂模式实现方式](#4.1.1 简单工厂模式实现方式)
- [4.1.2 简单工厂模式存在的问题](#4.1.2 简单工厂模式存在的问题)
- [4.1.3 简单工厂模式总结](#4.1.3 简单工厂模式总结)
- [4.2 工厂方法模式](#4.2 工厂方法模式)
-
- [4.2.1 工厂方法模式实现方式](#4.2.1 工厂方法模式实现方式)
- [4.2.2 为什么要使用工厂来创建对象?](#4.2.2 为什么要使用工厂来创建对象?)
- [4.2.3 为什么每种对象要单独有一个工厂?](#4.2.3 为什么每种对象要单独有一个工厂?)
- [4.2.4 工厂方法模式总结](#4.2.4 工厂方法模式总结)
- [4.3 抽象工厂模式](#4.3 抽象工厂模式)
-
- [4.3.1 抽象工厂模式实现方式](#4.3.1 抽象工厂模式实现方式)
- [4.3.2 抽象工厂模式用途](#4.3.2 抽象工厂模式用途)
- [4.3.3 抽象工厂模式总结](#4.3.3 抽象工厂模式总结)
- 5、责任链模式
- 6、什么是代理模式,有哪些应用?
- 7、什么是模板方法模式,有哪些应用?
1、什么是设计模式?有什么好处?
设计模式是在软件开发过程中经常遇到的问题的通用解决方案 。它们是经过无数的验证和经验积累的最佳实践。
首先,设计模式是一些前人经验的一些总结,所以,当遇到相似的问题的时候,我们可以直接借鉴好的设计模式来实现,这样可以大大降低我们的试错成本和迭代成本 。可以大大提升我们的开发速度。
不要认为只有23种,只要是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结都是软件设计模式的。比如说MVC
而且,设计模式都是遵守了很多设计原则的,这些原则可以帮助我们大大提升代码的可重用性、可维护性和可扩展性等。
2、设计模式的7大基本原则有哪些?
设计模式是软件设计中常用的解决方案,它们有助于构建可重用、可维护和可扩展的代码。在设计模式中,有七个基本原则,它们提供了指导设计的准则和原则。
设计模式的7大基本原则中,有5个来源于面向对象的5大基本原则(SOLID原则),他们分别是:
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起变化的原因。换句话说,一个类应该只做一件事。这个原则鼓励将功能分解为小的、独立的单元,每个单元只负责一件事情。
开放封闭原则(Open/Closed Principle,OCP) :软件实体(类、模块、函数等)应该对扩展是开放的,而对修改是关闭的。这意味着应该通过扩展现有代码来引入新功能,而不是直接修改已有的代码。
里氏替换原则(Liskov Substitution Principle,LSP):任何一个父类出现的地方,都可以用它的子类来替代,而不会导致程序的错误或异常。这个原则强调继承关系的正确使用,子类应该能够完全替代父类而不引发意外行为。
接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖于它不需要使用的接口。一个类不应该强迫它的客户端依赖于它们不需要的方法。接口应该小而专注,不应该包含多余的方法。这要求我们要使用多个小的专门的接口,而不要使用一个大的总接口。
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。这要求我们写出来的程序要依赖于抽象接口,而不是具体的实现。这个原则通过引入抽象接口或抽象类,将高层模块与低层模块解耦。
除了这5大基本原则以外,在设计模式中,还有2个原则需要我们遵守的。那就是:
迪米特法则(Law of Demeter,LoD):一个对象应该对其他对象有最少的了解。对象之间应该保持松耦合。这个原则鼓励将复杂系统分解为许多小的、相对独立的模块,模块之间的交互通过最少的接口进行。
合成/聚合复用原则(Composition/Aggregation Reuse Principle,CARP):优先使用合成/聚合,而不是继承来实现代码复用。这个原则推崇对象组合和聚合的方式来构建复杂的对象结构,而不是依赖继承。
3、单例模式
3.1 单例模式的多种写法
想要实现一个单例,首先就是要考虑把构造函数设置成private的,否则的话就可以随时通过构造函数创建对象了,就不是单例了。
那把构造函数private之后,就还需要提供一个方法,可以初始化单例对象,并且要保证只能初始化一个单例对象。并且需要考虑线程安全的问题。
具体到写法上,主要有5种。分别是懒汉、饿汉、静态内部类、双重校验锁以及枚举。
1、懒汉
所谓懒汉,就是在需要的时候才会去创建对象。好处就是避免提前创建浪费资源,但是缺点也明显,就是第一创建的时候浪费时间。
java
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、饿汉
所谓饿汉,就是在类刚一初始化的时候就立即把单例对象创建出来。以下两种都是饿汉模式的具体实现。
java
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
java
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
3、静态内部类
java
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4、枚举
java
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
5、双重校验锁
java
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
3.2 如何破坏单例模式?
单例模式主要是通过把一个类的构造方法私有化,来避免重复创建多个对象的。那么,想要破坏单例,只要想办法能够执行到这个私有的构造方法就行了。
一般来说做法有使用反射及使用反序列化都可以破坏单例。
1、反射破坏单例
我们尝试通过反射技术,来破坏单例。以下代码,输出结果为false,也就是说通过反射技术,我们给单例对象创建出来了一个"兄弟"。
java
Singleton singleton1 = Singleton.getSingleton();
//通过反射获取到构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
//将构造函数设置为可访问类型
constructor.setAccessible(true);
//调用构造函数的newInstance创建一个对象
Singleton singleton2 = constructor.newInstance();
//判断反射创建的对象和之前的对象是不是同一个对象
System.out.println(s1 == s2);
2、反序列化破坏单例
我们尝试通过序列化+反序列化来破坏一下单例。
java
public class SerializableDemo1 {
//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
//Exception直接抛出
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
//false
3.3 如何避免单例被破坏?
1、避免反射破坏单例
反射是调用默认的构造函数创建出来的,只需要我们改造下构造函数,使其在反射调用的时候识别出来对象是不是被创建过就行了:
java
private Singleton() {
if (singleton != null){
throw new RuntimeException("单例对象只能创建一次... ");
}
2、避免反序列化破坏单例
解决反序列化的破坏单例,只需要我们自定义反序列化的策略就行了,就是说我们不要让他走默认逻辑一直调用到Unsafe创建对象,而是我们干预他的这个过程,干预方式就是在Singleton类中定义readResolve,这样就可以解决该问题:
java
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
3.4 为什么说枚举是实现单例最好的方式?
主要有以下三个好处:
1、枚举实现的单例写法简单
2、枚举实现的单例天然是线程安全的
定义枚举时使用enum和class一样,是Java中的一个关键字。就像class对应用一个Class类一样,enum也对应有一个Enum类。
通过将定义好的枚举反编译,我们就能发现,其实枚举在经过javac的编译之后,会被转换成形如public final class T extends Enum的定义。
而且,枚举中的各个枚举项同时通过static来定义的。如:
java
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}
反编译后代码为:
java
public final class T extends Enum
{
//省略部分内容
public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
AUTUMN = new T("AUTUMN", 2);
WINTER = new T("WINTER", 3);
ENUM$VALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}
static类型的属性会在类被加载过程中被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。
也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。
所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。
3、枚举实现的单例可避免被反序列化破坏
对于普通的单例对象来说,反序列化过程中会通过Java 的 Unsafe 机制来创建对象的。这意味着即使类的构造函数是私有的,反序列化仍然可以创建该类的实例,因为它不依赖于常规的构造过程。
但是,枚举的反序列化并不是通过Unsafe,也不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
4、工厂模式
工厂模式的主要功能就是帮助我们实例化对象的。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替new操作的。
这样做的好处是封装了对象的实例化细节,尤其是对于实例化较复杂或者对象的生命周期应该集中管理的情况。会给你系统带来更大的可扩展性和尽量少的修改量。
工厂模式有三种,分别是简单工厂模式、工厂方法模式、抽象工厂模式。三种模式从前到后越来越抽象,也更具有一般性。

1、简单工厂 :一个工厂创建所有具体产品。对于增加新的产品,主要是新增产品,就要修改工厂类。符合单一职责原则。不符合开放-封闭原则
● 优点:
○ 1、屏蔽产品的具体实现,调用者只关心产品的接口。
○ 2、实现简单
● 缺点:
○ 1、增加产品,需要修改工厂类,不符合开放-封闭原则
○ 2、工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则
**2、工厂方法 :一个工厂方法只创建一个具体产品。**支持增加任意产品,**新增产品时不需要更改已有的工厂,需要增加该产品对应的工厂。符合单一职责原则、符合开放-封闭原则。**但是引入了复杂性
● 优点:
○ 1、继承了简单工厂模式的优点
○ 2、符合开放-封闭原则
● 缺点:
○ 1、增加产品,需要增加新的工厂类,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
**3、抽象工厂 :一个工厂方法只创建一类具体产品。增加新产品时,需要修改工厂,增加产品族时,需要增加工厂。符合单一职责原则,部分符合开放-封闭原则,**降低了复杂性
● 优点:
○ 1、隔离了具体类的生成,使得客户并不需要知道什么被创建
○ 2、每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;
● 缺点
○ 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对"开闭原则"的支持呈现倾斜性。
4.1 简单工厂模式
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
现在我们要使用面向对象的形式定义计算器,为了实现各算法之间的解耦。主要的用到的类如下:
java
// 计算类的基类
public abstract class Operation {
private double value1 = 0;
private double value2 = 0;
public double getValue1() {
return value1;
}
public void setValue1(double value1) {
this.value1 = value1;
}
public double getValue2() {
return value2;
}
public void setValue2(double value2) {
this.value2 = value2;
}
protected abstract double getResule();
}
//加法
public class OperationAdd extends Operation {
@Override
protected double getResule() {
return getValue1() + getValue2();
}
}
//减法
public class OperationSub extends Operation {
@Override
protected double getResule() {
return getValue1() - getValue2();
}
}
//乘法
public class OperationMul extends Operation {
@Override
protected double getResule() {
return getValue1() * getValue2();
}
}
//除法
public class OperationDiv extends Operation {
@Override
protected double getResule() {
if (getValue2() != 0) {
return getValue1() / getValue2();
}
throw new IllegalArgumentException("除数不能为零");
}
}
当我想要执行加法运算时,可以使用如下代码:
java
public class Main {
public static void main(String[] args) {
OperationAdd operationAdd = new OperationAdd();
operationAdd.setValue1(10);
operationAdd.setValue2(5);
System.out.println(operationAdd.getResule());
}
}
当我需要执行减法运算时,我就要创建一个OperationSub类。也就是说,我想要使用不同的运算的时候就要创建不同的类,并且要明确知道该类的名字。
那么这种重复的创建类的工作其实可以放到一个统一的工厂类中。简单工厂模式有以下优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、屏蔽产品的具体实现,调用者只关心产品的接口。
4.1.1 简单工厂模式实现方式
简单工厂模式其实和他的名字一样,很简单。先来看看它的组成:
Factory:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由 一个具体类实现。(OperationFactory)
Product:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。(Operation)
ConcreteProduct:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。 来用类图来清晰的表示下的它们之间的关系(OperationAdd\OperationSub等)

在原有类的基础上,定义工厂类:
java
//工厂类
public class OperationFactory {
public static Operation createOperation(String operation) {
Operation oper = null;
switch (operation) {
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
default:
throw new UnsupportedOperationException("不支持该操作");
}
return oper;
}
}
有了工厂类之后,可以使用工厂创建对象:
java
Operation operationAdd = OperationFactory.createOperation("+");
operationAdd.setValue1(10);
operationAdd.setValue2(5);
System.out.println(operationAdd.getResule());
通过简单工厂模式,该计算器的使用者不需要关心实现加法逻辑的那个类的具体名字,他只要知道该类对应的参数"+"就可以了。
4.1.2 简单工厂模式存在的问题
当我们需要增加一种计算时,例如开平方。这个时候我们需要先定义一个类继承Operation类,其中实现平方的代码。除此之外我们还要修改OperationFactory类的代码,增加一个case。这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。
我们举的例子是最简单的情况。而在实际应用中,很可能产品是一个多层次的树状结构。 简单工厂可能就不太适用了。
4.1.3 简单工厂模式总结
工厂类是整个简单工厂模式的关键。包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责"消费"对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。
但是由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。
当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利。
4.2 工厂方法模式
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
工厂方法模式是一种实现了"工厂"概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。
工厂方法模式的实质是"定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。
4.2.1 工厂方法模式实现方式
工厂方法模式包含如下角色:
Product:抽象产品(Operation)
ConcreteProduct:具体产品(OperationAdd)
Factory:抽象工厂(IFactory)
ConcreteFactory:具体工厂(AddFactory)

修改简单工厂模式中的工厂类(OperationFactory)。替代原有的那个"万能"的大工厂类,这里使用工厂方法来代替:
java
//工厂接口
public interface IFactory {
Operation CreateOption();
}
//加法类工厂
public class AddFactory implements IFactory {
public Operation CreateOption() {
return new OperationAdd();
}
}
//除法类工厂
public class DivFactory implements IFactory {
public Operation CreateOption() {
return new OperationDiv();
}
}
//乘法类工厂
public class MulFactory implements IFactory {
public Operation CreateOption() {
return new OperationMul();
}
}
//减法类工厂
public class SubFactory implements IFactory {
public Operation CreateOption() {
return new OperationSub();
}
}
这样,在客户端中想要执行加法运算时,需要以下方式:
java
public class Main {
public static void main(String[] args) {
IFactory factory = new AddFactory();
Operation operationAdd = factory.CreateOption();
operationAdd.setValue1(10);
operationAdd.setValue2(5);
System.out.println(operationAdd.getResult());
}
}
4.2.2 为什么要使用工厂来创建对象?
在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
4.2.3 为什么每种对象要单独有一个工厂?
主要目的是为了解耦。在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合"开闭原则。
以上就是工厂方法模式的优点。但是,工厂模式也有一些不尽如人意的地方:
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
4.2.4 工厂方法模式总结
工厂方法模式是简单工厂模式的进一步抽象和推广。
由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
工厂方法模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
4.3 抽象工厂模式
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
**抽象工厂模式和工厂方法模式一样,都符合开放-封闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。**也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。
4.3.1 抽象工厂模式实现方式
抽象工厂模式包含如下角色:
AbstractFactory(抽象工厂):用于声明生成抽象产品的方法
ConcreteFactory(具体工厂):实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;
AbstractProduct(抽象产品):为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;
Product(具体产品):定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
本文的例子采用一个汽车代工厂造汽车的例子。假设我们是一家汽车代工厂商,我们负责给奔驰和特斯拉两家公司制造车子。我们简单的把奔驰车理解为需要加油的车,特斯拉为需要充电的车。其中奔驰车中包含跑车和商务车两种,特斯拉同样也包含跑车和商务车。

以上场景,我们就可以把跑车和商务车分别对待,对于跑车有单独的工厂创建,商务车也有单独的工厂。这样,以后无论是再帮任何其他厂商造车,只要是跑车或者商务车我们都不需要再引入工厂。同样,如果我们要增加一种其他类型的车,比如越野车,我们也不需要对跑车或者商务车的任何东西做修改。
下面是抽象产品,奔驰车和特斯拉车:
java
public interface BenzCar {
//加汽油
public void gasUp();
}
public interface TeslaCar {
//充电
public void charge();
}
下面是具体产品,奔驰跑车、奔驰商务车、特斯拉跑车、特斯拉商务车:
java
public class BenzSportCar implements BenzCar {
public void gasUp() {
System.out.println("给我的奔驰跑车加最好的汽油");
}
}
public class BenzBusinessCar implements BenzCar{
public void gasUp() {
System.out.println("给我的奔驰商务车加一般的汽油");
}
}
public class TeslaSportCar implements TeslaCar {
public void charge() {
System.out.println("给我特斯拉跑车冲满电");
}
}
public class TeslaBusinessCar implements TeslaCar {
public void charge() {
System.out.println("不用给我特斯拉商务车冲满电");
}
}
下面是抽象工厂:
java
public interface CarFactory {
public BenzCar getBenzCar();
public TeslaCar getTeslaCar();
}
下面是具体工厂:
java
public class SportCarFactory implements CarFactory {
public BenzCar getBenzCar() {
return new BenzSportCar();
}
public TeslaCar getTeslaCar() {
return new TeslaSportCar();
}
}
public class BusinessCarFactory implements CarFactory {
public BenzCar getBenzCar() {
return new BenzBusinessCar();
}
public TeslaCar getTeslaCar() {
return new TeslaBusinessCar();
}
}
4.3.2 抽象工厂模式用途
在以下情况下可以使用抽象工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
系统中有多于一个的产品族,而每次只使用其中某一产品族。
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
4.3.3 抽象工厂模式总结
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对"开闭原则"的支持呈现倾斜性。
5、责任链模式
责任链模式的目的是避免请求发送者与多个接收者之间的耦合关系,将这些接收者组成一条链,并沿着这条链传递请求,直到有一个接收者处理它为止。
在责任链模式中,通常将处理请求的对象称为处理器或者链的节点,每个节点都包含了处理该请求的逻辑以及指向下一个节点的引用。当请求到达一个节点时,如果该节点无法处理该请求,它会将请求转发给下一个节点,直到有一个节点处理该请求或者整个链都无法处理该请求。
责任链模式在实际开发中有很多应用场景,比如:
-
过滤器链:在Web开发中,可以通过责任链模式来实现过滤器链,例如Spring框架中的FilterChain就是一条责任链,每个过滤器都有机会对请求进行处理,直到最后一个过滤器处理完毕。
-
日志记录器:在日志系统中,可以使用责任链模式来将日志记录器组成一条链,从而实现多种日志记录方式的灵活组合。
-
异常处理器:在应用程序中,可以使用责任链模式来实现异常处理器的链式调用,从而灵活地处理各种异常情况。
-
授权认证:在系统中,可以使用责任链模式来实现授权认证的链式调用,从而灵活地控制不同用户对系统的访问权限。
6、什么是代理模式,有哪些应用?
代理模式是一种结构设计模式,它允许通过创建代理对象来控制对其他对象的访问。代理对象充当原始对象的接口,客户端通过代理对象间接地访问原始对象,并可以在访问过程中添加额外的逻辑或控制。
代理模式的主要目的是通过引入代理对象,为原始对象提供一层间接访问的方式,以实现对原始对象的控制、保护或增强。他的常用场景有以下几个:
1、远程代理:在分布式系统中,代理模式可用于代理远程对象。远程代理隐藏了远程对象的实际实现细节,使客户端可以像访问本地对象一样访问远程对象。如Dubbo的实现就是用到了代理模式。
2、动态代理:动态代理允许在运行时动态地创建代理对象,并动态地将方法调用分派到不同的处理器。它通过Java的反射机制实现,可以用于实现通用的代理逻辑,而无需为每个被代理的类单独创建代理。如Spring的AOP,就用到了动态代理。
3、缓存代理:缓存代理可以缓存原始对象的结果,以避免重复计算或访问资源。一般我们在用到缓存的时候,可以用这种模式。先访问代理对象,代理对象会去查询缓存,如果缓存中你没有,再去查询真实对象。
4、日志代理:这种用的也挺多的,当我需要做日志记录的时候,可以做一个代理,在代理对象中进行统一的日志记录及管理。
5、异常代理:通常我们的系统中如果有统一的异常机制或者ERROR_CODE的机制,可以通过创建一个统一的代理来做处理。在代理对象中这些异常的捕捉及转换。
示例
假设我们有一个比较耗时的查询服务,他的接口定义及实现如下:
java
public interface DataService {
String getData();
}
java
public class DataServiceImpl implements DataService {
@Override
public String getData() {
//执行非常耗时的数据查询
return "Data from expensive operation";
}
}
这时候我们想要引入缓存,则可以定义以一个代理:
java
public class CachedDataServiceProxy implements DataService {
private DataService dataService;
@Autowired
private CacheService cacheService;
public CachedDataServiceProxy(DataService dataService) {
this.dataService = dataService;
}
@Override
public String getData() {
//先从缓存中获取
String cachedData = cacheService.getdata();
//缓存中没有,则查询接口
if (cachedData == null) {
data = dataService.getData();
//获取到以后再保存到缓存中
cacheService.putData(data);
}
return cachedData;
}
}
7、什么是模板方法模式,有哪些应用?
**模板方法模式是一种行为设计模式,他的主要作用就是复用代码。在很多时候,我们的代码中可能会有一些公共的部分并且还有一些定制的部分,那么公共这部分就可以定义在一个父类中,然后将定制的部分实现在子类中。**这样子类可以根据需要扩展或重写父类的方法,而不需要改变算法的结构。
通常会把模板方法模式和策略模式一起使用,因为当我们使用策略模式的时候,会把具体的策略实现在策略服务里面,但是还剩下一些通用的逻辑,就可以通过模板方法模式进行复用。
拿一个常见的优惠券作为示例,假设我们需要定义一个优惠券的申请服务。
java
abstract class Coupon {
// 模板方法,定义优惠券的应用流程
public final void applyCoupon() {
if (isCouponValid()) {
if (isEligibleForDiscount()) {
applyDiscount();
}
displayConfirmation();
} else {
displayInvalidCouponMessage();
}
}
// 具体方法,用于判断优惠券是否有效
protected boolean isCouponValid() {
// 具体的判断逻辑,子类可以重写该方法来实现特定的有效性判断
return true;
}
// 具体方法,用于判断用户是否符合优惠券的折扣条件
protected boolean isEligibleForDiscount() {
// 具体的判断逻辑,子类可以重写该方法来实现特定的条件判断
return true;
}
// 抽象方法,由子类实现具体的优惠券折扣逻辑
protected abstract void applyDiscount();
// 抽象方法,由子类实现具体的优惠券确认展示逻辑
protected abstract void displayConfirmation();
// 具体方法,用于展示无效优惠券的信息
protected void displayInvalidCouponMessage() {
System.out.println("无效优惠券!");
}
}
以上是一个抽象类,这个类中有一个具体的方法applyCoupon,其中定义了一个优惠券申请的具体实现,并且编排了多个其他的方法。
这就是一个典型的模板方法。我们可以基于这个抽象类来定义具体的实现:
java
class PercentageCoupon extends Coupon {
@Override
protected void applyDiscount() {
// 具体的百分比折扣逻辑
System.out.println("应用百分比折扣优惠!");
}
@Override
protected void displayConfirmation() {
// 具体的百分比优惠券确认展示逻辑
System.out.println("百分比折扣优惠确认!");
}
}
class FixedAmountCoupon extends Coupon {
@Override
protected void applyDiscount() {
// 具体的固定金额折扣逻辑
System.out.println("应用固定金额优惠!");
}
@Override
protected void displayConfirmation() {
// 具体的固定金额优惠券确认展示逻辑
System.out.println("固定金额优惠确认!");
}
}
以上,就是两个具体的实现,分别继承Coupon抽象类,并且实现其中的部分方法就行了。
这样我们在实际使用时,就可以直接用FixedAmountCoupon和PercentageCoupon类,并且直接调用他的applyCoupon方法就行了。如:
java
public class Main {
public static void main(String[] args) {
Coupon percentageCoupon = new PercentageCoupon();
percentageCoupon.applyCoupon();
System.out.println("----------------");
Coupon fixedAmountCoupon = new FixedAmountCoupon();
fixedAmountCoupon.applyCoupon();
}
}
参考链接:
1、https://www.yuque.com/hollis666/wk6won/dfs5eap0vcg5xzpi
2、https://www.yuque.com/hollis666/wk6won/xzem6qp7pl2bson0