11种行为型模式(上)

模板方法模式

板方法模式就是一种在接口中定义好一系列模板,子类完善模板,实现功能的设计模式。

可以把这个类比如PPT的模板,在网上找好模板,进行二次开发就行了。

介绍

1、模板方法模式,又叫模板模式。

在一个抽象类中中公开定义了执行它的方法模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

2、简单说,模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。

3、模板模式属于行为者模式。

image-20240309105529172

对原理图中的说明

1、AbstractClass抽象类,类中实现了模板方法,定义了算法的骨架,具体子类需要取实现,其他的抽象方法operation2,3,4
2、ConcrecteClass实现抽象方法operate2,3,4,以完成算法中特点子类的步骤

代码示例

image-20240309110800738

// 抽象类定义制作豆浆的模板
abstract class SoymilkMaker {
    // 制作豆浆的模板方法
    public final void makeSoymilk() {
        selectBeans();
        soakBeans();
        grindBeans();
        if (customerWantsCondiments()) {
            addCondiments();
        }
        boilSoymilk();
        serveSoymilk();
    }

    // 选择豆子
    public void selectBeans() {
        System.out.println("选择优质的黄豆");
    }

    // 浸泡豆子
    public void soakBeans() {
        System.out.println("浸泡黄豆6-8小时");
    }

    // 研磨豆子
    public void grindBeans() {
        System.out.println("研磨浸泡后的黄豆和水");
    }

    // 添加调料,由子类实现
    public abstract void addCondiments();

    // 煮豆浆
    public void boilSoymilk() {
        System.out.println("将磨好的豆浆煮沸");
    }

    // 提供豆浆
    public void serveSoymilk() {
        System.out.println("将豆浆倒入杯子中");
    }

    // 钩子方法,子类可以重写以决定是否添加调料,默认为true
    public boolean customerWantsCondiments() {
        return true;
    }
}

// 具体类实现不同口味的豆浆
class SweetSoymilkMaker extends SoymilkMaker {
    // 添加调料
    @Override
    public void addCondiments() {
        System.out.println("加入适量糖和香草");
    }
}

class SaltySoymilkMaker extends SoymilkMaker {
    // 添加调料
    @Override
    public void addCondiments() {
        System.out.println("加入适量盐和麻辣");
    }

    // 重写钩子方法,不添加调料
    @Override
    public boolean customerWantsCondiments() {
        return false;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("制作甜豆浆:");
        SoymilkMaker sweetSoymilk = new SweetSoymilkMaker();
        sweetSoymilk.makeSoymilk();

        System.out.println("\n制作咸豆浆:");
        SoymilkMaker saltySoymilk = new SaltySoymilkMaker();
        saltySoymilk.makeSoymilk();
    }
}

补充钩子方法:

钩子方法(Hook Method)是在模板方法模式中的一种技术,它是在抽象类中定义的一个空或默认的方法,子类可以选择性地覆盖它来提供特定的行为。这样的方法允许子类影响算法的流程,但不改变模板方法的结构。

钩子方法通常在模板方法中被调用,它们可以有默认实现,也可以是抽象的,具体实现由子类提供。这样,子类可以通过覆盖钩子方法来定制算法的行为,从而实现对模板方法的扩展或修改。

在模板方法模式中,钩子方法允许子类在模板方法的执行过程中插入自己的逻辑,以达到更灵活的控制算法的行为。

模板方法模式在SpringIOC容器中的应用

如果要追源码从:ConfigurableApplicationContext接口开始追。

4a5b8a7f989503a26dd6e0ccbd3dbcb

image-20240309112734090

这里就是AbstractApplicationContext抽象类中的模板方法:refresh。getBeanFactory、refreshBeanFactory都是AbstractApplicationContext的抽象方法,由子类实现。

 public void refresh() throws BeansException, IllegalStateException {
        this.startupShutdownLock.lock();

        try {
            this.startupShutdownThread = Thread.currentThread();
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (Error | RuntimeException var12) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var12);
                }

                this.destroyBeans();
                this.cancelRefresh(var12);
                throw var12;
            } finally {
                contextRefresh.end();
            }
        } finally {
            this.startupShutdownThread = null;
            this.startupShutdownLock.unlock();
        }

    }

总结

注意事项和细节

1、模板方法模式基本思想:算法只存在父类中,容易修改。需要修改算法时,只需要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。

2、实现了最大化代码复用。父类模板方法和已实现的某些步骤会被子类继承而直接使用

3、即统一了算法,也提供了很大的灵活性。

