java设计模式学习笔记

设计模式7大原则

1、单一职责:每个类或者对象有严格的标准划分,一个类只能负责自己标准范围的功能

2、接口隔离:抽象的接口与接口之间要隔离开,减低接口之间的耦合

3、依赖倒转:把业务都抽象成接口来减少代码的耦合,利用接口来进行解耦,让代码之间有一层缓冲层,有利于维护和扩展

(1)new不同的接口传参

(2)利用类的构造方法

(3)利用set方法

4、里氏替换:通过继承的方式让每个类有统一的聚合,但是父类尽量不能修改,防止影响继承的子类,子类非必要情况不能重写父类的方法,子类可以通过相互依赖的方式进行解耦

5、开闭原则:对提供方开启,对使用方关闭,对代码的扩展是要增加而不是修改原来的代码

6、迪米特法则:对依赖的类知道的细节越少越好,对依赖的类的使用是不需要知道细节,只需要知道调用方法就行

7、合成复用:尽可能使用组合或者聚合的方式来使用类,避免使用继承来使用类

设计模式:3大类型,共23种设计模式

1、创建类型:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

2、结构类型:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

3、行为类型:模板方式模式、命令模式、访问者模式、迭代模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式

单例模式:保证整个系统只有一个实例对象,并且只有一个获取该实例对象的方法

单例模式的实现方式:(1)饿汉式(静态常量)(2)饿汉式(静态代码块)(3)懒汉式(线程不安全)(4)懒汉式(线程安全,同步方法)(5)懒汉式(线程不安全,同步代码块)(6)双重检查(开发推荐使用)(7)静态内部类(开发推荐使用)(8)枚举(开发推荐使用)

  1. 饿汉式(静态常量)
class Person {
	
  private final static Person person = new Person();
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  
  public static Person getInstance(){
  	return person;
  }

}
  1. 饿汉式(静态代码块)
class Person {
	
  private static Person person;
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  //静态代码块 
  static {
  	person = new Person();
  }
  
  public static Person getInstance(){
  	return person;
  }

}
  1. 懒汉式(线程不安全)开发不能使用
class Person {
	
  private static Person person;
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  
  public static Person getInstance(){
      if(person == null){
          person = new person();
      }
      return person;
  }

}
  1. 懒汉式(线程安全,同步方法)有性能问题,不推荐使用
class Person {
	
  private static Person person;
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  
  public static synchronized Person getInstance(){
      if(person == null){
          person = new person();
      }
      return person;
  }

}
  1. 懒汉式(线程不安全,同步代码块)开发不能使用
class Person {
	
  private static Person person;
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  
  public static Person getInstance(){
      if(person == null){
          synchronized (Person.class) {
              person = new person();
          }
      }
      return person;
  }

}
  1. 双重检查(开发推荐使用)
class Person {
	
  private static volatile Person person;
  
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  
  public static Person getInstance(){
      if(person == null){
          synchronized (Person.class) {
              if(person == null){
                  person = new person();
              }
          }
      }
      return person;
  }

}
  1. 静态内部类(开发推荐使用)
class Person {
	
  //私有化构造函数,外部不能new
  private Person(){
  
  }
  //装载Person类的时候并不会装载PersonInstance,在使用的时候才会装载
  private static class PersonInstance{
  	private static final Person person = new Person();
  }
  
  public static Person getInstance(){
      return PersonInstance.person;
  }

}
  1. 枚举(推荐使用)
enum Person {
    INSTANCE;

}

简单工厂模式:通过一个简单的工厂类来生产不同类型的对象

工厂方法模式:抽象出一个父类,把细节推迟到子类去实现,也就是把业务细节给到不同类型的子类去实现

抽象工厂模式:抽象出一个抽象工厂,根据不同类型的工厂进行分类实现抽象工厂,形成多个不同分类的工厂

原型模式:

浅拷贝:类实现cloneable接口,调用clone()方法,基本数据类型会复制一份给新的对象,数组或引用对象则是使用复制对象的引用地址

深拷贝:基本数据类型和对象引用都是复制一份给新的对象

(1)重写clone()方法

(2)利用序列化和反序列性,把对象转为流的方式进行处理,使用到字节流和对象流

建造者模式:

建造者模式中的4个角色:(1)产品角色(2)抽象建造者(3)具体建造者(4)指挥者

通过指挥者来构建出产品,抽象建造者定义建造流程,指挥者中使用抽象的建造者来实现建造流程,建造细节在具体建造者中实现,最后返回产品

