Spring中的部分设计模式

目录:

目录

设计模式在应用中遵循六大原则:

a、开闭原则

b、里氏代换原则

c、依赖倒转原则

d、接口隔离原则

e、迪米特法则(最少知道原则)

f、合成复用原则

设计模式之间的关系图 

1、工厂模式(Factory)

1.1、小作坊模式

1.2、普通工厂模式

1.3、抽象工厂模式

2、单例模式(Singleton)

2.1、饿汉式

2.2、懒汉式

2.3、静态内部类的方式

优化后的静态内部类单例:

2.4、注册登记式

2.5、序列化和反序列化保证单例

3、原型模式(Prototype)

4、代理模式(proxy)

5、策略模式(Strategy)

6、模板方法模式(Template Method) 

7、委派模式(Delegate)

8、适配器模式(Adapter)

9、装饰者模式(Decorator)(包装器模式) 

10、观察者模式(Observer)

各种设计模式的使用场景


Spring 中常用的设计模式

 

设计模式在应用中遵循六大原则:

a、开闭原则

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

b、里氏代换原则

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

c、依赖倒转原则

这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

d、接口隔离原则

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

e、迪米特法则(最少知道原则)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

f、合成复用原则

原则是尽量使用合成/聚合的方式,而不是使用继承。

设计模式之间的关系图 

1、工厂模式(Factory)

应用场景:又叫做静态工厂方法(StaticFactory Method)模式,但不属于 23 种设计模式之一。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

生活中的案例:

实体类

public interface Milk {
    public String getName();
}

public class Telunsu implements Milk{
    @Override
    public String getName() {
        return "telunsu";
    }
}

public class MengNiu  implements Milk{
    @Override
    public String getName() {
        return "mengniu";
    }
}

public class YiLi  implements Milk{
    @Override
    public String getName() {
        return "yili";
    }
}

1.1、小作坊模式

/**
 * 简单工厂模式
 */
public class SimpleFactory {
    /**
     * 小作坊模式
     * @param name
     * @return
     */
    public Milk getMile(String name){
        if("telunsu".equals(name)){
            return new Telunsu();
        }else if("yili".equals(name)){
            return new YiLi();
        }else if("mengniu".equals(name)){
            return new MengNiu();
        }
        return null;
    }
}


public class SimpleFactoryTest {
    public static void main(String[] args) {
        //这个new的过程是比较复杂的
        System.out.println(new Telunsu().getName());
        //小作坊模式
        SimpleFactory sf = new SimpleFactory();
        System.out.println(sf.getMile("yili").getName());
    }
}

1.2、普通工厂模式

public interface Factory {
    //工厂生产产品的出口
   Milk getMilk();
}
public class TelunsuFactory implements Factory{
    @Override
    public Milk getMilk() {
        return new Telunsu();
    }
}
public class MengniuFactory implements Factory{
    @Override
    public Milk getMilk() {
        return new MengNiu();
    }
}
public class YiliFactory implements Factory{
    @Override
    public Milk getMilk() {
        return new YiLi();
    }
}
public class SimpleFactoryTest {
    public static void main(String[] args) {
        //初级工厂 用户不关心配方 只用关注品牌
        Factory factory = new MengniuFactory();
        System.out.println(factory.getMilk().getName());
    }
}

1.3、抽象工厂模式

/**
 * 抽象工厂是用户的主入口
 * 在Spring中应用的最为广泛的一种设计模式
 */
public abstract class AbstractFactory {
    /**
     * 不关心是怎么生产的
     * 公共的逻辑 方便统一管理
     * 容易扩展
     * @return
     */
    public abstract Milk getMengniu();
    public abstract Milk getYili();
    public abstract Milk getTelunsu();
}
public class AbstractFactoryTest {
    public static void main(String[] args) {
        //减少配置,以及传入的参数,防止错误的发生,使用户更加方便
        MilkFactory factory = new MilkFactory();
        System.out.println(factory.getMengniu());
        System.out.println(factory.getYili());
        System.out.println(factory.getTelunsu());
    }
}

2、单例模式(Singleton)

应用场景:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory。但没有从构造器级别去控制单例,这是因为 Spring 管理的是是任意的 Java 对象。 Spring 下默认的 Bean 均为单例。

解决一个并发访问的时候线程安全问题

保证单例的技术方案有很多种:

饿汉式、懒汉式、注册登记式、枚举式、序列化与反序列化的时候出现多例。

 

2.1、饿汉式

在实例使用之前,不管你用不用都先给new出来,避免线程安全问题。

public class Hungry {


    private Hungry(){}


    //先静态、后动态、
    //先属性、后方法
    //先上后下
    private static final Hungry hungry = new Hungry();


    public static Hungry getInstance(){
        System.out.println(System.nanoTime()+" "+hungry);
        return hungry;
    }


}


public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 100;
        CountDownLatch latch = new CountDownLatch(count);
        final Set<Hungry> syncSet = Collections
                                    .synchronizedSet(new HashSet<>());
        
        for (int i = 0; i < count; i++){
            new Thread(()->syncSet.add(Hungry.getInstance())).start();
            latch.countDown();
        }


        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  

返回结果:不同时间点获取到的对象是相同的

987494937519600 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494937525200 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494937600000 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494937710800 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494937964700 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494937968200 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494938080100 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494938158400 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494938233200 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f
987494938208900 com.zyz.spring5.codes.sjms.singleton.Hungry@5a05214f

 