4、使用场景:当要完成某个过程时,该过程需要一系列的步骤,这一系列操作步骤基本相同,但其个别也有可能不同,可以使用模板方法模式来处理。

优点:

  1. 封装不变部分:模板方法模式通过将算法的通用部分封装在模板方法中,使得子类只需关注特定的实现细节,提高了代码的复用性。

  2. 提高扩展性:模板方法模式通过允许子类重写部分步骤来实现特定的功能,从而提高了系统的扩展性和灵活性。

  3. 代码复用:模板方法模式将相同的代码放在父类中,避免了重复编写相似的代码,提高了代码的可维护性和可读性。

  4. 便于维护:由于模板方法模式将算法的骨架和具体步骤分离,使得系统的各个部分更容易理解和维护。

缺点:

  1. 限制子类选择:模板方法模式在一定程度上限制了子类的选择余地,因为子类必须遵循父类定义的算法结构,不能自由地定义自己的算法。

  2. 增加了系统复杂性:在模板方法模式中,由于父类中的模板方法可能会调用具体子类中的方法,因此可能会增加系统的复杂性。

命令模式

命令模式是一种通过调用端调用命令方法,让最终的执行类去执行的模式

介绍

1、命令模式(Command Pattern)是一种行为设计模式。

2、它旨在将请求封装成对象,从而使得可以将请求参数化、将请求队列化、以及支持可撤销的操作。

3、这种模式背后的核心思想是将请求的发送者与接收者解耦,使得它们之间不直接交互,而是通过命令对象来进行通信。

主要角色:

  1. 命令(Command):定义了执行操作的接口,通常包含一个执行(execute)方法。在接口中可能会定义撤销(undo)方法等扩展操作。

  2. 具体命令(Concrete Command):实现了命令接口,负责执行具体的操作,通常包含一个接收者对象,用于执行实际的操作。

  3. 接收者(Receiver)士兵:负责执行请求相关的操作,具体命令对象会将请求委托给接收者。

  4. 调用者/客户端(Invoker/Client)将军:负责创建命令对象,并且将命令对象与接收者进行绑定,将请求发送给命令对象执行。

  5. 客户端(Client):创建命令对象并设置其接收者。

image-20240310153815740

1、Invoker:调用者角色
2、Comand:命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
3、Receiver:接收者角色,知道如何实施和执行一个请求相关操作
4、ConcreteCommand:将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现execute

Client调用一个Command,然后就命令去调用Receiver。

工作步骤

  1. 客户端创建一个命令对象,并且将其与一个接收者对象绑定。

  2. 客户端将命令对象发送给调用者。

  3. 调用者执行命令,调用命令对象的执行方法。

  4. 命令对象将具体操作委托给接收者执行。

代码实现

image-20240310154655964

Main类中的使用:

1、先创建一个Light电灯对象

2、将电灯对象与ConcreteCommand具体命令进行绑定

3、创建一个Invoker遥控器对象

4、设置遥控器的命令,并执行命令

// 定义命令接口
interface Command {
    void execute();
}
​
// 具体命令:打开电灯
class LightOnCommand implements Command {
    private Light light;
​
    public LightOnCommand(Light light) {
        this.light = light;
    }
​
    @Override
    public void execute() {
        light.turnOn();
    }
}
​
// 具体命令:关闭电灯
class LightOffCommand implements Command {
    private Light light;
​
    public LightOffCommand(Light light) {
        this.light = light;
    }
​
    @Override
    public void execute() {
        light.turnOff();
    }
}
​
// 接收者:电灯
class Light {
    public void turnOn() {
        System.out.println("电灯已打开");
    }
​
    public void turnOff() {
        System.out.println("电灯已关闭");
    }
}
​
// 调用者:遥控器
class RemoteControl {
    private Command command;
​
    public void setCommand(Command command) {
        this.command = command;
    }
​
    public void pressButton() {
        command.execute();
    }
}
​
public class Main {
    public static void main(String[] args) {
        // 创建电灯对象
        Light light = new Light();
​
        // 创建具体命令对象,并将其与接收者绑定
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
​
        // 创建遥控器
        RemoteControl remoteControl = new RemoteControl();
​
        // 设置命令并执行
        remoteControl.setCommand(lightOnCommand);
        remoteControl.pressButton(); // 打开电灯
​
        remoteControl.setCommand(lightOffCommand);
        remoteControl.pressButton(); // 关闭电灯
    }
}

命令模式在Spring框架JdbcTemplate应用

