常见的设计模式

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓 的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题, 以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总 结,具有一定的普遍性,可以反复使用。

本文章将讲解几种常见的设计模式

目录

一.单例模式

二.工厂模式

1.简单工厂模式

2.工厂方法模式

3.抽象工厂模式

4.工厂模式总结

三.代理模式

1.静态代理

2.JDK动态代理

3.CGLIB动态代理

4.代理模式总结

四.适配器模式

1.类适配器模式

2.对象适配器模式

3.接口适配器模式

4.适配器模式总结


一.单例模式

作为最常见的一种设计模式,单独写了一篇博客来介绍它

详情可参考最常考的设计模式之一---单例模式-CSDN博客


二.工厂模式

1.简单工厂模式

简单工厂模式是一种创建型设计模式,它提供了一种创建对象的最简单方式,通过将创建对象的逻辑封装在一个单独的类中,而不暴露具体实例化的逻辑给调用方。简单工厂模式通常由一个工厂类来负责根据传入的参数来决定创建哪种具体的产品类的实例。

简单工厂模式包含以下角色:

  1. 产品接口(Product Interface):定义了工厂创建的所有对象的通用接口。它可以是抽象类或者接口,规定了产品对象的通用行为。

  2. 具体产品(Concrete Products):实现产品接口的具体类,是简单工厂模式创建的对象。

  3. 工厂类(Factory Class):负责根据客户端的需求来创建具体产品的对象。它通常包含一个方法,根据输入的参数来实例化具体产品对象。

示例如下:

1)创建图形的接口Shape和图形的具体实现类CircleSquare

//图形的接口(图形的标准)
public interface Shape {void draw();
}//具体实现类Circle
public class Circle implements Shape{@Overridepublic void draw() {System.out.println("画一个圆圈");}
}//具体实现类Square
public class Square implements Shape{@Overridepublic void draw() {System.out.println("画一个正方形");}
}

2)创建提供对象的工厂类ShapeFactory

public class ShapeFactory {//根据输入参数创建对应的对象public Shape createShape(String shapeType){if(shapeType.equals("circle")){return new Circle();}else if(shapeType.equals("square")){return new Square();}else {return null;}}
}

3)在客户端即可创建工厂类来获取想要的对象

public class Client {public static void main(String[] args) {ShapeFactory shapeFactory=new ShapeFactory();Shape circle= shapeFactory.createShape("circle");circle.draw();Shape square= shapeFactory.createShape("square");square.draw();}
}//运行结果画一个圆圈
画一个正方形Process finished with exit code 0

简单工厂模式的优点包括:

  • 封装了对象的创建过程,使得客户端不需要知道具体的实现细节。
  • 通过工厂类,可以更灵活地管理对象的创建过程,符合开闭原则,当需要添加新的产品时,只需要修改工厂类而不需要修改客户端代码。
  • 简化了客户端的调用,只需要与工厂类进行交互,不需要直接依赖具体的产品类。

然而,简单工厂模式也有一些缺点:

  • 违反了单一职责原则,因为工厂类不仅负责对象的创建,还负责决定创建哪种对象。
  • 当产品类型较多时,工厂类可能会变得臃肿,难以维护和扩展。
  • 因为工厂类是静态的,所以无法通过继承来改变创建方法的行为。

2.工厂方法模式

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它通过定义一个用于创建对象的接口,但是让子类决定实例化哪个类。这样,工厂方法模式允许一个类在运行时创建它所需的具体对象,而不是在设计时就指定

工厂方法模式包含以下角色:

  1. 产品接口(Product Interface):定义了工厂方法模式创建的所有对象的通用接口。它可以是抽象类或者接口,规定了产品对象的通用行为。

  2. 具体产品(Concrete Products):实现产品接口的具体类,是工厂方法模式创建的对象。

  3. 工厂接口(Factory Interface):定义了一个用于创建产品对象的接口,可以是抽象类或者接口。它包含一个抽象的工厂方法,子类通过实现这个方法来创建具体的产品对象。

  4. 具体工厂(Concrete Factory):实现工厂接口,负责创建具体的产品对象。每个具体工厂对应创建一个具体产品对象。

示例如下:

1)创建图形的接口Shape和图形的具体实现类CircleSquare

//图形的接口(图形的标准)
public interface Shape {void draw();
}//具体实现类Circle
public class Circle implements Shape{@Overridepublic void draw() {System.out.println("画一个圆圈");}
}//具体实现类Square
public class Square implements Shape{@Overridepublic void draw() {System.out.println("画一个正方形");}
}