适配器模式:

(1)类适配器模式:使用到类的继承方式,直接继承被适配对象

(2)对象适配器模式:使用聚合的方式,把被适配对象聚合到适配器中

(3)接口适配器模式:通过抽象类来选择使用接口的方法,抽象类空实现接口的所有方法,使用的时候可以根据需要来重写抽象类中的接口方法

代码示例:

//此接口的一大问题是抽象方法太多了,
//如果我们要用这个接口,意味着我们要实现每一个抽象方法
public interface FileAlterationListener {
    void onStart(final FileAlterationObserver observer);
    void onDirectoryCreate(final File directory);
    void onDirectoryChange(final File directory);
    void onDirectoryDelete(final File directory);
    void onFileCreate(final File file);
    void onFileChange(final File file);
    void onFileDelete(final File file);
    void onStop(final FileAlterationObserver observer);
}
// 适配器空实现,然后再通过继承适配器选择实现需要用到的方法
public class FileAlterationListenerAdaptor implements FileAlterationListener {

    public void onStart(final FileAlterationObserver observer) {
    }

    public void onDirectoryCreate(final File directory) {
    }

    public void onDirectoryChange(final File directory) {
    }

    public void onDirectoryDelete(final File directory) {
    }

    public void onFileCreate(final File file) {
    }

    public void onFileChange(final File file) {
    }

    public void onFileDelete(final File file) {
    }

    public void onStop(final FileAlterationObserver observer) {
    }
}

对象适配器:

public interface Duck {
    public void quack(); // 鸭的呱呱叫
    public void fly(); // 飞
}

public interface Cock {
    public void gobble(); // 鸡的咕咕叫
    public void fly(); // 飞
}

public class WildCock implements Cock {
    public void gobble() {
        System.out.println("咕咕叫");
    }
    public void fly() {
        System.out.println("鸡也会飞哦");
    }
}
// 鸭接口有 fly() 和 quare() 两个方法,鸡 Cock 如果要冒充鸭,fly() 方法是现成的,
但是鸡不会鸭的呱呱叫,没有 quack() 方法。这个时候就需要适配了
public class CockAdapter implements Duck {

    Cock cock;
    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
      public CockAdapter(Cock cock) {
        this.cock = cock;
    }

    // 实现鸭的呱呱叫方法
    @Override
      public void quack() {
        // 内部其实是一只鸡的咕咕叫
        cock.gobble();
    }

      @Override
      public void fly() {
        cock.fly();
    }
}

桥接模式:把抽象和具体实现分开,通过抽象来作为一个桥梁来连接具体的实现

代码示例:

我们首先需要一个桥梁,它是一个接口,定义提供的接口方法。
public interface DrawAPI {
   public void draw(int radius, int x, int y);
}

然后是一系列实现类:
public class RedPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class GreenPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class BluePen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}

定义一个抽象类,此类的实现类都需要使用 DrawAPI:
public abstract class Shape {
    protected DrawAPI drawAPI;
    protected Shape(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }
    public abstract void draw();
}
定义抽象类的子类:
// 圆形
public class Circle extends Shape {
    private int radius;
    public Circle(int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.radius = radius;
    }
    public void draw() {
        drawAPI.draw(radius, 0, 0);
    }
}
// 长方形
public class Rectangle extends Shape {
    private int x;
    private int y;
    public Rectangle(int x, int y, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
    }
    public void draw() {
        drawAPI.draw(0, x, y);
    }
}

最后,我们来看客户端演示:
public static void main(String[] args) {
    Shape greenCircle = new Circle(10, new GreenPen());
    Shape redRectangle = new Rectangle(4, 8, new RedPen());
    greenCircle.draw();
    redRectangle.draw();
}

装饰器模式:通过对类的抽象和打包,把被装饰者类抽象定义在一个装饰者类内部供装饰者使用

首先,定义饮料抽象基类:
public abstract class Beverage {
      // 返回描述
      public abstract String getDescription();
      // 返回价格
      public abstract double cost();
}

然后是三个基础饮料实现类,红茶、绿茶和咖啡:
public class BlackTea extends Beverage {
      public String getDescription() {
        return "红茶";
    }
      public double cost() {
        return 10;
    }
}
public class GreenTea extends Beverage {
    public String getDescription() {
        return "绿茶";
    }
      public double cost() {
        return 11;
    }
}
...// 咖啡省略