可能在实现的时候,略有差别,但是总体的思想是不变的。

需要追源码,从JdbcTemplate类开始追。

勉强可以写成这样:

548b160fc730712e95648a9a4942ac9

1、StatementCallback接口(命令接口Command)
​
2、QueryStatementCallback类(具体命令类 )
    这个是在query方法中的一个内部类
    
3、query是JdbcTemplate类中的一个方法。(在此充当接收者角色Receiver)
​
4、命令调用者是JdbcTemplate(调用者角色;Invoker)
其中在JdbcTemplate类中有一个execute方法:
public <T> T execute(StatementCallback<T> action) throws DataAccessException 。用来实现StatementCallback接口中的对象。
​

总结

优点

  1. 解耦发送者和接收者:命令模式将请求发送者和请求接收者解耦,发送者无需知道接收者的具体实现,降低了系统的耦合度,提高了系统的灵活性。

  2. 更容易扩展和维护:由于命令模式将请求封装成对象,每个命令对象都实现了一个统一的接口,因此可以方便地添加新的命令和接收者,而无需修改已有的代码。

  3. 支持撤销和重做:命令对象通常会保存执行操作所需的状态信息,这使得可以支持撤销和重做等功能,增强了系统的交互性。

  4. 支持命令队列和日志记录:命令模式可以将命令对象保存在队列中,实现命令的排队、延迟执行等功能,同时还可以通过记录命令对象的执行日志来实现系统的日志记录功能。

缺点

  1. 会增加类的数量:在命令模式中,每个具体命令都需要创建一个独立的类,如果命令较多,会导致类的数量增加,增加了系统的复杂度。

  2. 可能会导致过多的细粒度命令对象:如果系统中存在大量的细粒度命令对象,可能会导致命令对象过多,增加了系统的管理和维护成本。

补充:

细粒度命令对象指的是具有非常小的粒度或者具有单一职责的命令对象。在命令模式中,每个具体的命令对象通常都负责执行一个特定的操作,如果这个操作非常细小或者具有单一的功能,那么这个命令对象就可以称为细粒度命令对象。

访问者模式

访问者模式是一种通过调用访问者类来访问具体类的设计模式,但实际上是通过被访问的具体类将自身作为参数传递给访问者对象,然后由访问者对象来处理具体类的访问和操作。

介绍

1、访问者设计模式,封装了一些作用于某些数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。

2、上述的总结:主要将数据结构于数据操作分离,解决数据结构和操作耦合性问题

3、访问者模式的基本工作原理:在被访问的类中加一个对外提供接待访问者的接口

4、访问者模式主要应用场景:需要对一个对象结构中的对象进行很多不同操作(操作直接没有关联),同时需要避免这些操作”污染“这些对象的类,可以用访问者模式解决。

image-20240311191716565

讲解:

1、Visitor是抽象的访问者,为该对象结构中的ConcreteElement的每个类声明一个visite操作
2、ConcreteVisitor:是一个具体的访问值,实现每个有Visitor声明的操作,是每个操作实现的部分
3、ObjectStructure能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
4、Element定义一个accept方法,接收一个访问者对象
5、ConcreteElement 为具体元素,实现了accept方法

代码

image-20240311192833303

讲解:
客户端(Main 类)中,首先创建了一个具体的访问者对象(ConcreteVisitor),然后创建了对象结构,并向其中添加了元素。最后,调用对象结构的 accept 方法,访问了其中的每一个元素。
import java.util.ArrayList;
import java.util.List;
​
// 定义一个接口表示访问者
interface Visitor {
    void visit(ElementA elementA);
    void visit(ElementB elementB);
}
​
// 具体的访问者实现
class ConcreteVisitor implements Visitor {
    @Override
    public void visit(ElementA elementA) {
        System.out.println("访问了元素A");
    }
​
    @Override
    public void visit(ElementB elementB) {
        System.out.println("访问了元素B");
    }
}
​
// 定义一个接口表示元素
interface Element {
    void accept(Visitor visitor);
}
​
// 具体的元素类A
class ElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
​
// 具体的元素类B
class ElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
​
// 对象结构,包含了一组元素
class ObjectStructure {
    private List<Element> elements = new ArrayList<>();
​
    public void attach(Element element) {
        elements.add(element);
    }
​
    public void detach(Element element) {
        elements.remove(element);
    }
​
    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}