2)创建工厂接口和对应的工厂实例

//工厂接口
public interface ShapeFactory {Shape createShape();
}//圆圈工厂实类
public class CircleFactory implements ShapeFactory{@Overridepublic Shape createShape() {return new Circle();}
}//正方形工厂实类
public class SquareFactory implements ShapeFactory{@Overridepublic Shape createShape() {return new Square();}
}

3)在客户端即可创建对应的工厂类来获取想要的对象

public class Client {public static void main(String[] args) {//获取圆圈工厂ShapeFactory circleFactory=new CircleFactory();Shape circle= circleFactory.createShape();circle.draw();//获取正方形工厂ShapeFactory squareFactory=new SquareFactory();Shape square= squareFactory.createShape();square.draw();}
}//运行结果:画一个圆圈
画一个正方形Process finished with exit code 0

工厂方法模式的核心思想是将对象的创建延迟到子类中去完成,使得核心类只负责定义创建对象的接口而不负责创建对象的过程,这样可以更好地符合开闭原则,使得系统更加灵活可扩展。

工厂方法模式的优点包括:

  • 将对象的创建和使用分离,降低了系统的耦合度。
  • 符合开闭原则,当需要添加新的产品时,只需要添加对应的具体产品类和具体工厂类,不需要修改已有的代码。
  • 可以通过增加新的具体工厂来扩展系统,而无需修改现有的客户端代码。

然而,工厂方法模式也有一些缺点:

  • 每增加一个新的产品,都需要编写一个新的具体产品类和对应的具体工厂类,导致类的数量增加。
  • 客户端需要知道所使用的具体工厂类,增加了系统的复杂度。

3.抽象工厂模式

抽象工厂模式是一种创建型设计模式它提供了一个接口用于创建一系列相关或相互依赖的对象,而无需指定其具体类。抽象工厂模式是工厂方法模式的升级版,它通过引入一个抽象工厂接口和多个具体工厂类来实现,每个具体工厂类负责创建一组相关的产品

抽象工厂模式包含以下角色:

  1. 抽象工厂(Abstract Factory):声明用于创建一系列产品的抽象工厂接口。通常包含多个创建产品的抽象方法,每个方法用于创建不同类型的产品。

  2. 具体工厂(Concrete Factory):实现抽象工厂接口,负责创建具体类型的产品。每个具体工厂类对应一组相关的产品。

  3. 抽象产品(Abstract Product):声明了一类产品的接口,是具体产品的抽象。

  4. 具体产品(Concrete Product):实现了抽象产品接口,是工厂生产的具体产品实例。

示例如下:

1)创建两个产品族:小米(小米空调、小米手机)和华为(华为空调、华为手机)

//空调定义接口
public interface Air {void takeAir();
}//华为空调
public class HuaWeiAir implements Air{@Overridepublic void takeAir() {System.out.println("展示一台华为空调");}
}//小米空调
public class XiaoMiAir implements Air{@Overridepublic void takeAir() {System.out.println("展示一台小米空调");}
}//手机定义接口
public interface Phone {void takePhone();
}//华为手机
public class HuaWeiPhone implements Phone{@Overridepublic void takePhone() {System.out.println("展示一台华为手机");}
}//小米手机
public class XiaoMiPhone implements Phone{@Overridepublic void takePhone() {System.out.println("展示一台小米手机");}
}

2)创建抽象工厂和对应的具体工厂

//抽象工厂定义
public interface AbstractFactory {Phone getPhone();Air getAir();}//小米工厂
public class XiaoMiFactory implements AbstractFactory{@Overridepublic Phone getPhone() {return new XiaoMiPhone();}@Overridepublic Air getAir() {return new XiaoMiAir();}
}//华为工厂
public class HuaWeiFactory implements AbstractFactory{@Overridepublic Phone getPhone() {return new HuaWeiPhone();}@Overridepublic Air getAir() {return new HuaWeiAir();}
}

3)在客户端即可创建对应的工厂类来获取想要的对象

