常见的设计模式

发布于:2024-06-02 ⋅ 阅读:(135) ⋅ 点赞:(0)

软件设计模式(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{
    @Override
    public void draw() {
        System.out.println("画一个圆圈");
    }
}

//具体实现类Square
public class Square implements Shape{
    @Override
    public 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{
    @Override
    public void draw() {
        System.out.println("画一个圆圈");
    }
}

//具体实现类Square
public class Square implements Shape{
    @Override
    public void draw() {
        System.out.println("画一个正方形");
    }
}

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

//工厂接口
public interface ShapeFactory {
    Shape createShape();
}

//圆圈工厂实类
public class CircleFactory implements ShapeFactory{

    @Override
    public Shape createShape() {
        return new Circle();
    }
}

//正方形工厂实类
public class SquareFactory implements ShapeFactory{
    @Override
    public 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{
    @Override
    public void takeAir() {
        System.out.println("展示一台华为空调");
    }
}

//小米空调
public class XiaoMiAir implements Air{

    @Override
    public void takeAir() {
        System.out.println("展示一台小米空调");
    }
}

//手机定义接口
public interface Phone {
    void takePhone();
}

//华为手机
public class HuaWeiPhone implements Phone{

    @Override
    public void takePhone() {
        System.out.println("展示一台华为手机");
    }
}

//小米手机
public class XiaoMiPhone implements Phone{

    @Override
    public void takePhone() {
        System.out.println("展示一台小米手机");
    }
}

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

//抽象工厂定义
public interface AbstractFactory {

    Phone getPhone();

    Air getAir();

}

//小米工厂
public class XiaoMiFactory implements AbstractFactory{
    @Override
    public Phone getPhone() {
        return new XiaoMiPhone();
    }

    @Override
    public Air getAir() {
        return new XiaoMiAir();
    }
}

//华为工厂
public class HuaWeiFactory implements AbstractFactory{
    @Override
    public Phone getPhone() {
        return new HuaWeiPhone();
    }

    @Override
    public 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;
    }
    @Override
    public void read() {
        System.out.println("正在阅读文件:"+fileName);
    }
}

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

public class ProxyFile implements File{
    ReadFile readFile;
    public ProxyFile(String fileName){
        readFile =new ReadFile(fileName);
    }

    @Override
    public 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{
    @Override
    public 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() {
                    @Override
                    public 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{
    @Override
    public 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.适配器模式总结

  • 类适配器模式

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

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到