抽丝剥茧设计模式

Singleton 单例

饿汉式

最简单的方式

/**
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全
 * 简单实用,推荐使用!
 * 唯一缺点:不管用到与否,类装载时就完成实例化
 * Class.forName("")
 * (话说你不用的,你装载它干啥)
 */
public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();
    private Mgr01() {};
    public static Mgr01 getInstance() {
        return INSTANCE;
    }
    public void m() {
        System.out.println("m");
    }
    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);//true
    }
}
双重检测锁
/**
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Mgr06 {
    private static volatile Mgr06 INSTANCE; //JIT
    private Mgr06() {}
    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }
    
    public void m() {
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}
静态内部类
/**
 * 静态内部类方式
 * JVM保证单例
 * 加载外部类时不会加载内部类,这样可以实现懒加载
 */
public class Mgr07 {
    private Mgr07() {}
    private static class Mgr07Holder {
        private final static Mgr07 INSTANCE = new Mgr07();
    }
    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }
    public void m() {
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }
}
枚举方式
/**
 * 不仅可以解决线程同步,还可以防止反序列化。
 */
public enum Mgr08 {
    INSTANCE;
    public void m() {}
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }
}

Strategy 策略

在这里插入图片描述

Comparator接口

比较猫的属性排序

public class CatHeightComparator implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        if(o1.height > o2.height) return -1;
        else if (o1.height < o2.height) return 1;
        else return 0;
    }
}
Comparable接口
public class Apple implements Compareable{
	priavte double redius;//半径值
	public int compateTo(Object obj){
		if(obj!=null && obj instanceof Apple){
			Apple app = (Apple)obj;
			if(this.redius> app.redius){
				return 1;
			}else if(this.redius == app.redius){
				return 0;
			}
		}
		return -1;
	}
}

冒泡排序

public class BubbleSort{
	public void Sort(Comparable[] arr){
		for(int i = 1;i<arr.length;i++){
			for(int j = i; j<arr.length-i;j++){
				if(arr[j].comparableTo(arr[j+1]) > 0){
					Comparable tmp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = tmp;
				}
			}
		}
	}
}

Comparable:强行对实现它的每个类的对象进行整体排序。
这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。
只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。
实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator强行对某个对象进行整体排序。
可以将Comparator 传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。
还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序

工厂 Factory

  • 任何可以产生对象的方法或类,都可以称之为工厂

  • 单例也是一种工厂

  • 为什么有了new之后,还要有工厂?

    • 灵活控制生产过程
    • 权限、修饰、日志…
FactoryMethod工厂方法
public interface Moveable {
    void go();
}

public class Car implements  Moveable {
    public void go() {
        System.out.println("Car go wuwuwuwuw....");
    }
}

public class Plane implements Moveable {
    public void go() {
        System.out.println("plane flying shushua....");
    }
}


public class CarFactory {
    public Moveable create() {
        System.out.println("a car created!");
        return new Car();
    }
}

public class Main {
    public static void main(String[] args) {
        Moveable m = new CarFactory().create();
        m.go();
    }
}

AbstractFactory 抽象工厂

食物类

public abstract class Food {
    abstract void printName();
}

public class Bread extends Food{
    public void printName() {
        System.out.println("wdm");
    }
}
public class MushRoom extends Food{
    public void printName() {
        System.out.println("dmg");
    }
}

武器

public abstract class  Weapon {
    abstract void shoot();
}

public class AK47 extends  Weapon{
    public void shoot() {
        System.out.println("tututututu....");
    }
}

public class MagicStick extends  Weapon{
    public void shoot() {
        System.out.println("diandian....");
    }
}

交通工具

public abstract class Vehicle { 
     void go();
}

public class Broom extends Vehicle{
    public void go() {
        System.out.println("Car go wuwuwuwuw....");
    }
}

public class Car extends Vehicle{
    public void go() {
        System.out.println("Car go wuwuwuwuw....");
    }
}

抽象工厂
简化产品的设计,方便产品扩展
在这里插入图片描述

public abstract class AbastractFactory {
    abstract Food createFood();
    abstract Vehicle createVehicle();
    abstract Weapon createWeapon();
}

//产品1
public class MagicFactory extends AbastractFactory {
    @Override
    public Food createFood() {
        return new MushRoom();
    }

    @Override
    public Vehicle createVehicle() {
        return new Broom();
    }

    @Override
    public Weapon createWeapon() {
        return new MagicStick();
    }
}

public class ModernFactory extends AbastractFactory {
    @Override
    public Food createFood() {
        return new Bread();
    }

    @Override
    public Vehicle createVehicle() {
        return new Car();
    }

    @Override
    public Weapon createWeapon() {
        return new AK47();
    }
}
public class Main {
    public static void main(String[] args) {
        AbastractFactory f = new ModernFactory();
        Vehicle c = f.createVehicle();
        c.go();
        Weapon w = f.createWeapon();
        w.shoot();
        Food b = f.createFood();
        b.printName();
    }
}