public class Client {public static void main(String[] args) {//创建华为工厂AbstractFactory huaWeiFactory=new HuaWeiFactory();//展示华为产品Air huaWeiAir= huaWeiFactory.getAir();huaWeiAir.takeAir();Phone huaWeiPhone= huaWeiFactory.getPhone();huaWeiPhone.takePhone();//创建小米工厂AbstractFactory xiaoMiFactory=new XiaoMiFactory();//展示小米产品Air xiaoMiAir= xiaoMiFactory.getAir();xiaoMiAir.takeAir();Phone xiaoMiiPhone= xiaoMiFactory.getPhone();xiaoMiiPhone.takePhone();}
}//运行结果展示:展示一台华为空调
展示一台华为手机
展示一台小米空调
展示一台小米手机Process finished with exit code 0

抽象工厂模式的优点包括:

  • 它将产品的创建与客户端分离,客户端不需要知道具体产品的类名,只需要通过抽象接口来操作产品,从而降低了客户端与具体产品类之间的耦合。

  • 它能够确保创建的产品是相互兼容的,因为每个具体工厂类都负责创建一组相关的产品,这些产品之间有一定的关联性。

然而,抽象工厂模式也存在一些缺点:

  • 新增产品族比较困难,因为需要修改抽象工厂接口和所有的具体工厂类。

  • 类的数量随着产品族的增加而增加,导致系统变得更加复杂。

4.工厂模式总结

  • 简单工厂模式

    • 特点:由一个工厂类根据传入的参数决定创建哪一种产品类的实例。
    • 优点:结构简单,适用于创建对象的场景较少。
    • 缺点:违反了开闭原则,每次添加新产品都需要修改工厂类的代码。
  • 工厂方法模式

    • 特点:定义一个创建对象的接口,让子类决定实例化哪个类。
    • 优点:符合开闭原则,每个具体工厂类只负责创建对应的产品。
    • 缺点:会导致类的数量增加,系统结构复杂。
  • 抽象工厂模式

    • 特点:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
    • 优点:可以创建一组相关的产品对象,符合开闭原则。
    • 缺点:难以支持新种类的产品,如果需要添加新产品族,需要修改接口及所有实现类。

总的来说,简单工厂模式适用于对象的创建逻辑较为简单且不会频繁变动的场景;工厂方法模式适用于需要创建一类产品的场景,不同的产品由不同的工厂类来创建;抽象工厂模式适用于需要创建一组相关对象的场景,且系统需要支持多个产品族的情况。选择合适的工厂模式取决于具体的需求和系统设计


三.代理模式

代理模式是一种结构型设计模式,它允许你提供一个代理类,控制对其他对象的访问。代理模式通常用于管理访问对象的方式,以在访问对象时添加额外的功能,或者控制对对象的访问权限。

代理模式涉及以下几个角色:

  1. 抽象主题(Subject):定义了代理类和真实主题(被代理的对象)的共同接口,客户端通过这个接口访问真实主题和代理类。

  2. 真实主题(Real Subject):定义了真实对象的类,代理类通过调用真实主题来完成实际的操作。

  3. 代理(Proxy):提供了与真实主题相同的接口,持有对真实主题的引用,并负责在调用真实主题之前或之后执行额外的操作。

1.静态代理

静态代理是代理模式的一种形式,在编译期间就已经确定代理类和真实主题之间的关系代理类在编译期间就确定了对哪个真实主题进行代理。在静态代理中,代理类和真实主题都实现了相同的接口或继承了相同的父类,客户端通过调用代理类来访问真实主题。

示例如下:

1)创建File接口类和对应的实体类ReadFile

//接口
public interface File {void read();
}//实体类
public class ReadFile implements File{String fileName;public ReadFile(String fileName){this.fileName=fileName;}@Overridepublic void read() {System.out.println("正在阅读文件:"+fileName);}
}

2)创建代理类ProxyFile来代理FileRead

public class ProxyFile implements File{ReadFile readFile;public ProxyFile(String fileName){readFile =new ReadFile(fileName);}@Overridepublic void read() {System.out.println("代理类调用readFile:");readFile.read();}
}

3)在客户端创建代理类来执行功能

public class Client {public static void main(String[] args) {//直接创建ReadFile类ReadFile readFile=new ReadFile("一个文件");readFile.read();System.out.println("-------------------");//创建ReadFile的代理类ProxyFile proxyFile=new ProxyFile("一个文件");proxyFile.read();}
}//执行结果:正在阅读文件:一个文件
-------------------
代理类调用readFile:
正在阅读文件:一个文件Process finished with exit code 0

静态代理的优点包括:

  • 简单易懂:静态代理在编译期间就确定了代理类和真实主题之间的关系,因此易于理解和实现。