定义调料,也就是装饰者的基类,此类必须继承自 Beverage:
// 调料
public abstract class Condiment extends Beverage {

}

然后我们来定义柠檬、芒果等具体的调料,它们属于装饰者,毫无疑问,这些调料肯定都需要继承调料 Condiment 类:
public class Lemon extends Condiment {
    private Beverage bevarage;
    // 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
    // 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
    public Lemon(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        // 装饰
        return bevarage.getDescription() + ", 加柠檬";
    }
    public double cost() {
        // 装饰
        return beverage.cost() + 2; // 加柠檬需要 2 元
    }
}

public class Mango extends Condiment {
    private Beverage bevarage;
    public Mango(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        return bevarage.getDescription() + ", 加芒果";
    }
    public double cost() {
        return beverage.cost() + 3; // 加芒果需要 3 元
    }
}
...// 给每一种调料都加一个类

看客户端调用:
public static void main(String[] args) {
    // 首先,我们需要一个基础饮料,红茶、绿茶或咖啡
    Beverage beverage = new GreenTea();
    // 开始装饰
    beverage = new Lemon(beverage); // 先加一份柠檬
    beverage = new Mongo(beverage); // 再加一份芒果

    System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
    //"绿茶, 加柠檬, 加芒果 价格:¥16"
}

代理模式:

对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现

代码实例:

public interface FoodService {
    Food makeChicken();
    Food makeNoodle();
}

public class FoodServiceImpl implements FoodService {
    public Food makeChicken() {
          Food f = new Chicken()
        f.setChicken("1kg");
          f.setSpicy("1g");
          f.setSalt("3g");
        return f;
    }
    public Food makeNoodle() {
        Food f = new Noodle();
        f.setNoodle("500g");
        f.setSalt("5g");
        return f;
    }
}

// 代理要表现得“就像是”真实实现类,所以需要实现 FoodService
public class FoodServiceProxy implements FoodService {

    // 内部一定要有一个真实的实现类,当然也可以通过构造方法注入
    private FoodService foodService = new FoodServiceImpl();

    public Food makeChicken() {
        System.out.println("我们马上要开始制作鸡肉了");

        // 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,
        // 代理只是在核心代码前后做些“无足轻重”的事情
        Food food = foodService.makeChicken();

        System.out.println("鸡肉制作完成啦,加点胡椒粉"); // 增强
          food.addCondiment("pepper");

        return food;
    }
    public Food makeNoodle() {
        System.out.println("准备制作拉面~");
        Food food = foodService.makeNoodle();
        System.out.println("制作完成啦")
        return food;
    }
}

客户端调用,注意,我们要用代理来实例化接口:

// 这里用代理类来实例化
FoodService foodService = new FoodServiceProxy();
foodService.makeChicken();

外观模式:客户端不再需要关注实例化时应该使用哪个实现类,直接调用门面提供的方法就可以了

首先,我们定义一个接口:
public interface Shape {
   void draw();
}

定义几个实现类:
public class Circle implements Shape {
    @Override
    public void draw() {
       System.out.println("Circle::draw()");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
       System.out.println("Rectangle::draw()");
    }
}

客户端调用:
public static void main(String[] args) {
    // 画一个圆形
      Shape circle = new Circle();
      circle.draw();

      // 画一个长方形
      Shape rectangle = new Rectangle();
      rectangle.draw();
}

以上是我们常写的代码,我们需要画圆就要先实例化圆,画长方形就需要先实例化一个长方形,然后再调用相应的 draw() 方法。

下面,我们看看怎么用门面模式来让客户端调用更加友好一些。

我们先定义一个门面:
public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }

  /**
   * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
   */

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

看看现在客户端怎么调用:
public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();

  // 客户端调用现在更加清晰了
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();        
}

组合模式:组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // 下属

   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }

   public void add(Employee e) {
      subordinates.add(e);
   }

   public void remove(Employee e) {
      subordinates.remove(e);
   }

   public List<Employee> getSubordinates(){
     return subordinates;
   }

   public String toString(){
      return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
   }   
}

通常,这种类需要定义 add(node)、remove(node)、getChildren() 这些方法

享元模式:复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中

策略模式:

首先,先定义一个策略接口:
public interface Strategy {
   public void draw(int radius, int x, int y);
}

然后我们定义具体的几个策略:
public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

使用策略的类:
public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

客户端演示:
public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
      context.executeDraw(10, 0, 0);
}