2.2、懒汉式

默认在加载的时候不进行实例化,在使用的时候才进行实例化(延时加载)。

public class Lazzy {
    private Lazzy(){}
    private static  Lazzy lazzy = null;
    public static Lazzy getInstance(){
        if(lazzy == null){
            lazzy = new Lazzy();
        }
        return lazzy;
    }
}
public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 100;
        CountDownLatch latch = new CountDownLatch(count);
        final Set<Hungry> syncSet = Collections
                                    .synchronizedSet(new HashSet<>());


        for (int i = 0; i < count; i++){
            new Thread(()->{
                syncSet.add(Hungry.getInstance());
                try {
                    latch.await();
                    Object obj = Lazzy.getInstance();
                    System.out.println(System.nanoTime()+" "+obj);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }).start();
            latch.countDown();
        }
    }

返回结果:不同时间访问,会出现多个对象,是线程不安全的

990373984480500 com.zyz.spring5.codes.sjms.singleton.Lazzy@41cc0e2f
990373984550200 com.zyz.spring5.codes.sjms.singleton.Lazzy@3ca815a8
990373984612400 com.zyz.spring5.codes.sjms.singleton.Lazzy@3ca815a8
990373984542700 com.zyz.spring5.codes.sjms.singleton.Lazzy@3ca815a8

懒汉式的改进方案:添加同步锁