Decorator装饰器

buffer缓冲流就是装饰者模式最好的体现
在这里插入图片描述

/**
 * 抽象构建类
 * @author yuyang
 **/
public abstract class Component {
    //抽象方法
    public abstract void operation();
}

/**
 * 具体构建类(被装饰类)
 * @author yuyang
 **/
public class ConcreteComponent extends Component{
    public void operation() {
        //基础功能的实现(一些复杂功能通过装饰类进行扩展)
        System.out.println("被装饰的原有方法");
    }
}

/**
 * 抽象装饰类-装饰者模式的核心
 * @author yuyang
 **/
public class Decorator extends Component{

    //维持一个对抽象构件对象的引用
    private Component component;

    //通过构造注入一个抽象构件类型的对象
    public Decorator(Component component) {
        this.component = component;
    }

    public void operation() {
        //调用原有的业务方法,并没有真正的进行装饰,而是提供了一个统一的接口,将装饰的过程交给子类完成
        component.operation();
    }
}

/**
 * 具体装饰类(具体实现子类)
 * @author spikeCong
 * @date 2022/9/27
 **/
public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation(); //调用原有的业务方法
        add(); //调用新增的方法
    }

    //新增业务方法
    public void add(){
        System.out.println("新增的增强方法");
        //......
    }
}

/**
 * 启动类
 * @author yuyang
 */
public class Main {
    public static void main(String[] args) {
        Component component=new ConcreteComponent();
        Decorator decorator = new ConcreteDecorator(component);
        
        decorator.operation();
        //被装饰的原有方法
		//新增的增强方法
    }
}
应用实例

以一个文件读写器程序为例
在这里插入图片描述

  • DataLoader
    • 抽象的文件读取接口DataLoader
  • BaseFileDataLoader
    • 具体组件BaseFileDataLoader,重写组件 DataLoader 的读写方法
  • DataLoaderDecorator
    • 装饰器DataLoaderDecorator,这里要包含一个引用 DataLoader 的对象实例 wrapper,同样是重写 DataLoader 方法,不过这里使用 wrapper 来读写,并不进行扩展
  • EncryptionDataDecorator
    • 读写时有加解密功能的具体装饰器EncryptionDataDecorator,它继承了装饰器 DataLoaderDecorator 重写读写方法

导入IO工具类

    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

1 ) DataLoader

/**
 * 抽象的文件读取接口DataLoader
 * @author yuyang
 **/
public interface DataLoader {

    String read();

    void write(String data);
}

2 ) BaseFileDataLoader

/**
 * 具体组件,重写读写方法
 * @author yuyang 
 **/
public class BaseFileDataLoader implements DataLoader {

    private String filePath;