​
public class Main {
    public static void main(String[] args) {
        // 创建访问者对象
        Visitor visitor = new ConcreteVisitor();
​
        // 创建对象结构
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new ElementA());
        objectStructure.attach(new ElementB());
​
        // 访问对象结构中的元素
        objectStructure.accept(visitor);
    }
}

小结:

1、双分派,所谓双分派就是不管类怎么变化,我们都可以找到期望的方法允许。
双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
​
2、这里来展示一下访问者模式的优点:
当我们需要添加一个ElementC时,我们只需添加一个ElementC子类,并在客户端调用就可以了,不需要改动其他类的代码。

分析:看的这个实现,会不会有点想到,这个访问模式,怎么跟接口代理有点思想相似呢?

访问模式和代理的区别

访问者模式:
1、访问者模式的重点在于对一个数据结构的不同元素进行不同的操作,从而将操作与数据结构解耦。
​
2、在访问者模式中,ObjectStructure 提供了一种方式来遍历数据结构中的元素,并将操作委托给访问者对象,访问者对象则根据实际的元素类型来执行相应的操作。
​
3、因此,用户在使用 ObjectStructure 时,主要是为了访问数据结构中的元素,并执行与元素相关的操作。
​
​
​
代理模式:
1、代理模式的重点在于控制对对象的访问,为了提供额外的功能或者控制访问的方式。
​
2、在代理模式中,代理对象提供了与被代理对象相同的接口,用户通过代理对象来访问被代理对象,并且代理对象可能会在访问被代理对象之前或之后执行一些额外的逻辑。
​
3、因此,用户在使用代理对象时,主要是为了获得对被代理对象的访问,并且可能获得额外的功能或者控制。
​
​
总结:
尽管用户通过 ObjectStructure 访问 ElementA 和 ElementB 中的方法,看起来有些类似代理模式中用户通过代理对象访问被代理对象的行为,但这两种模式的设计目的和应用场景不同,因此它们之间并没有直接的等价关系。

总结

个人理解:

访问者模式是一种通过借助ObjectStructure类,来提供访问。

这里是一个小例子用来继续理解的:

举个简单的例子,假设你有一个家庭成员的集合,其中包含了父母、孩子和宠物。你想要执行某些操作,比如说问候、检查健康状态等,但你不想在每个成员类中都实现这些操作,因为这样会导致代码的复杂性增加。访问者模式就是为了解决这个问题而设计的。
​
在访问者模式中,你会有以下角色:
​
访问者(Visitor):定义了对不同类型的家庭成员执行的操作的接口。例如,问候、检查健康状态等。
​
具体访问者(ConcreteVisitor):实现了访问者接口,提供了对每个具体类型的家庭成员执行操作的具体逻辑。
​
元素(Element):定义了一个 accept 方法,用来接受访问者的访问。在我们的例子中,家庭成员就是元素。
​
具体元素(ConcreteElement):实现了元素接口,提供了接受访问者的具体逻辑。
​
对象结构(ObjectStructure):维护了所有的元素,并提供了一个 accept 方法,用来让访问者访问每个元素。
​
在这个例子中,ObjectStructure 不是用来访问想要访问的类方法的。它的作用是作为一个容器,管理家庭成员,并提供接受访问者的方法。当你调用 accept 方法时,它会依次将每个家庭成员传递给访问者,让访问者执行相应的操作。这样,你可以很容易地添加新的操作,而不必修改家庭成员的类。

1、角色:

  • 访问者(Visitor):定义了对每个元素进行的操作,它的方法签名决定了可以访问的元素类型。

  • 具体访问者(ConcreteVisitor):实现了访问者定义的操作。

  • 元素(Element):定义了 accept 方法,该方法接受访问者对象作为参数,以便访问者访问该元素。

  • 具体元素(ConcreteElement):实现了元素定义的 accept 方法,并在该方法中调用访问者的相应方法。

  • 对象结构(ObjectStructure):维护了元素的集合,并提供了一个接受访问者的方法,以便访问者访问该集合中的元素。

2、优点

  • 访问者模式符合单一职责原则,有优秀的扩展性、灵活性

  • 可以对功能进行统一,适合数据结构相对稳定的系统

3、缺点

  • 具体元素对访问者公布细节(让访问者关注其他类的内部细节,是迪米特法则不建议的,这样造成了具体元素变更比较困难)

  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素(ObjectStructure中的attach方法直接依赖的是ElementA/B类,而不是具体接口Element)

迭代器模式

迭代器模式是一种不暴露底层实现来实现遍历聚合同类对象的集合的设计模式。

介绍

1、迭代器模式是一种行为型模式