public class LazzyTwo {
    private LazzyTwo(){}
    private static LazzyTwo lazzy = null;
    public static synchronized LazzyTwo getInstance(){
        if(lazzy == null){
            lazzy = new LazzyTwo();
        }
        return lazzy;
    }
}
public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 100;
        CountDownLatch latch = new CountDownLatch(count);
        final Set syncSet = Collections
                            .synchronizedSet(new HashSet());
       
        for (int i = 0; i < count; i++){
            new Thread(()->{
                try {
                    latch.await();
                    Object obj = LazzyTwo.getInstance();
                    System.out.println(System.nanoTime()+" "+obj);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            latch.countDown();
        }
    }
}

2.3、静态内部类的方式

public class LazzyThree {
    public static final LazzyThree getInstance(){
        return LazyHolder.LAZY;
    }


    private static class LazyHolder{
        private static final LazzyThree LAZY = new LazzyThree();
    }
}
package com.zyz.spring5.codes.sjms.singleton;


import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;


public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 100;
        CountDownLatch latch = new CountDownLatch(count);
        final Set syncSet = Collections
                            .synchronizedSet(new HashSet());


        for (int i = 0; i < count; i++){
            new Thread(()->{
                try {
                    latch.await();
                    Object obj = LazzyThree.getInstance();
                    System.out.println(System.nanoTime()+" "+obj);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }).start();
            latch.countDown();
        }
    

优化后的静态内部类单例:

/**
 * 懒汉式单例:
 * 特点:在外部类被调用的时候内部类才会被加载
 * 内部类一定是在方法调用之前初始化
 * 巧妙避免了线程安全问题
 *
 * 这种形式兼饿汉式的内存浪费,也兼顾synchronized性能问题
 * 完美地屏蔽了这两个缺点
 */
public class LazzyThree {
    private static boolean initialized = false;
    //默认使用LazyThree的时候,会优先加载初始化内部类
    //如果没有使用的话,内部类是不加载的
    private LazzyThree(){


        synchronized (LazzyThree.class){
            if (initialized){
                throw new RuntimeException("单例被侵犯");
            }else{
                initialized = true;
            }
        }
    }
    
    /**
     * 每一个关键字都不是多余的
     * static 是为了使单例的空间共享
     * 保证这个方法不会被重写、重载
     * @return
     */
    public static final LazzyThree getInstance(){
        //在返回结果以前一定先加载内部类
        return LazyHolder.LAZY;
    }


    /**
     * 默认不加载
     */
    private static class LazyHolder{
        private static final LazzyThree LAZY = new LazzyThree();
    }
}
public class LazyThreeTest {
    public static void main(String[] args) {
        Class<?> clazz = LazzyThree.class;
        //通过反射拿到私有的构造方法
        Constructor[] cs = clazz.getDeclaredConstructors();
        Arrays.asList(cs).stream().forEach(c -> {
            //强制访问
            c.setAccessible(true);
            try {
                Object o1 = c.newInstance();
                //调用了两次构造方法,相当于new了两次
                Object o2 = c.newInstance();
                System.out.println(o1 == o2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

2.4、注册登记式

每次使用一次,都往第一个固定的容器中去注册并且将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。IOC中的单例模式就是典型的注册登记式单例

public class RegisterMap {
    private static Map<String,Object> register = new ConcurrentHashMap<>();
    public static RegisterMap getInstance(String name){
        if(name == null){
            name = RegisterMap.class.getName();
        }


        if(register.get(name) == null){
            try{
                register.put(name,new RegisterMap());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return (RegisterMap)register.get(name);
    }
}

2.5、序列化和反序列化保证单例

重写readResolve()

public class Seriable implements Serializable {
    public final static  Seriable INSTANCE = new Seriable();
    private Seriable(){}
    public static Seriable getInstance(){
        return INSTANCE;
    }


    //序列化和反序列化的协议
    private Object readResolve(){
        return INSTANCE;
    }
}

单例模式:初衷就是为了使资源能够共享,只需要赋值或者初始化一次,大家都能重复利用

Listener本身单例、日历Calender、IOC容器、配置信息Config。

技术方案:保证整个运行过程只有一份。

解决问题:在恶劣环境(程序的健全性)

饿汉式:在类加载的时候就立即初始化,并且创建单例对象

优点:没有添加任何的锁,执行效率比较高;

缺点:;浪费了内存,有可能造成资源浪费;

绝对的线程安全,在线程还没有出现以前就是实例化了,不可能存在访问安全问题。

汉式:在外部使用的时候才开始进行实例化,调用之前判断实例是不是进行了初始化,如果没有进行初始化那么就进行初始化,如果初始化了直接返回之前已经保存好的结果。内部类实现懒汉式只有在外部类被调用的时候才会加载。

3、原型模式(Prototype)

DTO、VO、POJO、Entity

DTO和VO之间存在一些属性名称、类型都相同;

数据库中表查询出来的对象会赋值给DTO;

MVC中的Model

把DTO中的值会赋值给VO;

再把VO中的值传输到View中;

复制,就是要把DTO中每一个属性的值赋值给VO中的每一个属性的值,属性名称相同、属性类型相同。

apache反射去实现(原型模式)

clone()

spring中把对象配置的依赖关系,在每次使用对象之前,都会创建一个新的对象,并且会将依赖关系完整的复制给这个新创建的对象 scope="prototype" 给Spring默认是单例模式。

 

应用场景:原型模式就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。所谓原型模式,就是 Jav 中的克隆技术,以某个对象为原型。复制出新的对象。显然新的对象具备原型对象的特点,效率高(避免了重新执行构造过程步骤)。

public class Monkey {
    private int height;
    private int weight;
    private Date birthday;


    public int getHeight() {
        return height;
    }


    public void setHeight(int height) {
        this.height = height;
    }


    public int getWeight() {
        return weight;
    }


    public void setWeight(int weight) {
        this.weight = weight;
    }


    public Date getBirthday() {
        return birthday;
    }


    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}


public class SunWuKong extends Monkey implements Cloneable,Serializable{
    private Jingubang jingubang;


    public SunWuKong() {
        this.setBirthday(new Date());
        this.jingubang = new Jingubang();
    }


    public Jingubang getJingubang() {
        return jingubang;
    }


    public void setJingubang(Jingubang jingubang) {
        this.jingubang = jingubang;
    }


    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        return deepClone();
    }


    public Object deepClone(){
        try {
            ByteArrayOutputStream bos =  new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis = 
                          new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            SunWuKong copy = (SunWuKong) ois.readObject();
            copy.setBirthday(new Date());
            return copy;


        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
public class Jingubang  implements Serializable {
    private float h = 100;
    private float d = 10;
    public void big(){
        this.h  *= 2;
        this.d *= 2;
    }
    public void small(){
        this.h /= 2;
        this.d /= 2;
    }
}
public class CloneTest {
    public static void main(String[] args) {
        try {
            SunWuKong swk = new SunWuKong();
            SunWuKong cl= (SunWuKong) swk.clone();
            System.out.println(swk.getJingubang()==cl.getJingubang());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
     

4、代理模式(proxy)

代理模式的作用:

AOP实现、拦截器、中介、黄牛、媒婆、解耦、专人做专事、自己不想做但有不得不做的事增强。

代理分为:静态代理和动态代理;

动态代理:分为代理角色 和 被代理角色(目标对象),由被代理角色做最终的决定;

代理角色通常来说会持有代理角色对象的引用(以便于代理角色完成工作之前或者之后能找到被代理对象,能够通知被代理对象)

静态代理和动态代理最根本的区别:

静态代理:在代理之前,所有的东西都是已知的(人工);

动态代理:在代理之前,所有的东西都是未知的(自动化);

应用场景:为其他对象提供一种代理以控制对这个对象的访问。从结构上来看和 Decorator 模式类似, 但 Proxy 是控制,更像是一种对功能的限制,而 Decorator 是增加职责。

Spring 的 Proxy 模式在 AOP 中有体现,比如 JdkDynamicAopProxy 和Cglib2AopProxy。 

public interface Person {
    public void findGirlFriend();
    public void findJob();
//    public void work();
//    public void rentHouse();

    //.........
}

public class LaoTian implements Person {

    @Override
    public void findGirlFriend(){
        println("XIONG大");
        println("肤白,貌美");
        println("大长腿");
    }

    @Override
    public void findJob() {
        println("我要去找工作");
        println("我要去一线大厂");
        println("我要月薪25k+");
    }

    private static void println(Object obj){
        System.out.println(obj);
    }
}

public class JDK58 implements InvocationHandler, PrintTools {
    //被代理的对象,把引用给保存下来
    private Person target;

    public Object getInstance(Person target) throws  Exception{
        this.target = target;

        Class<?> clazz = target.getClass();

        //用来生成一个新的对象,底层就是使用字节码重组来实现的
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        println("我是58同城,我可以给你找房子,找工作,现在已经收到了你的需求");
        println("开始寻找");
        method.invoke(this.target,args);
        println("如果感觉满意,那么就可以给你发出面谈邀请了");
        return null;
    }
}

public class JDKProxyTest{
    public static void main(String[] args) {
        try {
            Person obj = (Person) new JDK58().getInstance(new LaoTian());
            obj.findJob();
            println(obj.getClass());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void println(Object obj){
        System.out.println(obj);
    }
}

 println("======================================================================");


public class ZhangSan {
    public void findGirlFriend(){
        System.out.println("B..");
        System.out.println("肤白,貌美");
        System.out.println("160左右");
        System.out.println("苗条");
    }
}
public class CglibMeiPo implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception{
        Enhancer enhancer = new Enhancer();
        //要把哪个类设置为即将生成的新类的父类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我是媒婆,我可以给你找对象,现在已经收到了你的需求");
        System.out.println("开始寻找");
        //业务的增强
        methodProxy.invokeSuper(o,objects);
        System.out.println("如果感觉满意,那么就可以给你发出面谈邀请了");
        return null;
    }

}
public class CglibTest {
    public static void main(String[] args) {

        try {
            ZhangSan obj = (ZhangSan) new CglibMeiPo().getInstance(ZhangSan.class);
            obj.findGirlFriend();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void println(Object obj){
        System.out.println(obj);
    }
}

5、策略模式(Strategy)

应用场景:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

Spring 中在实例化对象的时候用到 Strategy 模式,在 SimpleInstantiationStrategy 有使用。

应用场景:比较器、旅行路线、固定算法、买东西支付

根据用户的需求处理数据得时候需要对算法做出选择,但是这些算法是固定得(不再发生变化的算法),但是能够进行一些扩展(增加一些固定的算法),客户从已有的算法中进行选择。

 

用设计模式,是用来解决复杂问题,把复杂的问题变得简单化。

不要生搬硬套:容易把问题复杂化。

public interface Payment {
    public PayState pay(String uid, Double amount);
}
public class AliPay implements Payment{
    @Override
    public PayState pay(String uid, Double amount) {
        System.out.println("欢迎使用支付宝");
        return new PayState(200,"支付成功",amount);
    }
}
public class JDPay implements Payment{
    @Override
    public PayState pay(String uid, Double amount) {
        System.out.println("欢迎使用京东金融");
        return new PayState(200,"支付成功",amount);
    }
}
public class UnionPay implements Payment{
    @Override
    public PayState pay(String uid, Double amount) {
        System.out.println("欢迎使用银联卡支付");
        return new PayState(200,"支付成功",amount);
    }
}
public class WechatPay implements Payment {
    @Override
    public PayState pay(String uid, Double amount) {
        System.out.println("欢迎使用微信支付");
        return new PayState(200,"支付成功",amount);
    }
}


public class GetPayMent {
    public static Payment getPayMentByName(String className){
        StringBuffer sb = new StringBuffer();
        sb.append(PayMentEnum.PAYMENT_PATH.getValue());
        sb.append(className);
        Payment payment = null;
        try {
            Class<?> clazz = Class.forName(sb.toString());
            payment = (Payment) clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return payment;
    }
}


public enum PayMentEnum {
    ALI_PAY("AliPay"),
    JD_PAY("JDPay"),
    WECHAT_PAY("WechatPay"),
    UNION_PAY("UnionPay"),
    PAYMENT_PATH("com.zyz.spring5.codes.sjms.stategy.pay.payport.");
    private String value;
    PayMentEnum(String value){
        this.value = value;
    }
    public String getValue() {
        return value;
    }
}


/**
 * 支付完成之后的状态
 */
public class PayState {
    private Integer code;
    private Object data;
    private String msg;


    public PayState(Integer code, String msg,Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }
    @Override
    public String toString() {
        return "支付状态:[" + code +"],"+msg+",交易详情:"+data;
    }
}


public class Order {
    private String uid;
    private String orderId;
    private Double amount;


    public Order(String uid, String orderId, Double amount) {
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }


    //这个参数完全可以用Payment这个接口来代替
    //为什么?
    //完美的解决了switch的过程,不需要在代码逻辑中写switch了
    //更不需要写if-else
    public PayState pay(PayMentEnum payMentName){
        Payment payment = 
        GetPayMent.getPayMentByName(payMentName.getValue());
        return payment.pay(this.orderId,this.amount);
    }
}


public class PayStrategyTest{
    public static void main(String[] args) {
        //省略了把商品添加到购物车,再从购物车下单
        //直接从订单开始
        Order order = new Order("1","202004071437000000001",324.25);
        //开始支付,选择支付方式(多种支付方式选择)
        //每个渠道它的支付方式的具体算法是不一样的
        //不同渠道基本的算法是固定的,没办法修改
        //但是我们可以选择使用不同的支付渠道完成支付
        //上面这种情况就是典型的策略模式


        //这里传入的枚举值是在支付的时候才确定的
        //策略模式关注的是选择,中间具体怎么做用户不需要关注
        System.out.println(order.pay(PayMentEnum.WECHAT_PAY));
        System.out.println(order.pay(PayMentEnum.JD_PAY));
        System.out.println(order.pay(PayMentEnum.ALI_PAY));
        System.out.println(order.pay(PayMentEnum.UNION_PAY));

6、模板方法模式(Template Method) 

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义改算法的某些特定步骤。

Template Mthod模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。Spring中的JdbcTemplate,在用这个类时并不想去继承这个类,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽取出来作为一个参数传入JdbcTemplate的方法中。但是变换的恭喜是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里。然后我们在传入这个回调对象到JdbcTemplate,从而完成了调用。这就是Template Method不需要继承的另一种实现方式。

策略模式和模板模式对比:

策略模式:只有选择权(由用户自己选择已有的固定算法)

模板模式:侧重点不是选择,你没有选择,你必须这么做,但是可以参与某一个部分的自定义。

public class Member {
    private String userName;
    private String password;
    private String nickName;
    private Integer age;
    private String address;
    ...
}


public interface RowMapper<T> {
    public T mapRow(ResultSet rs,Integer rowNum) throws Exception;
}


public class JdbcTemplate {
    private DataSource dataSource;


    public JdbcTemplate(DataSource dataSource){
        this.dataSource = dataSource;
    }
    private Connection getConnection() throws Exception{
        return this.dataSource.getConnection();
    }


    private PreparedStatement createPreparedStatement(
    Connection conn,String sql) throws Exception {
        return conn.prepareStatement(sql);
    }


    private ResultSet executeQuery(PreparedStatement pstmt,
    Object[] values) throws Exception {
        for (int i=0; i<values.length;i++){
            pstmt.setObject(i,values[i]);
        }
        return pstmt.executeQuery();
    }


    private void closeStatement(Statement stmt) throws Exception {
        if(stmt != null){
            stmt.close();
        }
    }


    private void closeResultSet(ResultSet rs) throws Exception {
        if(rs != null){
            rs.close();
        }
    }


    private void closeConnection(Connection conn) throws Exception {
        //通常放到连接池中回收
    }


    private List<?> parseResultSet(ResultSet rs,RowMapper rowMapper)
     throws Exception {
        List<Object> result = new ArrayList<>();
        int rowNum = 1;
        while (rs.next()){
            result.add(rowMapper.mapRow(rs,rowNum++));
        }
        return result;
    }






    public List<?> executeQurey(String sql,
    RowMapper<?> rowMapper,Object[] values){
            
        List<?> result = null;
        try {
            //1、获取连接
            Connection conn = this.getConnection();
            //2、创建语句集
            PreparedStatement pstmt = 
            this.createPreparedStatement(conn,sql);
            //3、执行语句集,并获得结果集
            ResultSet rs = this.executeQuery(pstmt,values);
            //4、解析结果集
            result = this.parseResultSet(rs,rowMapper);
            //5、关系结果集
            this.closeResultSet(rs);
            //6、关系语句集
            this.closeStatement(pstmt);
            //7、关闭连接
            this.closeConnection(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }   
        return result;
    }
}
/**
 * Spring使用抽象类的原因是为了解耦
 */
public class MemberDao{


    //为什么不使用继承的方式,主要是为了解耦
    private JdbcTemplate jdbcTemplate = new JdbcTemplate(null);


    public List<?> query(String sql){
        return jdbcTemplate.executeQurey(sql, 
        new RowMapper<Member>() {
            @Override
            public Member mapRow(ResultSet rs, Integer rowNum) 
            throws Exception {
                Member member = new Member();
                member.setUserName(rs.getString("userName"));
                member.setPassword(rs.getString("password"));
                member.setAge(rs.getInt("age"));
                member.setAddress(rs.getString("address"));
                return member;
            }
        },null);
    }
}
public class MemberDaoTest {
    public static void main(String[] args) {
        MemberDao memberDao = new MemberDao();
        String sql = "select * from t_member";
        memberDao.query(

7、委派模式(Delegate)

应用场景:不属于 23 种设计模式之一,是面向对象设计模式中常用的一种模式。这种模式的原理为类 B 和类 A 是两个互相没有任何关系的类,B 具有和 A 一模一样的方法和属性;并且调用 B 中的方法,属性就是调用 A 中同名的方法和属性。B 好像就是一个受 A 授权委托的中介。第三方的代码不需要知道 A 的存在,也不需要和 A 发生直接的联系,通过 B 就可以直接使用 A 的功能,这样既能够使用到 A 的各种功能,又能够很好的将 A 保护起来了,一举两得。 

 

项目经理看上去时Boss和员工之间的中介;

委派模式相当于静态代理一种分常特殊的呃情况,整个过程全权代理。

项目经理:在老板眼里,他负责干活;实际上只负责类似调度的工作,分配任务;

重要特征:项目经理分配任务之前要做一个权衡(选择),类似于策略模式。

Spring中以Delegate结尾的类名、Dispatcher开头的是委派模式。

/**
 * 相当于项目经理的角色
 */
public class ServletDispatcher {
    private List<Handler> handlerMapping = new ArrayList<>();


    public ServletDispatcher(List<Handler> handlerMapping) {
        try {
            Class<?> memberAction = MemberAction.class;
            handlerMapping.add(new Handler()
                    .setController(memberAction.newInstance())
                    .setMethod(memberAction
                    .getMethod("getMemberById",
                    new Class[]{String.class}))
                    .setUrl("/web/getMemberById.json"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void doService(HttpServletRequest request, 
    HttpServletResponse response){
        doDispatch(request,response);
    }


    private void doDispatch(HttpServletRequest request,
     HttpServletResponse response){
        //1、获取用户请求的url
        //如果按照J2EE的标准,每个url都对应一个Servlet,url由浏览器输入;
        String uri = request.getRequestURI();
        
        //2、用户请求:Servlet拿到url之后,要做权衡(要做判断,要做选择)
        //根据用户请求的url,去找到这个url对应的某一个Java类的方法


        //3、通过拿到的url,去handlerMapping找对应方法(我们认为是策略常量)
        Handler handler = null;
        for (Handler h:handlerMapping){
            if(uri.equals(h.getUrl())){
                handler = h;
                break;
            }
        }


        //4、将具体的任务分发给Method(通过反射区调用其对应的方法)
        Object object = null;
        try {
            object = handler.getMethod().invoke(
             handler.getController(),
             request.getParameter("mid"));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


        //5、获取到Method执行的结果,通过Respose返回回去
        //response.getWriter()...
    }


    class Handler{
        private Object controller;
        private Method method;
        private String url;


        public Object getController() {
            return controller;
        }


        public Handler setController(Object controller) {
            this.controller = controller;
            return this;
        }


        public Method getMethod() {
            return method;
        }


        public Handler setMethod(Method method) {
            this.method = method;
            return this;
        }


        public String getUrl() {
            return url;
        }


        public Handler setUrl(String url) {
            this.url = url;
            return this;
        }
    }
}
public class MemberAction {


    public void getMemberById(String mid){


    }
}
public class OrderAction {


    public void getOrderById(){


    }
}
public class SystemAction {


    public void l

样例二:

public class Boss {
    public static void main(String[] args) {
        //客户请求、委派者、被委派者
        //委派者要持有被委派者的引用;
        //代理模式:注重的是过程
        //策略模式:注重的是可扩展(外部扩展)
        //委派模式:注重的是内部的灵活和复用
        //委派模式:就是静态代理和策略模式一种特殊的组合
        Leader leader = new Leader();
        leader.doing("加密");
        leader.doing("登录");
    }
}
public interface ITarget {
    public void doing(String command);
}
public class Leader implements ITarget{
    private Map<String,ITarget> targets = new HashMap<>();
    public Leader() {
        targets.put("加密",new TargetA());
        targets.put("登录",new TargetB());
    }


    //项目经理不干活
    public void doing(String command){
        targets.get(command).doing(command);
    }
}
public class TargetA implements ITarget{
    @Override
    public void doing(String command) {
        System.out.println("我是A,我是来做加密操作的");
    }
}
public class TargetB implements ITarget {
    @Override
    public void doing(String command) {
        System.out.println("我是B,我是来做登录操作的");
    }
}

8、适配器模式(Adapter)

Spring AOP 模块对 BeforeAdvice、AfterAdvice、ThrowsAdvice 三种通知类型的支持实际上是借助适配器模式来实现的,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,上述三种通知类型是 Spring AOP 模块定义的,它们是 AOP 联盟定义的 Advice 的子类型。

public class Member {
    private String username;
    private String password;
    private String mid;
    private String info;


    public Member(String username, String password) {
        this.username = username;
        this.password = password;
    }


    public String getUsername() {
        return username;
    }


    public void setUsername(String username) {
        this.username = username;
    }


    public String getPassword() {
        return password;
    }


    public void setPassword(String password) {
        this.password = password;
    }


    public String getMid() {
        return mid;
    }


    public void setMid(String mid) {
        this.mid = mid;
    }


    public String getInfo() {
        return info;
    }


    public void setInfo(String info) {
        this.info = info;
    }
}
public class ResultMsg {
    private String code;
    private String msg;
    private Object data;


    public ResultMsg(String code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }


    public String getCode() {
        return code;
    }


    public void setCode(String code) {
        this.code = code;
    }


    public String getMsg() {
        return msg;
    }


    public void setMsg(String msg) {
        this.msg = msg;
    }


    public Object getData() {
        return data;
    }


    public void setData(Object data) {
        this.data = data;
    }
}
public class SiginService {


    /**
     * 注册方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg regist(String username,String password){
        return new ResultMsg("200","注册成功",new Member(username,password));
    }




    /**
     * 登录方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg login(String username,String password){


        return null;
    }
}
public class SiginForThirdService extends SiginService{


    public ResultMsg loginByQQ(String openId){
        //1、因为openId是全局唯一的,可以作为用户名(加长)


        //2、密码默认为QQ_EMPTY


        //3、注册(在原有的系统里面创建一个用户)
        //ResultMsg msg = super.regist(openId,null);
        //4、调用原来的登录方法登录
        //msg = super.login(openId,null);
        return loginByRegist(openId,null);
    }


    public ResultMsg loginByWechat(String id){


        return null;
    }


    public ResultMsg loginByToken(String token){
        //通过Token拿到用户信息,然后重新登录一次
        return null;
    }


    public ResultMsg loginByPhoneNumber(String phoneNumber){


        return null;
    }


    public ResultMsg loginByRegist(String username,String password){
        super.regist(username,password);
        return super.login(username,password);
    }
}
public class SiginForThirdServiceTest {


    public static void main(String[] args) {
        SiginForThirdService service = new SiginForThirdService();
        //这里还可以添加一个策略模式
        //不改变原来的代码,也要兼容新的需求
        service.log

9、装饰者模式(Decorator)(包装器模式) 

应用场景:在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在 Spring 和 Hibernate 框架中总是配置一个数据源,因而 SessionFactory 的 DataSource 属性总是指向这个数据源并且恒定不变,所有 DAO 在使用 SessionFactory 的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的 DAO 在访 问 SessionFactory 的 时 候 都 不 得 不 在 多 个 数 据 源 中 不 断 切 换 , 问 题 就 出 现 了 : 如 何 让SessionFactory 在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在 Spring 的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢?

首先想到在 Spring 的 ApplicationContext 中配置所有的 DataSource。这些 DataSource 可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL 等,也可能是不同的数据源:比如 Apache 提 供 的 org.apache.commons.dbcp.BasicDataSource 、 Spring 提 供 的

org.springframework.jndi.JndiObjectFactoryBean 等。然后 SessionFactory 根据客户的每次请求,将 DataSource 属性设置成不同的数据源,以到达切换数据源的目的。

Spring 中用到的包装器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator。基本上都是动态地给一个对象添加一些额外的职责。

public class Member {
    private String username;
    private String password;
    private String mid;
    private String info;


    public Member(String username, String password) {
        this.username = username;
        this.password = password;
    }
    //....setter getter method
}
public class ResultMsg {
    private String code;
    private String msg;
    private Object data;


    public ResultMsg(String code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    //....setter getter method
}
public interface ISigninService {
    /**
     * 注册方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg regist(String username, String password);


    /**
     * 登录方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg login(String username,String password);
}
public class SigninService implements ISigninService{


    @Override
    public ResultMsg regist(String username, String password) {
        Member member = new Member(username,password);
        return new ResultMsg("200","注册成功",member);
    }


    @Override
    public ResultMsg login(String username, String password) {
        return null;
    }
}
//扩展升级 
public interface ISigninByThirdService extends ISigninService {


    public ResultMsg loginByQQ(String openId);


    public ResultMsg loginByRegist(String username,String password);
}


public class SigninByThirdService implements ISigninByThirdService{


    private ISigninService service;


    public SigninByThirdService(ISigninService service) {
        this.service = service;
    }


    @Override
    public ResultMsg regist(String username, String password) {
        return service.regist(username,password);
    }


    @Override
    public ResultMsg login(String username, String password) {
        return service.login(username,password);
    }


    @Override
    public ResultMsg loginByQQ(String openId){
        //1、因为openId是全局唯一的,可以作为用户名(加长)


        //2、密码默认为QQ_EMPTY


        //3、注册(在原有的系统里面创建一个用户)
        //ResultMsg msg = super.regist(openId,null);
        //4、调用原来的登录方法登录
        //msg = super.login(openId,null);
        return loginByRegist(openId,null);
    }


    @Override
    public ResultMsg loginByRegist(String username,String password){
        this.regist(username,password);
        return this.login(username,password);
    }
}
public class SigninTest {
    public static void main(String[] args) {
        //原来的功能依旧对外开放,依旧保留
        //新的功能同样的也可以使用
        ISigninByThirdService service = new SigninByThirdService(new SigninService());
        service.loginByQQ("14250");
        /**
         * Decorator  Wrapper这种就是装饰模式
         * 装饰模式和适配器模式的比较
         * ===========================================================
         * 装饰模式                             适配器模式
         * -----------------------------+-----------------------------
         * 是一种非常特殊的适配器模式              可以不保留层级关系
         * -----------------------------+-----------------------------
         * 装饰者和被装饰者都要实现同一            适配者和被适配者没有必然的 
         * 个接口,主要目的是为了扩展,            层级联系,通常采用代理或者
         * 依旧保留OOP关系                      继承形式进行包装
         * -----------------------------+-----------------------------
         * 满足 is - a 的关系                   满足 has - a 的关系
         * -----------------------------+-----------------------------
         * 注重的四覆盖、扩展                    注重兼容、转换
         * ===========================================================
    

10、观察者模式(Observer)

应用场景:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

Spring 中 Observer 模式常用的地方是 Listener 的实现。如 ApplicationListener。

结合动态代理去做,就可以明白之间的解耦关系。

/**
 * 事件(事件信息的封装)
 */
public class Event {
    //事件源
    private Object source;
    //通知目标
    private Object target;
    //回调
    private Method callback;
    //触发
    private String trigger;


    private Long time;


    public Event(Object target, Method callback) {
        this.target = target;
        this.callback = callback;
    }
    //....setter getter method
}
/**
 * 事件的注册和监听
 */
public class EventLisenter {
    //Map相当于注册器
    protected Map<Enum,Event> events = new HashMap<>();
    
    public void addLisenter(Enum eventType, Object target, Method callback){
        //注册事件
        //用反射调用这个方法
        events.put(eventType,new Event(target,callback));
    }


    private void trigger(Event e){
        try {
            e.setSource(this);
            e.setTime(System.currentTimeMillis());
            e.getCallback().invoke(e.getTarget(),e);
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        }
    }


    protected void trigger(Enum call){
        if(!this.events.containsKey(call)){return;}
        trigger(this.events.get(call).setTrigger(call.toString()));
    }
}
/**
 * 被观察者
 */
public class Mouse extends EventLisenter {
    public void click(){
        System.out.println("鼠标单击");
        this.trigger(MouseEventType.ON_CLICK);
    }


    public void doubleClick(){
        System.out.println("鼠标双击");
        this.trigger(MouseEventType.ON_DOUBLE_CLICK);
    }


    public void up(){
        System.out.println("鼠标弹起");
        this.trigger(MouseEventType.ON_UP);
    }


    public void down(){
        System.out.println("鼠标按下");
        this.trigger(MouseEventType.ON_DOWN);
    }


    public void wheel(){
        System.out.println("鼠标滚动");
        this.trigger(MouseEventType.ON_WHEEL);
    }


    public void move(){
        System.out.println("鼠标移动");
        this.trigger(MouseEventType.ON_MOVE);
    }


    public void over(){
        System.out.println("鼠标悬停");
        this.trigger(MouseEventType.ON_OVER);
    }
}
public enum MouseEventType {
    ON_CLICK,
    ON_DOUBLE_CLICK,
    ON_UP,
    ON_DOWN,
    ON_WHEEL,
    ON_MOVE,
    ON_OVER;
}


/**
 * 观察者
 * 回调响应的逻辑,由自己实现
 */
public class MouseEventCallback {
    public void onClick(Event event){
        System.out.println("========触发鼠标单击事件========");
        System.out.println(event);
    }


    public void onDoubleClick(Event event){
        System.out.println("========触发鼠标双击事件========");
        System.out.println(event);
    }


    public void onUp(Event event){
        System.out.println("========触发鼠标弹起事件========");
        System.out.println(event);
    }


    public void onDown(Event event){
        System.out.println("========触发鼠标按下事件========");
        System.out.println(event);
    }


    public void onWheel(Event event){
        System.out.println("========触发鼠标滚动事件========");
        System.out.println(event);
    }


    public void onMove(Event event){
        System.out.println("========触发鼠标移动事件========");
        System.out.println(event);
    }


    public void onOver(Event event){
        System.out.println("========触发鼠标悬停事件========");
        System.out.println(event);
    }
}
/**
 * 观察者和被观察者之间,本质上是没有必然联系
 * 只要当我们去注册事件的时候才产生联系
 * 观察者模式最重要的一点是用来进行解耦
 */
public class MouseTest {
    public static void main(String[] args) {
        try {
            //回调函数
            MouseEventCallback callback = new MouseEventCallback();
            Method onClick = MouseEventCallback.class
                             .getMethod("onClick", Event.class);


            //人为的调用鼠标的单击事件
            Mouse mouse = new Mouse();
            mouse.addLisenter(MouseEventType.ON_CLICK,
                                             callback,
                                             onClick);
            mouse.click();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();

各种设计模式的使用场景

各种设计模式之间的关联:

创建型:

工厂模式:原始社会--农工社会--小作坊--工厂--流水线生产

Spring: BeanFactory --- FactoryBean

 

单例模式:在整个系统运行阶段,为了提高资源重复利用,通过技术手段保证在整个系统运行阶段,只有一个实例。

场景:配置文件、监控程序、IOC容器、日历

实现手段:懒汉式、饿汉式、注册登记式、反序列化处理

Spring中最常用的:注册登记式,效率性能最高的:内部类

 

原型模式:复制

Spring中的对象原型,主要是为了配置信息能够被重复使用,而且互不干扰

<bean scope="prototype">

<list></list>

</bean>

技术手段:实现cloneable重写clone方法,字节码操作来实现

Spring中常用的是:通过反射机制来实现。

 

代理模式:

应用场景:中介、黄牛、经纪人

代码场景:字节码增强 、动态实现非侵入式编程

完成一件事情:代理只参与某一部分的功能

技术手段:JDK Proxy, Cglib、AspectJ、asm

 

策略模式:将一些固定的算法同意起来

应用场景:旅游路线的选择、出行方式的选择、饮料制作原料的选择

代码场景:支付方式的选择

特点:巧妙的避免if...else...或者switch语句

 

模板方法模式:流程固定,某一个环节有差异

应用场景:jdbcTemplate、工作流

代码场景:模拟Spring jdbcTemplate的简单实现

Spring-orm,单表操作不需要写一句SQL

 

委派模式:代理模式的一种特殊情况,全权代理

应用场景:项目经理,Dispatcher

代码场景:Spring中的ServletDispatcher、Delegate命名结尾的都是委派模式

 

适配器模式:兼容、转换

应用场景:编码和解码

代码场景:登录,为了兼容旧系统的登录功能,在老系统基础上进行兼容编程

Spring中Adapter结尾的

 

装饰器模式:委派 + 适配器,注重的扩展、覆盖, is - a(同源同宗)

应用场景:IO流、数据源

代码场景:使用代码改造旧系统

在Spring中只要是以Derocator结尾的、Wrapper结尾的都是

 

观察者模式:针对于目标对象的一举一动,要得到一个反馈

应用场景:事件监听、日志监听、短信通知

代码场景:Listener、Monitor、Observer

Spring中通常Listener都是观察者模式,通常和动态代理一起来使用。

 

 

AOP:面向切面编程,动态代理

动态代理只是AOP的之中技术实现手段,AOP是一种编程思想;

Aspect 切面 :由几个本来不相干的东西,组到一块形成已给的新的产品;

专人干专事,达到解耦的目的

 

OOP:(封装、继承、多态)用代码去描述这个世界,

面向对象主要关注的是事物之间的联系。

 

BOP:Bean与Bean之间的关系,不希望每次认为的重复管理,由程序来实现自动管理

Spring开始就是从Bean的管理开始的;

 

IOC:控制反转,创建对象的控制权反转 (new的权力),所有的Bean都有Spring来new,所以才叫做控制反转。new 出来的对象需要统一管理,所以才有了IOC容器(Map)

 

DI技术:解决对象动态赋值的问题,动态调用getter和setter(采用反射)

Spring的加载步骤:定位、载入、注册、再确认要不要初始化Spring

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值