    public BaseFileDataLoader(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public String read() {
        try {
            String result = FileUtils.readFileToString(new File(filePath), "utf-8");
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void write(String data) {
        try{
            FileUtils.writeStringToFile(new File(filePath), data, "utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3 ) DataLoaderDecorator

/**
 * 装抽象饰者类
 * @author yuyang
 **/
public class DataLoaderDecorator implements DataLoader {

    private DataLoader wrapper;

    public DataLoaderDecorator(DataLoader wrapper) {
        this.wrapper = wrapper;
    }

    @Override
    public String read() {
        return wrapper.read();
    }

    @Override
    public void write(String data) {
        wrapper.write(data);
    }
}

4 ) EncryptionDataDecorator

/**
 * 具体装饰者-对文件内容进行加密和解密
 * @author yuyang 
 **/
public class EncryptionDataDecorator extends DataLoaderDecorator {

    public EncryptionDataDecorator(DataLoader wrapper) {
        super(wrapper);
    }

    @Override
    public String read() {
        return decode(super.read());
    }

    @Override
    public void write(String data) {
        super.write(encode(data));
    }

    //加密操作
    private String encode(String data) {
        try {
             Base64.Encoder encoder = Base64.getEncoder();
             byte[] bytes = data.getBytes("UTF-8");
             String result = encoder.encodeToString(bytes);

             return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //解密
    private String decode(String data) {
        try {
            Base64.Decoder decoder = Base64.getDecoder();
            String result = new String(decoder.decode(data), "UTF-8");
            return result;

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

5 ) 测试

public class TestDecorator {
    public static void main(String[] args) {
        String info = "name:tom,age:15";

        DataLoader loader = new BaseFileDataLoader("demo.txt");
        loader.write(info);
        String read = loader.read();
        System.out.println(read);

		//类似于buffer流  更快的方式  
        DataLoaderDecorator decorator = new EncryptionDataDecorator(new BaseFileDataLoader("demo.txt"));
        decorator.write(info);
        String data = decorator.read();
        System.out.println(data);


    }
}

装饰器模式的优点:

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为.
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合可以创造出很多不同行为的组合,得到更加强大的对象.
  4. 具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,原有类库代码无序改变,符合开闭原则.

装饰器模式的缺点:

  1. 在使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值不同,大量的小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能.
  2. 装饰器模式提供了一种比继承更加灵活、机动的解决方案,但同时也意味着比继承更加易于出错,排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,较为烦琐.

装饰器模式的适用场景

  1. 快速动态扩展和撤销一个类的功能场景。 比如,有的场景下对 API 接口的安全性要求较高,那么就可以使用装饰模式对传输的字符串数据进行压缩或加密。如果安全性要求不高,则可以不使用。

  2. 不支持继承扩展类的场景。 比如,使用 final 关键字的类,或者系统中存在大量通过继承产生的子类。

ChainOfResponsibility责任链

难度仅次于proxy代理
责任链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止.

在责任链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请 求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再 传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职 责,所以叫作责任链模式。
在这里插入图片描述
使用场景:
• 在论坛中发表文章
• 后台要经过信息处理才可以发表或者进入数据库

• 最直观的:定义MsgHadler直接处理
• 想扩展性好一些:定义不同的Filter
• 继续:将Filter们构成链条
• 小技巧:链式编程
• FilterChain其实自己也是一个Filter
• 由FilterChain中的某一个Filter决定链条是否继续

在这里插入图片描述
消息过滤

类似于二叉树遍历

public class Msg {
    String name;
    String msg;

    public String getMsg() {
        return msg;
    }

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

    @Override
    public String toString() {
        return "Msg{" +
                "msg='" + msg + '\'' +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("大家好:),<script>,欢迎访问 mashibing.com ,大家都是996 ");

        FilterChain fc = new FilterChain();
        fc.add(new HTMLFilter())
                .add(new SensitiveFilter());

        FilterChain fc2 = new FilterChain();
        fc2.add(new FaceFilter())
                .add(new URLFilter());

        fc.add(fc2);

        fc.doFilter(msg);
        System.out.println(msg);

    }
}

interface Filter {
    boolean doFilter(Msg m);
}

class HTMLFilter implements Filter {
    @Override
    public boolean doFilter(Msg m) {
        String r = m.getMsg();
        r = r.replace('<', '[');
        r = r.replace('>', ']');
        m.setMsg(r);
        return true;
    }
}

class SensitiveFilter implements Filter {
    @Override
    public boolean doFilter(Msg m) {
        String r = m.getMsg();
        r = r.replaceAll("996", "955");
        m.setMsg(r);
        return false;
    }
}

class FaceFilter implements Filter {
    @Override
    public boolean doFilter(Msg m) {
        String r = m.getMsg();
        r = r.replace(":)", "^V^");
        m.setMsg(r);
        return true;
    }
}

class URLFilter implements Filter {
    @Override
    public boolean doFilter(Msg m) {
        String r = m.getMsg();
        r = r.replace("mashibing.com", "http://www.mashibing.com");
        m.setMsg(r);
        return true;
    }
}

class FilterChain implements Filter {
    private List<Filter> filters = new ArrayList<>();

    public FilterChain add(Filter f) {
        filters.add(f);
        return this;
    }

    public boolean doFilter(Msg m) {
        for(Filter f : filters) {
            if(!f.doFilter(m)) return false;
        }
        return true;
    }
}

结构
在这里插入图片描述
主要包含以下角色:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(链上的每个处理者都有一个成员变量来保存对于下一处理者的引用,比如上图中的successor) 。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

每一个具体的处理类都会保存在它之后的下一个处理类。当处理完成后,就会调用设置好的下一个处理类,直到最后一个处理类不再设置下一个处理类,这时处理链条全部完成。

public class RequestData {
    private String data;

    public RequestData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}


/**
 * 抽象处理者类
 * @author spikeCong
 * @date 2022/10/14
 **/
public abstract class Handler {

    protected Handler successor = null;

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

    public abstract void handle(RequestData requestData);
}

public class HandlerA extends Handler {

    @Override
    public void handle(RequestData requestData) {
        System.out.println("HandlerA 执行代码逻辑! 处理: " + requestData.getData());

        requestData.setData(requestData.getData().replace("A",""));

        if(successor != null){
            successor.handle(requestData);
        }else{
            System.out.println("执行中止!");
        }
    }
}

public class HandlerB extends Handler {

    @Override
    public void handle(RequestData requestData) {
        System.out.println("HandlerB 执行代码逻辑! 处理: " + requestData.getData());

        requestData.setData(requestData.getData().replace("B",""));

        if(successor != null){
            successor.handle(requestData);
        }else{
            System.out.println("执行中止!");
        }
    }
}

public class HandlerC extends Handler {

    @Override
    public void handle(RequestData requestData) {
        System.out.println("HandlerC 执行代码逻辑! 处理: " + requestData.getData());

        requestData.setData(requestData.getData());

        if(successor != null){
            successor.handle(requestData);
        }else{
            System.out.println("执行中止!");
        }
    }
}

public class Client {

    public static void main(String[] args) {
        Handler h1 = new HandlerA();
        Handler h2 = new HandlerB();
        Handler h3 = new HandlerC();
        h1.setSuccessor(h2);
        h2.setSuccessor(h3);
        RequestData requestData = new RequestData("请求数据ABCDE");
        h1.handle(requestData);
    }

}

应用实例

接下来我们模拟有一个双11期间,业务系统审批的流程,临近双十一公司会有陆续有一些新的需求上线,为了保证线上系统的稳定,我们对上线的审批流畅做了严格的控制.审批的过程会有不同级别的负责人加入进行审批(平常系统上线只需三级负责人审批即可,双十一前后需要二级或一级审核人参与审批),接下来我们就使用职责链模式来设计一下此功能.

在这里插入图片描述

/**
 * 抽象审核链类
 * @author yuyang
 **/
public abstract class AuthLink {

    protected Logger logger = (Logger) LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat sdf =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    protected String levelUserId; //审核人id

    protected String levelUserName; //审核人姓名

    protected AuthLink next;      //表示持有下一个处理对象的引用

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    //获取下一个处理器
    public AuthLink getNext() {
        return next;
    }

    //向责任链中添加处理器
    public AuthLink appendNext(AuthLink next){
        this.next = next;
        return this;
    }

    //抽象审核方法
    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}


/**
 * 模拟审核服务
 * @author yuyang
 **/
public class AuthService {

    //审核信息容器 key:审批人Id+审批单Id , value:审批时间
    private static Map<String, Date> authMap = new HashMap<>();


    /**
     * 审核方法
     * @param uId     审核人id
     * @param orderId  审核单id
     */
    public static void auth(String uId , String orderId){
        System.out.println("进入审批流程,审批人ID: " + uId);
        authMap.put(uId.concat(orderId),new Date());
    }

    /**
     * 查询审核结果
     * @param uId
     * @param orderId
     * @return: java.util.Date
     */
    public static Date queryAuthInfo(String uId , String orderId){
        return authMap.get(uId.concat(orderId));
    }
}

审批人

/**
 * @author yuyang
 **/
public class Level1AuthLink extends AuthLink {

    private Date beginDate = sdf.parse("2022-11-11 00:00:00");
    private Date endDate = sdf.parse("2022-11-31 00:00:00");

    public Level1AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {

        Date date = AuthService.queryAuthInfo(levelUserId, orderId);

        if(null == date){
            return new AuthInfo("0001","单号: "+
                    orderId," 状态: 待一级审核人审批", levelUserName);
        }

        AuthLink next = super.getNext();

        if(next == null){
            return new AuthInfo("0001","单号: "+
                    orderId," 状态: 一级审批完成", "审批人: " + levelUserName);
        }

        if(authDate.before(beginDate) || authDate.after(endDate)){
            return new AuthInfo("0001","单号: "+
                    orderId," 状态: 一级审批完成", "审批人: " + levelUserName);
        }

        return next.doAuth(uId,orderId,authDate);
    }
}

/**
 * @author yuyang
 **/
public class Level2AuthLink extends AuthLink {

    private Date beginDate = sdf.parse("2022-11-11 00:00:00");
    private Date endDate = sdf.parse("2022-11-31 00:00:00");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {

        Date date = AuthService.queryAuthInfo(levelUserId, orderId);

        if(null == date){
            return new AuthInfo("0001","单号: "+
                    orderId," 状态: 待二级审核人审批", levelUserName);
        }

        AuthLink next = super.getNext();

        if(next == null){
            return new AuthInfo("0000","单号: "+
                    orderId," 状态: 二级审批完成", "审批人: " + levelUserName);
        }

        if(authDate.before(beginDate) || authDate.after(endDate)){
            return new AuthInfo("0000","单号: "+
                    orderId," 状态: 二级审批完成", "审批人: " + levelUserName);
        }

        return next.doAuth(uId,orderId,authDate);
    }
}

/**
 * @author yuyang
 **/
public class Level3AuthLink extends AuthLink {

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {

        Date date = AuthService.queryAuthInfo(levelUserId, orderId);

        if(null == date){
            return new AuthInfo("0001","单号: "+
                    orderId," 状态: 待三级审核人审批", levelUserName);
        }

        AuthLink next = super.getNext();

        if(next == null){
            return new AuthInfo("0000","单号: "+
                    orderId," 状态: 三级审批完成", "审批人: " + levelUserName);
        }

        return next.doAuth(uId,orderId,authDate);
    }
}

测试类

/**
 * @author yuyang
 **/
public class Client {

    private Logger logger = LoggerFactory.getLogger(Client.class);

    @Test
    public void test_Auth() throws ParseException {

        //定义责任链
        AuthLink authLink = new Level3AuthLink("1000013", "李工")
                .appendNext(new Level2AuthLink("1000012", "王经理")
                        .appendNext(new Level1AuthLink("1000011", "罗总")));

        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date currentDate = f.parse("2022-11-18 23:49:46");

        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));

//        // 模拟三级负责人审批
        AuthService.auth("1000013", "1000998004813441");
        logger.info("测试结果:{}", "模拟三级负责人审批,王工");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));

//        // 模拟二级负责人审批
        AuthService.auth("1000012", "1000998004813441");
        logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));

//        // 模拟一级负责人审批
        AuthService.auth("1000011", "1000998004813441");
        logger.info("测试结果:{}", "模拟一级负责人审批,段总");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));

    }
}

Observer观察者

观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应.

UML图
在这里插入图片描述
Source 事件发布者
ObserveListener 事件监听者
Event 事件

整体类似于消息队列

Spring中的观察者模式

Spring 基于观察者模式,实现了自身的事件机制也就是事件驱动模型,事件驱动模型通常也被理解成观察者或者发布/订阅模型。

spring事件模型提供如下几个角色

  • ApplicationEvent事件
  • ApplicationListener 事件监听
  • ApplicationEventPublisher事件源
  • ApplicationEventMulticaster 事件管理

在这里插入图片描述

实例

事件

abstract class Event<T> {
    abstract T getSource();
}

public class wakeUpEvent extends Event<Child>{
    long timestamp;
    String loc;
    Child source;

    public wakeUpEvent(long timestamp, String loc, Child source) {
        this.timestamp = timestamp;
        this.loc = loc;
        this.source = source;
    }

    @Override
    Child getSource() {
        return source;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    public void setSource(Child source) {
        this.source = source;
    }
}

发布者

public class Child {
    private boolean cry = false;
    private List<Observer> observers = new ArrayList<>();

    {
        observers.add(new Dad());
        observers.add(new Mum());
        observers.add(new Dog());
        observers.add((e)->{
            System.out.println("ppp");
        });
        //hook callback function  钩子函数
    }

    public boolean isCry() {
        return cry;
    }

    public void wakeUp() {
        cry = true;
        wakeUpEvent event = new wakeUpEvent(System.currentTimeMillis(), "bed", this);
        for(Observer o : observers) {
            o.actionOnWakeUp(event);
        }
    }
}

消费者

public interface Observer {
    void actionOnWakeUp(wakeUpEvent event);
}

class Dad implements Observer {
    public void feed() {
        System.out.println("dad feeding...");
    }

    @Override
    public void actionOnWakeUp(wakeUpEvent event) {
        feed();
    }
}

class Mum implements Observer {
    public void hug() {
        System.out.println("mum hugging...");
    }

    @Override
    public void actionOnWakeUp(wakeUpEvent event) {
        hug();
    }
}

class Dog implements Observer {
    public void wang() {
        System.out.println("dog wang...");
    }

    @Override
    public void actionOnWakeUp(wakeUpEvent event) {
        wang();
    }
}

Composite组合模式

将对象组合成树形结构以表示整个部分的层次结构.组合模式可以让用户统一对待单个对象和对象的组合.

组合模式其实就是将一组对象(文件夹和文件)组织成树形结构,以表示一种’部分-整体’ 的层次结构,(目录与子目录的嵌套结构). 组合模式让客户端可以统一单个对象(文件)和组合对象(文件夹)的处理逻辑(递归遍历).

主要用来遍历树状结构
在这里插入图片描述

abstract class Node {
    abstract public void p();
}

class LeafNode extends Node {
    String content;
    public LeafNode(String content) {this.content = content;}

    @Override
    public void p() {
        System.out.println(content);
    }
}

class BranchNode extends Node {
    List<Node> nodes = new ArrayList<>();

    String name;
    public BranchNode(String name) {this.name = name;}

    @Override
    public void p() {
        System.out.println(name);
    }

    public void add(Node n) {
        nodes.add(n);
    }
}


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

        BranchNode root = new BranchNode("root");
        BranchNode chapter1 = new BranchNode("chapter1");
        BranchNode chapter2 = new BranchNode("chapter2");
        Node r1 = new LeafNode("r1");
        Node c11 = new LeafNode("c11");
        Node c12 = new LeafNode("c12");
        BranchNode b21 = new BranchNode("section21");
        Node c211 = new LeafNode("c211");
        Node c212 = new LeafNode("c212");

        root.add(chapter1);
        root.add(chapter2);
        root.add(r1);
        chapter1.add(c11);
        chapter1.add(c12);
        chapter2.add(b21);
        b21.add(c211);
        b21.add(c212);

        tree(root, 0);

    }

    static void tree(Node b, int depth) {
        for(int i=0; i<depth; i++) System.out.print("--");
        b.p();

        if(b instanceof BranchNode) {
            for (Node n : ((BranchNode)b).nodes) {
                tree(n, depth + 1);
            }
        }
    }

}

Flyweight享元

重复利用对象
最典型的例子就是StringTable
在这里插入图片描述

class Apple{
    public UUID id = UUID.randomUUID();
    boolean living = true;