2、如果我们集合元素是不同的方式实现的,有数组、Java集合类等等,当客户端要遍历这些集合元素时,我们就要用多种遍历方式,还会暴露元素内部结构,这个时候可以考虑使用迭代器来解决了。

3、迭代器模式,提供一种遍历集合元素的统一接口,用一致的方式遍历元素,不需要知道集合对象的底层表示。(简单说:不暴露接口结构)

image-20240312193414783

说明:

1、Iterator:迭代器接口,是系统提供,函数hasNext、next、remove
2、ConcreteIterator:具体迭代器类,管理迭代
3、Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦
4、ConcreteAggregate:具体的聚合持有对象集合,并提供一个方法,返回迭代器,该迭代器可以正确遍历集合
5、Client:客户端。通过Iterator和Aggregate依赖子类

代码

image-20240312200924005

package com.pxl.test.diedaiqi;
​
import java.util.ArrayList;
import java.util.List;
​
// 定义一个迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}
​
// 定义一个集合接口
interface Aggregate<T> {
    Iterator<T> createIterator();
}
​
// 实现具体的迭代器
class ConcreteIterator<T> implements Iterator<T> {
    private List<T> elements;
    private int position = 0;
​
    public ConcreteIterator(List<T> elements) {
        this.elements = elements;
    }
​
    @Override
    public boolean hasNext() {
        return position < elements.size();
    }
​
    @Override
    public T next() {
        if (hasNext()) {
            return elements.get(position++);
        }
        throw new IndexOutOfBoundsException("No more elements to iterate.");
    }
}
​
// 实现具体的集合类
class ConcreteAggregate<T> implements Aggregate<T> {
    private List<T> elements;
​
    public ConcreteAggregate() {
        this.elements = new ArrayList<>();
    }
​
    public void addElement(T element) {
        elements.add(element);
    }
​
    @Override
    public Iterator<T> createIterator() {
        return new ConcreteIterator<>(elements);
    }
}
​
public class Main {
    public static void main(String[] args) {
        ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
        aggregate.addElement("Apple");
        aggregate.addElement("Banana");
        aggregate.addElement("Orange");
​
        // 使用迭代器遍历集合
        Iterator<String> iterator = aggregate.createIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

Java集合中Iterator的使用

image-20240312201253805

角色分析:

1、内部类Itr充当了实现迭代器接口Iterator的类,作为在ArrayList中的内部类
2、List接口充当了聚合接口,含有一个iterator()方法,返回一个迭代器类
3、ArrayList就是实现聚合接口的List子类,实现了iterator()方法
4、Iterator接口系统提供
5、迭代器模式解决了不同集合(ArrayList、LinkedList等集合)统一遍历问题

总结

个人理解:迭代器模式是一个调用聚合对象的类,返回一个遍历它所有对象的模式。

迭代器模式是一种设计模式,它提供了一种方法,通过该方法,客户端可以通过迭代器对象来遍历聚合对象中的元素。具体来说,迭代器模式包括两个主要角色:
​
聚合对象(Aggregate):聚合对象是包含一组元素的对象,它提供了一个方法用于创建迭代器对象,该迭代器对象用于遍历聚合对象中的元素。
​
迭代器对象(Iterator):迭代器对象负责遍历聚合对象中的元素,它提供了一系列方法,如检查是否有下一个元素、获取下一个元素等。
​
因此,迭代器模式可以理解为一个调用聚合对象的类(即迭代器对象),返回一个用于遍历聚合对象所有元素的模式。通过迭代器模式,客户端可以透明地遍历聚合对象的元素,而不必了解聚合对象的内部表示方式或数据结构。

迭代器模式优点

1. 提供一个统一的方法遍历对象,客户不用考虑聚合的类型,使用一种方法就可以遍历对象了(List集合中的Iterator)
2. 隐藏了聚合的内部结构,客户端要遍历聚合的时候,只能取到迭代器,而不会知道聚合的具体组成
3. 提供了一种设计思想:一个类应该只有一个引起变化的原因(单一职责原则)。在聚合类中,我们把迭代器类分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来,集合改变的话,只会影响聚合对象。如果遍历方式改变的话,只会影响迭代器。
4. 当要展示一组相似对象,或遍历一组相同对象时使用,适合使用迭代器模式

迭代器模式缺点

1. 每个聚合对象都要有一个迭代器,会生成多个迭代器不好管理类(ArrayList需要一个迭代器;LinkedList也要迭代器...) 
  • 34
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值