  • 编译期检查:由于在编译期间就确定了代理类和真实主题之间的关系,因此可以在编译期间进行类型检查。

  • 降低耦合度:客户端只需与代理类进行交互,而不需要直接与真实主题进行交互,降低了客户端和真实对象之间的耦合度。

静态代理的缺点包括:

  • 增加了代码量:每个真实主题都需要对应一个代理类,如果有多个真实主题,就需要编写多个代理类,增加了代码量。

  • 灵活性差:在编译期间确定了代理类和真实主题之间的关系,因此缺乏灵活性,不能动态地改变代理对象。


2.JDK动态代理

动态代理是Java语言提供的一种机制,允许在运行时动态创建代理类和对象,而不需要事先编写代理类的代码

动态代理涉及以下几个主要角色:

  1. 代理接口(Proxy Interface):定义了代理类和真实主题类共同的接口。

  2. 真实主题类(Real Subject Class):实现了代理接口的类,也称为委托类。

  3. 调用处理器(Invocation Handler):实现了java.lang.reflect.InvocationHandler接口的类,包含了实际的代理逻辑,当客户端调用代理对象的方法时,最终会由调用处理器来处理。

  4. 代理工厂(Proxy Factory):用于动态创建代理对象的工厂类。

示例如下:

1)创建要代理的接口SellCoffee和实现接口的具体类CoffeeShop

//要代理的接口
public interface SellCoffee {void sell();
}//实现的具体类
public class CoffeeShop implements SellCoffee{@Overridepublic void sell() {System.out.println("卖出一杯咖啡");}
}

2)实现代理工厂(代理工厂可以创建代理类)

public class CoffeeFactory {private CoffeeShop coffeeShop =new CoffeeShop();public SellCoffee getCoffeeProxy(){SellCoffee sellCoffee=(SellCoffee) Proxy.newProxyInstance(coffeeShop.getClass().getClassLoader(),coffeeShop.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK动态代理创建代理类实现功能:");Object obj=method.invoke(coffeeShop,args);return obj;}});return sellCoffee;}}

3)在客户端中调用代理工厂类(调用处理器)获得代理类

public class Client {public static void main(String[] args) {CoffeeFactory coffeeFactory=new CoffeeFactory();SellCoffee proxy=coffeeFactory.getCoffeeProxy();proxy.sell();}
}//运行结果:JDK动态代理创建代理类实现功能:
卖出一杯咖啡Process finished with exit code 0

Java动态代理的特点包括:

  • 运行时创建:代理类和代理对象是在运行时动态创建的,而不是在编译时确定的。

  • 基于接口:Java动态代理是基于接口的代理,也就是代理类和真实主题类都要实现同一个接口。

  • 灵活性:由于代理类和代理对象是在运行时创建的,因此具有更大的灵活性,能够根据需要动态创建不同的代理对象。

  • 简化开发:相比较静态代理,动态代理能够减少手动编写代理类的工作,提高开发效率


3.CGLIB动态代理

CGLIB(Code Generation Library)是一个功能强大的,高性能的代码生成库,用于在运行时扩展Java类和实现动态代理。与Java动态代理相比,CGLIB 动态代理不要求被代理的类实现接口而是通过生成被代理类的子类来实现代理,因此也被称为子类代理。

CGLIB 动态代理的原理是在内存中动态构建一个子类,覆盖被代理类中的方法,从而实现对被代理类的增强。当客户端调用代理对象的方法时,实际上是调用了子类中的方法,这些方法会在调用被代理类的方法前后执行额外的逻辑,比如记录日志、权限验证、性能监控等。

示例如下:

1)创建要代理类CoffeeShop


//要代理的类
public class CoffeeShop {public void sell() {System.out.println("卖出一杯咖啡");}
}

2)创建代理工厂类

public class CoffeeFactory implements MethodInterceptor{private CoffeeShop coffeeShop = new CoffeeShop();public CoffeeShop getProxyObject() {//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数Enhancer enhancer =new Enhancer();//设置父类的字节码对象enhancer.setSuperclass(coffeeShop.getClass());//设置回调函数enhancer.setCallback(this);//创建代理对象CoffeeShop obj = (CoffeeShop) enhancer.create();return obj;}/*intercept方法参数说明:o : 代理对象method : 真实对象中的方法的Method实例args : 实际参数methodProxy :代理对象中的方法的method实例*/public CoffeeShop intercept(Object o, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {System.out.println("CGLIB动态代理创建代理类实现功能:");CoffeeShop result = (TrainStation) methodProxy.invokeSuper(o,args);return result;}
}

3)在客户端中调用代理工厂类(调用处理器)获得代理类