    @Override
    public String toString() {
        return "Bullet{" +
                "id=" + id +
                '}';
    }
}

public class ApplePool {
    List<Apple> apples = new ArrayList<>();
    {
        for(int i=0; i<5; i++) apples.add(new Apple());
    }

    public Apple getApple() {
        for(int i=0; i<apples.size(); i++) {
            Apple a = apples.get(i);
            if(a.living) return a;
        }

        return new Apple();
    }

    public static void main(String[] args) {
        ApplePool ap = new ApplePool();

        for(int i=0; i<10; i++) {
            Apple a = ap.getApple();
            System.out.println(a);//都是同一个地址
        }
    }

}
public class TestString {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = new String("abc");

        System.out.println(s1 == s2); //true
        System.out.println(s1 == s3); //false
        System.out.println(s3 == s4);
        System.out.println(s3.intern() == s1);
        System.out.println(s3.intern() == s4.intern());
    }

Proxy 代理

静态代理

在这里插入图片描述

/**
 * 问题:我想记录坦克的移动时间
 * 最简单的办法:修改代码,记录时间
 * 问题2:如果无法改变方法源码呢?
 * 用继承?
 * v05:使用代理
 */
public class Tank implements Movable {

    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new TankTimeProxy(new Tank()).move();
    }
}