观察者模式:观察者订阅自己关心的主题和主题有数据变化后通知观察者们

观察者:
public class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
        // 数据已变更,通知观察者们
        notifyAllObservers();
    }
    // 注册观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }
    // 通知观察者们
    public void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

定义观察者接口:
public abstract class Observer {
    protected Subject subject;
    public abstract void update();
}

其实如果只有一个观察者类的话,接口都不用定义了,不过,通常场景下,既然用到了观察者模式,我们就是希望一个事件出来了,会有多个不同的类需要处理相应的信息。比如,订单修改成功事件,我们希望发短信的类得到通知、发邮件的类得到通知、处理物流信息的类得到通知等。

我们来定义具体的几个观察者类:
public class BinaryObserver extends Observer {
    // 在构造方法中进行订阅主题
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        // 通常在构造方法中将 this 发布出去的操作一定要小心
        this.subject.attach(this);
    }
    // 该方法由主题类在数据变更的时候进行调用
    @Override
    public void update() {
        String result = Integer.toBinaryString(subject.getState());
        System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
    }
}

public class HexaObserver extends Observer {
    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }
    @Override
    public void update() {
        String result = Integer.toHexString(subject.getState()).toUpperCase();
        System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
    }
}

客户端使用也非常简单:
public static void main(String[] args) {
    // 先定义一个主题
    Subject subject1 = new Subject();
    // 定义观察者
    new BinaryObserver(subject1);
    new HexaObserver(subject1);

    // 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
    subject.setState(11);
}

责任链模式:责任链通常需要先建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去

首先,我们要定义流程上节点的基类:
public abstract class RuleHandler {
    // 后继节点
    protected RuleHandler successor;

    public abstract void apply(Context context);

    public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }

    public RuleHandler getSuccessor() {
        return successor;
    }
}

接下来,我们需要定义具体的每个节点了。

校验用户是否是新用户:
public class NewUserRuleHandler extends RuleHandler {
    public void apply(Context context) {
        if (context.isNewUser()) {
            // 如果有后继节点的话,传递下去
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("该活动仅限新用户参与");
        }
    }
}

验用户所在地区是否可以参与:
public class LocationRuleHandler extends RuleHandler {
    public void apply(Context context) {
        boolean allowed = activityService.isSupportedLocation(context.getLocation);
        if (allowed) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
        }
    }
}

校验奖品是否已领完:
public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
        int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("您来得太晚了,奖品被领完了");
        }
    }
}

客户端:
public static void main(String[] args) {
    RuleHandler newUserHandler = new NewUserRuleHandler();
    RuleHandler locationHandler = new LocationRuleHandler();
    RuleHandler limitHandler = new LimitRuleHandler();

    // 假设本次活动仅校验地区和奖品数量,不校验新老用户
    locationHandler.setSuccessor(limitHandler);

    locationHandler.apply(context);
}

模板方法:

在含有继承结构的代码中,模板方法模式是非常常用的。

通常会有一个抽象类:
public abstract class AbstractTemplate {
    // 这就是模板方法
    public void templateMethod() {
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }

    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }

    // 留给子类实现
    protected abstract void apply();

    protected void end() {
    }
}

模板方法中调用了 3 个方法,其中 apply() 是抽象方法,子类必须实现它,其实模板方法中有几个抽象方法完全是自由的,我们也可以将三个方法都设置为抽象方法,让子类来实现。也就是说,模板方法只负责定义第一步应该要做什么,第二步应该做什么,第三步应该做什么,至于怎么做,由子类来实现。

我们写一个实现类:
public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }

    public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

客户端调用演示:
public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
    // 调用模板方法
    t.templateMethod();
}

状态模式:

在含有继承结构的代码中,模板方法模式是非常常用的。

通常会有一个抽象类:
public abstract class AbstractTemplate {
    // 这就是模板方法
    public void templateMethod() {
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }

    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }

    // 留给子类实现
    protected abstract void apply();

    protected void end() {
    }
}

模板方法中调用了 3 个方法,其中 apply() 是抽象方法,子类必须实现它,其实模板方法中有几个抽象方法完全是自由的,我们也可以将三个方法都设置为抽象方法,让子类来实现。也就是说,模板方法只负责定义第一步应该要做什么,第二步应该做什么,第三步应该做什么,至于怎么做,由子类来实现。

我们写一个实现类:
public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }

    public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

客户端调用演示:
public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
    // 调用模板方法
    t.templateMethod();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值