public class Client {public static void main(String[] args) {//创建代理工厂对象CoffeeFactory factory = new CoffeeFactory();//获取代理对象CoffeeShop proxyObject = factory.getProxyObject();proxyObject.sell();}
}

CGLIB 动态代理的特点包括:

  • 基于继承:CGLIB 动态代理是基于继承的代理,它通过生成被代理类的子类来实现代理。

  • 无需接口:与 Java 动态代理不同,CGLIB 动态代理不要求被代理类实现接口,可以代理任何类,包括 final 类。

  • 性能高:由于生成的代理类是被代理类的子类,因此方法调用会更快,性能较高。

CGLIB 动态代理通常需要使用第三方库,例如 Spring 框架中就广泛使用了 CGLIB 动态代理来实现 AOP(Aspect-Oriented Programming,面向切面编程)。

4.代理模式总结

  • 静态代理

    • 结构:在编译时就已经确定代理类和被代理类的关系。
    • 特点:代理类和被代理类是早已存在的,代理类需要显式地实现与被代理类相同的接口或继承相同的父类。
    • 优点:结构简单,易于理解和实现。
    • 缺点:如果需要代理多个类,会导致代理类的数量增加,不利于代码的维护。
  • 动态代理

    • 结构:在运行时生成代理类,无需在编码时就知道代理关系。
    • 特点:代理类在运行时动态生成,不需要显式地实现与被代理类相同的接口或继承相同的父类。
    • 优点:可以减少代理类的数量,对系统的扩展性和维护性更好。
    • 缺点:实现相对复杂,需要使用反射等技术,性能方面可能会有一定影响。

四.适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。这种模式通常用于使原本不兼容的接口能够一起工作,或者将已有的类集成到某个系统中而不需要修改原有类的代码。

1.类适配器模式

类适配器模式通过多重继承来实现适配器与被适配者之间的关系。在类适配器模式中,适配器类继承了被适配者类并实现了目标接口,从而使得适配器可以调用被适配者的方法,并且将其转换为客户端所期待的接口。

下面是类适配器模式中的几个关键角色:

  1. 目标接口(Target):客户端所期待的接口,适配器类通过实现这个接口来与客户端进行交互。

  2. 适配器(Adapter):适配器类继承了被适配者类并实现了目标接口,从而使得适配器可以调用被适配者的方法,并且将其转换为客户端所期待的接口。

  3. 被适配者(Adaptee):被适配者是客户端已有的类,它的接口与目标接口不兼容。

示例如下:

假设有一个旧的音频播放器类 OldAudioPlayer,它有一个 playAudio 方法用于播放音频,但它的接口与我们现在的音频播放器接口 AudioPlayer 不兼容。我们想要通过适配器模式来使得 OldAudioPlayer 能够适配到 AudioPlayer 接口。

1)定义目标接口 AudioPlayer和旧的音频播放器类 OldAudioPlayer

// 目标接口
public interface AudioPlayer {void play();}// 旧的音频播放器类
public class OldAudioPlayer {public void playAudio() {System.out.println("老播放器播放音乐");}}

2)创建一个适配器类 AudioPlayerAdapter 来适配 OldAudioPlayerAudioPlayer 接口:


// 适配器类
public class AudioPlayerAdapter extends OldAudioPlayer implements AudioPlayer{@Overridepublic void play() {//继承OldAudioPlayer类,就可以调用旧的播放音频方法playAudio();}
}

3)在客户端通过适配器类调用 play 方法来播放音频,而不需要直接调用 OldAudioPlayer 中的 playAudio 方法

public class Main {public static void main(String[] args) {// 创建适配器AudioPlayer adapter = new AudioPlayerAdapter();// 调用适配器的 play 方法,实际上会调用 OldAudioPlayer 中的 playAudio 方法adapter.play();}
}//运行结果老播放器播放音乐Process finished with exit code 0

类适配器模式的工作原理如下:

  • 适配器类继承了被适配者类,并实现了目标接口。
  • 适配器类中包含了一个被适配者对象的引用。
  • 在适配器类中,通过调用被适配者对象的方法来实现目标接口中的方法。

类适配器模式的优点包括:

  • 结构简单:类适配器模式只需要一个适配器类即可实现适配器与被适配者之间的关系,结构相对简单。
  • 单一职责原则:适配器类既充当了适配器的角色,又充当了被适配者的角色,符合单一职责原则。

但类适配器模式也有一些缺点:

  • 只能适配一个类:由于类适配器模式使用了继承关系,因此适配器类只能适配一个具体的被适配者类,不够灵活。
  • 不支持被适配者子类的适配:如果被适配者类存在子类,并且想要适配子类的方法,需要重写适配器类的相应方法,增加了系统的复杂性。

2.对象适配器模式

对象适配器模式用于使一个类的接口与另一个类的接口兼容。在对象适配器模式中,适配器类持有要适配的类的实例,并实现目标接口,从而将客户端对目标接口的调用转发给被适配的对象。

以下是对象适配器的主要参与者及其角色:

  1. 目标接口(Target):定义客户端使用的特定领域的接口。

  2. 适配器(Adapter):实现目标接口,并持有要适配的类的实例,在实现目标接口的方法中调用被适配对象的方法,完成适配工作。

  3. 被适配对象(Adaptee):需要被适配的类,其接口与目标接口不兼容。

示例如下:

假设有一个音频播放器(AudioPlayer),它有一个播放方法play(String audioType, String fileName),可以播放不同类型的音频文件。目前支持的音频类型有MP3和MP4

现在,我们希望能够播放其他格式的音频文件,比如VLC格式的文件。但是VLC格式的播放器接口与我们的播放器接口不兼容,这时候就可以使用对象适配器模式来解决这个问题。

1)定义接口MediaPlayer定义了播放器的基本操作和实现了MediaPlayer接口的AudioPlayer类

//接口MediaPlayer
public interface MediaPlayer {void play(String audioType, String fileName);
}//播放器类
public class AudioPlayer implements MediaPlayer {public void play(String audioType, String fileName) {// 播放MP3文件的逻辑if (audioType.equalsIgnoreCase("mp3")) {System.out.println("Playing MP3 file: " + fileName);}// 播放MP4文件的逻辑else if (audioType.equalsIgnoreCase("mp4")) {System.out.println("Playing MP4 file: " + fileName);}}
}

2)定义新的接口AdvancedMediaPlayer和实现了其接口的VlcPlayer类


public interface AdvancedMediaPlayer {void playVlc(String fileName);}public class VlcPlayer implements AdvancedMediaPlayer {public void playVlc(String fileName) {System.out.println("Playing VLC file: " + fileName);}}

3)创建适配器类MediaAdapter,实现目标接口MediaPlayer,并持有AdvancedMediaPlayer的实例

public class MediaAdapter implements MediaPlayer {private AdvancedMediaPlayer advancedMusicPlayer;public MediaAdapter(String audioType){if(audioType.equalsIgnoreCase("vlc") ){advancedMusicPlayer = new VlcPlayer();}}public void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer.playVlc(fileName);}}
}

4)在客户端使用适配器来播放VLC格式的音频文件

public class Client {public static void main(String[] args) {AudioPlayer audioPlayer = new AudioPlayer();audioPlayer.play("mp3", "song.mp3");audioPlayer.play("mp4", "movie.mp4");MediaPlayer mediaAdapter = new MediaAdapter("vlc");mediaAdapter.play("vlc", "vlc_song.vlc");}
}

对象适配器模式的优点包括:

  • 可以将不兼容的接口协调起来,使得原本不能一起工作的类可以协同工作。
  • 通过适配器,可以使系统更灵活,减少代码修改和耦合度。

然而,对象适配器模式也存在一些限制:

  • 适配器需要实例化被适配对象,可能会增加系统复杂性。
  • 对象适配器模式可能涉及多层嵌套,导致代码结构相对复杂。

3.接口适配器模式

接口适配器模式(Interface Adapter Pattern)允许一个类通过多个接口与其他类进行通信。这种模式通过引入一个抽象类作为适配器(接口适配器),该抽象类实现了目标接口,并且其中的方法都是空方法,被适配类只需要实现自己感兴趣的方法即可。

比较简单就不作示例了