class TankTimeProxy implements Movable {

    Tank tank;

    public TankTimeProxy(Tank tank) {
        this.tank = tank;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        tank.move();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

interface Movable {
    void move();
}

动态代理

底层核心都是基于ASM实现的,根据ASM修改字节码

  • jdk代理
    核心是InvocationHandler 接口

在这里插入图片描述

/*
 *  横切代码与业务逻辑代码分离 AOP
 */
public class Tank implements Movable {

    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Tank tank = new Tank();

        Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
                new Class[]{Movable.class}, //tank.class.getInterfaces()
                new TimeProxy(tank)
        );

        m.move();

    }
}

class TimeProxy implements InvocationHandler {
    Movable m;

    public TimeProxy(Movable m) {
        this.m = m;
    }

    public void before() {
        System.out.println("method start..");
    }

    public void after() {
        System.out.println("method stop..");
    }

	//核心
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object o = method.invoke(m, args);
        after();
        return o;
    }

}

interface Movable {
    void move();
}
  • cglib代理
    核心是MethodInterceptor 接口
/**
 * CGLIB实现动态代理不需要接口
 */
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Tank.class);
        enhancer.setCallback(new TimeMethodInterceptor());
        Tank tank = (Tank)enhancer.create();
        tank.move();
    }
}

class TimeMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println(o.getClass().getSuperclass().getName());
        System.out.println("before");
        Object result = null;
        result = methodProxy.invokeSuper(o, objects);
        System.out.println("after");
        return result;
    }
}

class Tank {
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

AOP

@Aspect
public class TimeProxy {

    @Before("execution (void com.mashibing.dp.spring.v2.Tank.move())")
    public void before() {
        System.out.println("method start.." + System.currentTimeMillis());
    }

    @After("execution (void com.mashibing.dp.spring.v2.Tank.move())")
    public void after() {
        System.out.println("method stop.." + System.currentTimeMillis());
    }

}

public class Tank {

    /**
     * 模拟坦克移动了一段儿时间
     */
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Iterator迭代器

容器和容器遍历

•构建动态扩展的容器 List.add()
• 数组的实现
• 链表的实现

HashSet,Queue,List,Stack(栈)的底层就是数组或链表实现的
数据结构的物理结构只有两种,数组和链表

Collection接口下的ArrayList和LinkedList都是基于此设计模式实现的

在这里插入图片描述

Visitor访问者

用于在结构不变的情况下动态改变对于内部元素的动作

适合结构固定的,减少if,else代码。多数情况下用在编译器这

在这里插入图片描述

/**
 * 好处是Computer永远不需要更改,
 * 如果需要经常更改则不适合Visitor模式
 *
 * 抽取三个类中重复的if,else
 * 适合于编译器
 */
public class Computer {
    ComputerPart cpu = new CPU();
    ComputerPart memory = new Memory();
    ComputerPart board = new Board();

    public void sell(Visitor v) {
        this.cpu.accept(v);
        this.memory.accept(v);
        this.board.accept(v);
    }

    public static void main(String[] args) {
//        PersonelVisitor p = new PersonelVisitor();
        CorpVisitor c = new CorpVisitor();
        new Computer().sell(c);
        System.out.println(c.totalPrice);
    }
}

abstract class ComputerPart {
    abstract void accept(Visitor v);
    //some other operations eg:getName getBrand
    abstract double getPrice();
}

class CPU extends ComputerPart {

    @Override
    void accept(Visitor v) {
        v.visitCpu(this);
    }

    @Override
    double getPrice() {
        return 500;
    }
}

class Memory extends ComputerPart {

    @Override
    void accept(Visitor v) {
        v.visitMemory(this);
    }

    @Override
    double getPrice() {
        return 300;
    }
}

class Board extends ComputerPart {

    @Override
    void accept(Visitor v) {
        v.visitBoard(this);
    }

    @Override
    double getPrice() {
        return 200;
    }
}

interface Visitor {
    void visitCpu(CPU cpu);
    void visitMemory(Memory memory);
    void visitBoard(Board board);
}

class PersonelVisitor implements Visitor {
    double totalPrice = 0.0;