4.适配器模式总结

  • 类适配器模式

    • 结构:类适配器模式使用继承来实现适配器。适配器类同时继承目标接口和被适配类,从而可以将目标接口的调用转发到被适配类。
    • 优点:结构相对简单,适配器与被适配类之间的关系更为明确。
    • 缺点:如果适配器需要适配多个被适配类,会导致多重继承,语言限制的问题。
  • 对象适配器模式

    • 结构:对象适配器模式使用组合(持有被适配对象的实例)来实现适配器。适配器类实现目标接口,并持有被适配类的实例,在实现目标接口的方法中调用被适配对象的方法。
    • 优点:可以适配多个被适配类,避免了多重继承的问题。
    • 缺点:需要实例化被适配对象的实例,可能增加系统复杂性。
  • 接口适配器模式

    • 结构:接口适配器模式通过引入一个抽象类作为适配器,该抽象类实现了目标接口,并且其中的方法都是空方法,被适配类只需要实现自己感兴趣的方法即可。
    • 优点:可以灵活地选择需要适配的方法,避免了类适配器和对象适配器中需要实现所有方法的问题。
    • 缺点:引入了额外的抽象类,可能增加系统复杂性。

到这里常见的设计模式就介绍完了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/847637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度神红网络——什么是 CNN(卷积神经网络)?

Facebook和Instagram自动检测图像中的面孔,Google通过上传照片搜索相似图片的功能,这些都是计算机视觉技术的实例,它们背后的核心技术是卷积神经网络(CNN)。那么,CNN究竟是什么呢?接下来&#x…

思维导图-vb.net开发带进度条的复制文件夹功能c#复制文件夹

你们谁写代码会用流程图来做计划,或者写项目总结报告? .net带进度条复制文件夹 方案 列出所有子文件夹,再创建,复制文件 大文件可以单独做进度条 缺点:设计会更复杂 直接…

如何用结构化写好GPT的Prompt提示词

背景 最早接触 Prompt engineering 时, 学到的 Prompt 技巧都是: 你是一个 XX 角色…你是一个有着 X 年经验的 XX 角色…你会 XX, 不要 YY…对于你不会的东西, 不要瞎说!… 对比什么技巧都不用, 直接像使用搜索引擎一样提问, 上面的技巧对于回复的效果确实有着 明显提升. 在看…

统一终端管理解决方案有哪些?必须收藏的统一终端管理软件

统一终端管理解决方案,是一种综合性的管理策略,旨在通过集中化的方式,对企业或组织的各种终端设备进行统一的管理、监控、保护和优化。以下是对统一终端管理解决方案的详细介绍。 一、方案概述 统一终端管理解决方案涵盖了从硬件到软件、从网…

[Linux系统编程]文件重定向dup和dup2

一.dup和dup2 实现重定向 1.文件描述符表 操作系统在管理文件时,会管理一张文件描述符表,根据打开的文件,分配一个文件描述符(int),根据该文件描述符,锁定指向该文件的指针,从而索取文件。 2.重定向 在li…

svg使用 element plus 使用外部下载的svg,使用或作为背景图片的使用方式,svg背景填充自适应父级宽高

friger.vue 注意:引入路径后加#svgView(preserveAspectRatio(none)),可解决宽高设置无效的问题 代码上就这两句就行,它去这个路径下去找@/assets/svgs/login-bg.svg,往这个目录下放svg文件就行<template><div class="parent-container"><el-row…

Python实现连连看6

3.2 生成图片地图 图2所示的界面实际上可以看成是一个1010的二维数组,数组中的每个国旗图片对应一个0-24中的一个值,所以每个值应该有1010(1+24)=4个,也就是该100个国旗中有25种国旗,每种国旗有4个。这种二维数组我们把它叫做图片地图,如图7所示。 图7 图片地图 要生成…

颜色分类 ---- 分治-快排

题目链接 题目: 分析: 运用将"数组分成三块"的思想: 需要定义三个指针: left指向最左侧的区域的最右边, 所以left起始为-1 right指向最右侧色区域的最左边, 所以right起始为nums.length i用来遍历数组这三个指针就将数组分成了四块 [0,left] 为0 [left 1, i] 为1 …

【工具】Vmware17 安装mac(13.6.7)虚拟机

目录 0.简介 1.环境 2.详细步骤 2.1下载mac镜像&#xff08;可以选择你所需要的&#xff09; 2.2 VMware安装 1&#xff09;创建新的虚拟机 2&#xff09;选择【典型】&#xff0c;点击下一步 3&#xff09;选择【安装程序光盘映像文件】&#xff0c;点击浏览&#xff…