    @Override
    public void visitCpu(CPU cpu) {
        totalPrice += cpu.getPrice()*0.9;
    }

    @Override
    public void visitMemory(Memory memory) {
        totalPrice += memory.getPrice()*0.85;
    }

    @Override
    public void visitBoard(Board board) {
        totalPrice += board.getPrice()*0.95;
    }
}

class CorpVisitor implements Visitor {
    double totalPrice = 0.0;

    @Override
    public void visitCpu(CPU cpu) {
        totalPrice += cpu.getPrice()*0.6;
    }

    @Override
    public void visitMemory(Memory memory) {
        totalPrice += memory.getPrice()*0.75;
    }

    @Override
    public void visitBoard(Board board) {
        totalPrice += board.getPrice()*0.75;
    }
}

Builder构建器

构建复杂对象

• 分离复杂对象的构建和表示
• 同样的构建过程可以创建不同的表示
• 无需记忆,自然使用
在这里插入图片描述

public interface TerrainBuilder {
    TerrainBuilder buildWall();
    TerrainBuilder buildFort();
    TerrainBuilder buildMine();
    Terrain build();
}

public class ComplexTerrainBuilder implements TerrainBuilder {
    Terrain terrain = new Terrain();

    @Override
    public TerrainBuilder buildWall() {
        terrain.w = new Wall(10, 10, 50, 50);
        return this;
    }

    @Override
    public TerrainBuilder buildFort() {
        terrain.f = new Fort(10, 10, 50, 50);
        return this;
    }

    @Override
    public TerrainBuilder buildMine() {
        terrain.m = new Mine(10, 10, 50, 50);
        return this;
    }

    @Override
    public Terrain build() {
        return terrain;
    }
}

public class Main {
    public static void main(String[] args) {
        TerrainBuilder builder = new ComplexTerrainBuilder();
        Terrain t = builder.buildFort().buildMine().buildWall().build();
    }
}

public class Person {
    int id;
    String name;
    int age;
    double weight;
    int score;
    Location loc;

    private Person() {}

    public static class PersonBuilder {
        Person p = new Person();

        public PersonBuilder basicInfo(int id, String name, int age) {
            p.id = id;
            p.name = name;
            p.age = age;
            return this;
        }

        public PersonBuilder weight(double weight) {
            p.weight = weight;
            return this;
        }

        public PersonBuilder score(int score) {
            p.score = score;
            return this;
        }

        public PersonBuilder loc(String street, String roomNo) {
            p.loc = new Location(street, roomNo);
            return this;
        }

        public Person build() {
            return p;
        }
    }
}

public class Main {
    public static void main(String[] args) {
Person p = new Person.PersonBuilder()
                .basicInfo(1, "zhangsan", 18)
                //.score(20)
                .weight(200)
                //.loc("bj", "23")
                .build();
    }
}

Adapter适配器

接口转换器模式
举例,类似于,电压转换器。欧转英,英转中
在这里插入图片描述
案例:读取文件信息

public class Main {
    public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream("c:/test.text");
        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader br = new BufferedReader(isr);
        String line = br.readLine();
        while (line != null && !line.equals("")) {
            System.out.println(line);
        }
        br.close();
    }
}

Bridge桥接

在这里插入图片描述

Command命令

封装命令
结合cor实现undo
在这里插入图片描述

Prototype原型

Object .clone()
深浅拷贝
在这里插入图片描述
java中的原型模式
• 自带
• 实现原型模式需要实现标记型接口Cloneable
• 一般会重写clone()方法
• 如果只是重写clone()方法,而没实现接口,调用时会报异常
• 一般用于一个对象的属性已经确定,需要产生很多相同对象的时候
• 需要区分深克隆与浅克隆

/**
 * 浅克隆
 */

public class Test {
    public static void main(String[] args) throws Exception {
        Person p1 = new Person();
        Person p2 = (Person)p1.clone();
        System.out.println(p2.age + " " + p2.score);
        System.out.println(p2.loc);

        System.out.println(p1.loc == p2.loc);
        p1.loc.street = "sh";
        System.out.println(p2.loc);

    }
}

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("bj", 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Location {
    String street;
    int roomNo;

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\'' +
                ", roomNo=" + roomNo +
                '}';
    }

    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }
}

/**
 * 深克隆的处理
 */
public class Test {
    public static void main(String[] args) throws Exception {
        Person p1 = new Person();
        Person p2 = (Person)p1.clone();
        System.out.println(p2.age + " " + p2.score); //8 100
        System.out.println(p2.loc); //Location{street='bj', roomNo=22}

        System.out.println(p1.loc == p2.loc);//false
        p1.loc.street = "sh";
        System.out.println(p2.loc);
    }
}

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("bj", 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.loc = (Location)loc.clone();
        return p;
    }
}

class Location implements Cloneable {
    String street;
    int roomNo;

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\'' +
                ", roomNo=" + roomNo +
                '}';
    }

    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Memento备忘录

记录状态
便于回滚
在这里插入图片描述

TemplateMethod模板方法

钩子函数
在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        F f = new C1();
        f.m();
    }
}

abstract class F {
    public void m() {
        op1();
        op2();
        op3();
    }

    void op1(){
        System.out.println("op1");
    }
    void op3(){
        System.out.println("op3");
    }
    abstract void op2();
}

class C1 extends F {
    @Override
    void op3() {
        System.out.println("op03");
    }
    @Override
    void op2() {
        System.out.println("op2");
    }
}

在这里插入图片描述

State状态

根据状态决定行为
在这里插入图片描述

/**
 * 当增加新的状态时非常不方便
 */

public class MM {
    String name;
    MMState state = new MMHappyState();

    public void smile() {
        state.smile();
    }

    public void cry() {
        state.cry();
    }

    public void say() {
        state.say();
    }

    public static void main(String[] args) {
        MM mm = new MM();
        mm.say();
        mm.cry();
        mm.smile();
    }
}

public abstract class MMState {
    abstract void smile();
    abstract void cry();
    abstract void say();
}
public class MMSadState extends MMState {
    @Override
    void smile() {
        System.out.println("SadSmile");
    }

    @Override
    void cry() {
        System.out.println("SadCry");
    }

    @Override
    void say() {
        System.out.println("SadSay");
    }
}
public class MMNervousState extends MMState {
    @Override
    void smile() {
        System.out.println("NervousSmile");
    }

    @Override
    void cry() {
        System.out.println("NervousCry");
    }

    @Override
    void say() {
        System.out.println("NervousSay");
    }
}

public class MMHappyState extends MMState {
   	@Override
    void smile() {
        System.out.println("happy smile");
    }

    @Override
    void cry() {
        System.out.println("happy cry");
    }

    @Override
    void say() {
        System.out.println("happy say");
    }
}

设计模式的分类

总体来说设计模式分为三大类:

创建型模式:

共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式:

共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式:

共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

在这里插入图片描述

面向对象六大原则

单一职责原则

• Single Responsibility Principle
• 一个类别太大,别太累,负责单一的职责
• Person
• PersonManager
• 高内聚,低耦合

开闭原则

• Open-Closed Principle
• 对扩展开放,对修改关闭
• 尽量不修改原来代码的情况下进行扩展
• 抽象化,多态是开闭原则的关键

里氏替换原则

• Liscov Substitution Principle
• 所有使用父类的地方,必须能够透明的使用子类对象

依赖倒置原则

• Dependency Inversion Priciple
• 依赖倒置原则
• 依赖抽象,而不是依赖具体
• 面向抽象编程

接口隔离原则

• Interface Segregation Principle
• 每一个接口应该承担独立的角色,不干不该自己干的事儿

  • Flyable Runnable 不该合二为一
  • 避免子类实现不需要实现的方法
  • 需要对客户提供接口的时候,只需要暴露最小的接口
迪米特法则

• Law of Demeter
• 尽量不要和陌生人说话
• 在迪米特法则中,对于一个对象,非陌生人包括以下几类:

  • 当前对象本身(this);
  • 以参数形式传入到当前对象方法中的对象;
  • 当前对象的成员对象;
  • 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  • 当前对象所创建的对象。

• 和其他类的耦合度变低

总结

• OCP: 总纲,对扩展开放,对修改关闭
• SRP: 类的职责要单一
• LSP: 子类可以透明替换父类
• DIP: 面向接口编程
• ISP: 接口的职责要单一
• LoD: 降低耦合

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Flask中,前端传递参数可以通过URL参数、表单数据、JSON数据等方式进行。具体取决于你使用的是GET请求还是POST请求以及数据的格式。 如果是GET请求,参数可以通过URL参数传递。例如,你可以在URL中使用问号后面跟着参数名和值的形式来传递参数。例如,如果你想传递一个名为name的参数,可以这样做:/example?name=value。 如果是POST请求,参数可以通过表单数据或JSON数据传递。对于表单数据,你可以在HTML表单中使用input元素来定义参数,并在提交表单时将参数值发送到后端。对于JSON数据,你可以在前端使用JavaScript将参数封装为JSON对象,并在发送请求时将其作为请求体发送到后端。 在Flask中,你可以使用request对象来获取前端传递的参数。对于URL参数,你可以使用request.args来获取参数值。对于表单数据和JSON数据,你可以使用request.form和request.json来获取参数值。 总结起来,Flask前端传递参数的方式包括URL参数、表单数据和JSON数据,你可以根据具体的需求选择合适的方式来传递参数。 #### 引用[.reference_title] - *1* [Springboot 网页重定向时向前端传递参数Model类失效](https://blog.csdn.net/weixin_48456383/article/details/123201052)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【Spring】抽丝剥茧SpringMVC-请求间传递参数机制FlashMap](https://blog.csdn.net/beFocused/article/details/106929357)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值