【代码随想录算法训练Day28】LeetCode 93. 复原 IP 地址、LeetCode 78.子集、LeetCode 90.子集II

Day28 回溯第四天 LeetCode 93. 复原 IP 地址 这道题目的难点同样是题意&#xff0c;有了回文串的前车之鉴&#xff0c;这道题的分割倒是有迹可循&#xff0c;但这道题判断是否合理同样也有很多细节。 class Solution { public:vector<string> res;bool isValid(const…

服务器的初始化

服务器的初始化 新的服务器到手&#xff0c;部署服务器的初始化。 1、配置ip地址 网关 dns解析&#xff08;static&#xff09;内网和外网 2、安装源&#xff0c;外网&#xff08;在线即可&#xff09;内网&#xff08;只能用源码包编译安装&#xff09; 3、磁盘分区&#…

【python】成功解决“TypeError: ‘method’ object is not subscriptable”错误的全面指南

成功解决“TypeError: ‘method’ object is not subscriptable”错误的全面指南 一、引言 在Python编程中&#xff0c;TypeError: method object is not subscriptable错误是一个常见的陷阱&#xff0c;特别是对于初学者来说。这个错误通常意味着你尝试像访问列表、元组、字典…

2024蓝桥杯初赛决赛pwn题全解

蓝桥杯初赛决赛pwn题解 初赛第一题第二题 决赛getting_startedbabyheap 初赛 第一题 有system函数&#xff0c;并且能在bss上读入字符 而且存在栈溢出&#xff0c;只要过掉check函数即可 check函数中&#xff0c;主要是对system常规获取权限的参数&#xff0c;进行了过滤&…

upload-labs-第一关和第二关

目录 第一关 思路&#xff1a; 1、上传一个php文件 2、查看源码 3、查看文件上传地址 4、BP抓包&#xff0c;修改文件后缀名 5、使用蚁剑连接 第二关 1、这一关也可以跟第一关一样的方法进行绕过 2、上传一个一句话木马文件 第一关 原理&#xff1a; 思路&#xff1a…

Spark中广播的使用

前言 在PySpark中&#xff0c;"/* broadcast(a) */"是一种注释语法&#xff0c;用于提示Spark优化器在执行查询计划时使用广播变量。广播变量是将数据广播到集群中的所有节点&#xff0c;以便在计算过程中能够更高效地访问数据。 在Spark中&#xff0c;默认情况下&a…

高精度滚珠丝杆在自动化生产中的关键因素!

如今&#xff0c;自动化技术正以前所未有的速度改变着人们的生活和工作方式&#xff0c;特别是在高精度精密设备的制造与应用领域&#xff0c;提高生产效率和优化生产流程正变得越来越重要。在自动化生产中&#xff0c;滚珠丝杆的优化应用对于提高生产效率、保证产品质量至关重…

MedSAM 学习笔记(续):利用训练好的权重进行gui的推理

1、介绍 MedSAM 代码复现参考&#xff1a;第一章&#xff1a;MedSAM 视觉大模型介绍_medsam 系统简介-CSDN博客 利用MedSAM迁移学习训练自定义数据集介绍&#xff1a;MedSAM 学习笔记&#xff08;续&#xff09;&#xff1a;训练自定义数据集_sam训练自定义数据-CSDN博客 本文…

leetcode第867题:转置矩阵

matrix[i][j]需要放在转置矩阵的(j,i)位置 public class Solution {public int[][] Transpose(int[][] matrix) {int rows matrix.Length; int columns matrix[0].Length; int[][] array2 new int[columns][];// 初始化内部数组&#xff08;列数&#xff09;for (int i 0…

数据库加密系统设计与实现的设计与开发

数据库加密系统设计与实现的设计与开发 Design and Development of a Database Encryption System: Design and Implementation 完整下载链接:数据库加密系统设计与实现的设计与开发 文章目录 数据库加密系统设计与实现的设计与开发摘要第一章 绪论1.1 引言1.2 研究目的与内容…

通过仪器分类方式修订看监测仪器发展新趋势

随着科技的进步和监测需求的不断升级&#xff0c;监测仪器的分类方式亟需与时俱进。本文旨在探讨《混凝土坝监测仪器系列型谱》中对现有仪器分类方式的修订&#xff0c;以及监测仪器发展的新趋势相关内容。 一、仪器分类方式的修订 传统的仪器分类方式往往基于功能、原理或应用…