设计模式:详细拆解策略模式

news2024/11/15 8:33:57

策略模式

既然是详解,就不以案例开头了,直奔主题,先来看看什么是策略模式。

模式定义

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

结构

Strategy(策略接口):

  • 用来约束一系列具体的策略算法。
  • 定义了算法的公共接口,使得算法可以互换使用。

ConcreteStrategy(具体策略实现)

  • 具体的算法实现,继承自策略接口。
  • 每个具体策略类实现了策略接口中定义的算法

Context(上下文):

  • 负责和具体的策略类交互。
  • 通常上下文会持有一个真正的策略实现。
  • 上下文可以让具体的策略类来获取上下文的数据。
  • 甚至可以让具体的策略类来回调上下文的方法。

样例代码:

// 策略接口
interface Strategy {
    void algorithmInterface();
}

// 具体策略A
class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略A的算法实现");
    }
}

// 具体策略B
class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略B的算法实现");
    }
}

// 具体策略C
class ConcreteStrategyC implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略C的算法实现");
    }
}

// 上下文类
class Context {
    private Strategy strategy;

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

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void contextInterface() {
        strategy.algorithmInterface();
    }
}

// 客户端代码
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategyA());
        context.contextInterface();

        context.setStrategy(new ConcreteStrategyB());
        context.contextInterface();

        context.setStrategy(new ConcreteStrategyC());
        context.contextInterface();
    }
}

策略模式实现案例

我们以CRM中的报价场景为例,来看一下策略模式的使用,简化一下场景,会有如下的报价方案:

  • 对普通客户或者是新客户报全价:
  • 对老客户报的价格,统一折扣5%;
  • 对大客户报的价格,统一折扣10%。

根据策略模式的思路,我们大致要做如下内容:

  1. 首先需要定义出算法的接口。
  2. 然后把各种报价的计算方式单独出来,形成算法类。
  3. 对于Price类,把它当做上下文,在计算报价的时候,不再需要判断,直接使 用持有的具体算法进行运算即可。具体选择使用哪一个算法的功能挪出去,放到外部使 用的客户端去。

策略模式形成的类图如下:

基于SpringBoot的项目实现

// 策略接口
public interface Strategy {
    double calcPrice(double goodsPrice);
}

// 普通客户策略
@Commpont
public class NormalCustomerStrategy implements Strategy {
    @Override
    public double calcPrice(double goodsPrice) {
        // 普通客户不打折
        return goodsPrice;
    }
}

// 老客户策略
@Commpont
public class OldCustomerStrategy implements Strategy {
    @Override
    public double calcPrice(double goodsPrice) {
        // 老客户享受5%的折扣
        return goodsPrice * 0.95;
    }
}

// 大客户策略
@Commpont
public class LargeCustomerStrategy implements Strategy {
    @Override
    public double calcPrice(double goodsPrice) {
        // 大客户享受10%的折扣
        return goodsPrice * 0.90;
    }
}

// 报价上下文类
public class Price {
    private Strategy strategy;

    // 构造函数,初始化策略
    public Price(Strategy strategy) {
        this.strategy = strategy;
    }

    // 设置策略
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    // 计算并返回报价
    public double quote(double goodsPrice) {
        return strategy.calcPrice(goodsPrice);
    }
}




public enum ClientType{

  //正常客户
  normal,

  //老客户
  old,

  //大客户
  large;

}



@Service
public class ClientServiceImpl implement ClientService{

@Autowired
private ApplicationContext context;

//……


public ResData createQutePrice(String clientId){
    ResData result = new ResData();
    Client client = ClientMapper.findById(clientId);
    //……客户其他认证逻辑
    Double nowPrice = XXMapper.queryNowPrice();
    result.setPrice(getPriceUtil(client.getClientType()).qute(nowPrice ));
    return result;
}



private Price getPriceUtil(ClientType type){
  Strategy strategy = null; 
  switch(type){
     case old:
          strategy = (Strategy)context.getBean(OldCustomerStrategy.class);
          break;
     case large:
          strategy = (Strategy)context.getBean(LargeCustomerStrategy.class);
          break;
     default:
          strategy = (Strategy)context.getBean(NormalCustomerStrategy.class);
          break;
   }
   Price price = new Price(strategy);
   return price;
}


}

使用该写法,虽然相比if-else来讲要更为麻烦,但是随着报价场景的增加,我们仅仅通过新增实现类和调整getPriceUtil方法即可完成扩展。

策略模式详解

策略模式的本质

策略模式的本质是:

分离算法,选择实现

策略模式的功能是把具体的算法实现从具体的业务处理中独立出来,把它们实现成 为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换。 策略模式的重心不是如何来实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法, 大家的地位是完全一样的,正是因为这个平等性,才能实现算法之间可以相互替换。 所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。 所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。

从设计原则角度

  • 策略模式很好地体现了开一闭原则。策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能够很容易地把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。这在前面的示例中已经体现出来了,好好体会一下。
  • 策略模式还很好地体现了里氏替换原则。策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个父类。这样只要使用策略的客户保持面向抽象类型编程,就能够使用不同策略的具体实现对象来配置它,从而实现一系列算法可以相互替换。

策略模式与If-else

看了前面的示例,很多朋友会发现,每个策略算法具体实现的功能,就是原来在f-else 结构中的具体实现。 没错,其实多个if-elseif语句表达的就是一个平等的功能结构,你要么执行if,要么 执行else,或者是elseif,这个时候,if块中的实现和else块中的实现从运行地位上来讲是平等的。 而策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下 文来与具体的策略类进行交互。所以其实很多地方都在讲策略模式能消除遍地if-else是不准确的,if-else并没有消失,只是代码形式改变了,但是使用策略模式替换if-else的优势是不可否认的,将原本if-else内的逻辑抽离出来,可以达到修改具体内容而不破坏外层框架的目的。因此多个f-else语句可以考虑使用策略模式。

Strategy的扩展

在前面的示例中,Strategy都是使用接口来定义的,这也是常见的实现方式。但是如 果多个算法具有公共功能的话,可以把Strategy实现成为抽象类,然后把多个算法的公 共功能实现到Strategy中。

// 抽象策略类,包含公共功能
public abstract class Strategy {
    // 公共方法,所有策略类都会用到
    public void commonMethod() {
        System.out.println("这是一个公共方法");
    }

    // 抽象方法,具体的策略算法实现
    public abstract void algorithmInterface();
}

// 具体策略A
public class ConcreteStrategyA extends Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略A的算法实现");
    }
}

// 具体策略B
public class ConcreteStrategyB extends Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略B的算法实现");
    }
}

// 具体策略C
public class ConcreteStrategyC extends Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("执行策略C的算法实现");
    }
}

Context与Strategy的关系

在策略模式中,通常是上下文(Context)使用具体的策略实现对象。反过来,策略实现对象也 可以从上下文获取所需要的数据。因此可以将上下文当作参数传递给策略实现对象,这 种情况下上下文和策略实现对象是紧密耦合的。 在这种情况下,上下文封装看具体策略对象进行算法运算所需要的数据,具体策略 对象通过回调上下文的方法来获取这些数据。 甚至在某些情况下,策略实现对象还可以回调上下文的方法来实现一定的功能,这种使用场景下,上下文变相充当了多个策略算法实现的公共接口。在上下文定义的方法 可以当作是所有或者是部分策略算法使用的公共功能。

但是需要注意,由于所有的策略实现对象都实现同一个策略接口,传入同一个上 下文,可能会造成传入的上下文数据的浪费,因为有的算法会使用这些数据, 而有的算法不会使用,但是上下文和策略对象之间交互的开销是存在的。

以一个公司结算的场景案例来说:

很多企业的工资支付方式是很灵活的,可支付方式是比较多的,比如,人民币现金支付、美元现金支付、银行转账到工资账户、银行转账到工资卡;一些创业型的企业为了留住骨干员工,还可能有工资转股权等方式。总之一句话,工资支付方式很多。

随着公司的发展,会不断有新的工资支付方式出现,这就要求能方便地扩展;另外工资支付方式不是固定的,是由公司和员工协商确定的,也就是说可能不同的员工采用的是不同的支付方式,甚至同一个员工,不同时间采用的支付方式也可能会不同,这就要求能很方便地切换具体的支付方式。

要实现这样的功能,显然策略模式是一个很好的选择。在实现这个功能的时候,不同的策略算法需要的数据是不一样,比如,现金支付就不需要银行账号,而银行转账就需要账号。

这就导致在设计策略接口中的方法时,不太好确定参数的个数,而且,就算现在把所有的参数都列上了,扩展性难以保障,加入一个新策略,就需要修改接口。我们根据上面对于Context与Strategy的关系分析,基于将上下文当作参数传递给策略对象的方式以策略模式实现上述需求

先定义工资支付的策略接口,也就是定义一个支付工资的方法:

/**
 * 支付工资的策略接口,公司有多种支付工资的算法
 * 比如,现金、银行卡、现金加股票、现金加期权、美元支付等
 */
public interface PaymentStrategy {
    /**
     * 公司给某人真正支付工资
     * @param ctx 支付工资的上下文,里面包含算法需要的数据
     */
    public void pay(PaymentContext ctx);
}

这里先简单实现人民币现金支付和美元现金支付方式,当然并不是真地去实现跟银行的交互,只是示意一下:

/**
 * 人民币现金支付
 */
public class RMBCash implements PaymentStrategy {
    public void pay(PaymentContext ctx) {
        System.out.println("现在给" + ctx.getUserName() 
                           + "人民币现金支付" + ctx.getMoney() + "元");
    }
}

/**
 * 美元现金支付
 */
public class DollarCash implements PaymentStrategy {
    public void pay(PaymentContext ctx) {
        System.out.println("现在给" + ctx.getUserName() 
                           + "美元现金支付" + ctx.getMoney() + "元");
    }
}

 下面是上下文的实现以及使用:

/**
 * 支付工资的上下文,每个人的工资不同,支付方式也不同
 */
public class PaymentContext {
    /**
     * 应被支付工资的人员,简单点,用姓名来代替
     */
    private String userName = null;
    /**
     * 应被支付的工资金额
     */
    private double money = 0.0;
    /**
     * 支付工资的方式的策略接口
     */
    private PaymentStrategy strategy = null;

    // 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略
    // @param userName 被支付工资的人员
    // @param money 应支付的金额
    // @param strategy 具体的支付策略
    public PaymentContext(String userName, double money, PaymentStrategy strategy) {
        this.userName = userName;
        this.money = money;
        this.strategy = strategy;
    }

    // 只有getter方法,让策略算法在实现的时候,根据需要来获取上下文中的数据
    public String getUserName() {
        return userName;
    }

    public double getMoney() {
        return money;
    }

    /**
     * 立即支付工资
     */
    public void payNow() {
        // 使用客户希望的支付策略来支付工资
        this.strategy.pay(this);
    }
}


public class Client {
    public static void main(String[] args) {
        // 创建相应的支付策略
        PaymentStrategy strategyRMB = new RMBCash();
        PaymentStrategy strategyDollar = new DollarCash();

        // 准备小李的支付工资上下文
        PaymentContext ctx1 = new PaymentContext("小李", 5000, strategyRMB);
        // 向小李支付工资
        ctx1.payNow();

        // 切换一个人,给Petter支付工资
        PaymentContext ctx2 = new PaymentContext("Petter", 8000, strategyDollar);
        ctx2.payNow();
    }
}

基本的策略模式框架已经搭建完成,如果接下来需要增加一种支付方式,要求能支付到银行,基于以上代码,扩展方式有两种。

扩展方式一:通过扩展上下文对象(Context)来准备新的算法需要的数据

/**
 * 扩展的支付上下文对象
 */
public class PaymentContext2 extends PaymentContext {
    /**
     * 银行账号
     */
    private String account = null;

    /**
     * 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略
     * @param userName 被支付工资的人员
     * @param money 应支付的金额
     * @param account 支付到的银行账号
     * @param strategy 具体的支付策略
     */
    public PaymentContext2(String userName, double money, String account, PaymentStrategy strategy) {
        super(userName, money, strategy);
        this.account = account;
    }

    public String getAccount() {
        return account;
    }
}

/**
 * 算法策略的实现
 * 支付到银行卡
 */
public class Card implements PaymentStrategy {
    public void pay(PaymentContext ctx) {
        // 这个新的算法自己知道要使用扩展的支付上下文,所以强制造型一下
        PaymentContext2 ctx2 = (PaymentContext2) ctx;
        System.out.println("现在给" + ctx2.getUserName() + "的"
                           + ctx2.getAccount() + "账号支付了" + ctx2.getMoney() + "元");
        // 连接银行,进行转账,就不去管了
    }
}


public class Client {
    public static void main(String[] args) {
        // 创建相应的支付策略
        PaymentStrategy strategyRMB = new RMBCash();
        PaymentStrategy strategyDollar = new DollarCash();
        // 准备小李的支付工资上下文
        PaymentContext ctx1 = new PaymentContext("小李", 5000, strategyRMB);
        // 向小李支付工资
        ctx1.payNow();
        // 切换一个人,给Petter支付工资
        PaymentContext ctx2 = new PaymentContext("Petter", 8000, strategyDollar);
        ctx2.payNow();
        // 测试新添加的支付方式
        PaymentStrategy strategyCard = new Card();
        PaymentContext2 ctx3 = new PaymentContext2("小王", 9000, "010998877656", strategyCard);
        ctx3.payNow();
    }
}

 通过代码实现,可以看出,这种扩展方式是新增加一种支付到银行卡的策略实现,然后通过继承来扩展支付上下文,其中添加新的支付方式需要的新数据,比如银行卡账户,并在客户端使便用新的上下文和新的策略实现就可以了,这样已有的实现都不需要改变,完全遵循开一闭原则。

扩展方式二:通过策略的构造方法传入新算法所需数据 

/**
 * 支付到银行卡
 */
public class Card2 implements PaymentStrategy {
    // 账号信息
    private String account = "";

    /**
     * 构造方法,传入账号信息
     * @param account 账号信息
     */
    public Card2(String account) {
        this.account = account;
    }

    public void pay(PaymentContext ctx) {
        System.out.println("现在给" + ctx.getUserName() + "的"
                           + this.account + "账号支付了" + ctx.getMoney() + "元");
        // 连接银行,进行转账,就不去管了
    }
}

// 直接在客户端测试就可以了。示例代码如下:
public class Client {
    public static void main(String[] args) {
        // 测试新添加的支付方式
        PaymentStrategy strategyCard2 = new Card2("010998877656");
        PaymentContext ctx4 = new PaymentContext("小", 9000, strategyCard2);
        ctx4.payNow();
    }
}

 

  • 对于扩展上下文的方式:

    • 优点:所有策略的实现风格更统一,策略需要的数据都统一从上下文来获取,这样在使用方法上也很统一;在上下文中添加新的数据,别的相应算法也可以用得上,可以视为公共的数据。
    • 缺点:如果这些数据只有一个特定的算法来使用,那么这些数据有些浪费;每次添加新的算法都去扩展上下文,容易形成复杂的上下文对象层次,也未见得有必要。
  • 对于在策略算法的实现上添加自己需要的数据的方式:

    • 优点:实现起来简单,容易理解。
    • 缺点:实现风格与其他策略不一致,其他策略都是从上下文中来获取数据,而这个策略的实现一部分数据来自上下文,一部分数据来自自己,有些不统一;外部使用这些策略算法的时候也不一,难于以一个统一的方式来动态切换策略算法。

适用策略模式的场景

  1. 当出现有许多相关的类,仅仅是行为有差别的情况下,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换。

  2. 当出现同一个算法,有很多不同实现的情况下,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次。

  3. 当需要封装算法中,有与算法相关数据的情况下,可以使用策略模式来避免暴露这些跟算法相关的数据结构。

  4. 当出现抽象一个定义了很多行为的类,并且是通过多个f-else语句来选择这些行为的情况下,可以使用策略模式来代替这些条件语句。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1971457.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win10 没有自动亮度选项注册表关闭屏幕亮度自动变化

1、 WinR ,输入命令:regedit 回车 2、依次展开位置:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class{4d36e968-e325-11ce-bfc1-08002be10318} 然后将0000和0001可能还有0002中的 KMD_EnableBrightnessInterface2 的值改为0即可。 …

亚马逊、ebay自养号测评方法:从零到一打造高效流量和销量

在跨境电商领域,如马逊、拼多多Temu、shopee、Lazada、wish、速卖通、煤炉、敦煌、雅虎、eBay、TikTok、Newegg、乐天、美客多、阿里国际、沃尔玛、OZON等平台上,卖家为了提升店铺的权重、流量及销量,常常需要进行产品测评。自养号测评作为一…

TransactionAspectSupport.currentTransactionStatus.setRollbackOnly 是什么作用,为什么这么写

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 是Spring框架提供的一个方法,用于在事务执行过程中显式地指示当前事务需要回滚。 使用这个方法一般结合try catch使用,出现异常时,可以做到不抛出异常,但…

华为云耀服务器安装和使用MobSF进行APP基线检查

华为云耀服务器安装和使用MobSF进行APP基线检查 MobSF简介 Mobile Security Framework(MobS,移动安全框架)是一种自动化多平台移动应用程序,支持Android、iOS和Windows应用自动化测试。能够进行静态、动态分析,web A…

【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

最终效果 文章目录 最终效果前言为什么使用CharacterControllerSimpleMove和Move如何选择?1. SimpleMove2. Move 配置CharacterController参数控制相机移动跳跃方式一方式二 下蹲处理下坡抖动问题实现奔跑和不同移速控制完整代码补充,简单版本 实现物理碰…

--归并排序--

归并排序是我们研究的最后一种排序了,那什么是归并排序呢? 我们之前在力扣上面刷过一个题目,就是合并两个有序数组,其实也是和这个是一个思想借用第三 个数组,然后将第三个数组拷贝给a数组,这样就实现了排…

计算机毕业设计选题推荐-智慧物业服务系统-Java/Python项目实战

✨作者主页:IT毕设梦工厂✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

2024年08月05日截稿 | 第三届人工智能、物联网和云计算技术国际会议(AIoTC 2024)

第三届人工智能、物联网和云计算技术国际会议(AIoTC 2024) 2024 3rd International Conference on Artificial Intelligence, Internet of Things and Cloud Computing Technology 2024年9月13-15日 | 中国武汉 重要信息 大会官网:www.ic…

S200自检程序报错

现象:在ATE电脑断网的情况下,启动自检程序,提示如下报错: 解决方法:

大模型在RPA领域的应用与探索-代码生成

01. 前言 随着人工智能技术的飞速发展,大模型在多个领域的应用日益广泛。特别是在机器人流程自动化(RPA)领域,这些技术的进步为自动化任务的执行带来了显著的效率提升。然而,传统RPA在任务流程编排上依赖人工编写脚本…

如何在 VSCode 中使用驭码CodeRider?【实践篇】

极狐GitLab 在 5 月 28 日正式发布了 AI 产品驭码CodeRider,可以使用驭码CodeRider 进行AI 编程 & DevOps 流程处理。现已开启免费试用,登录官网:https://coderider.gitlab.cn/ 即可申请试用。 GitLab 中文版学习资料 驭码CodeRider 官…

大模型之大模型技术生态

本文作为大模型综述第四篇,介绍语言大模型技术生态。 随着大模型技术的快速发展,大模型的生态体系也在快速构建。典型的大模型平台如 ChatGPT、文心一言、讯飞星火等提供如 APP、网页版、 API 接口等多种形式的开放服务,并通过开放插件机制、Function Call 等实现大模型外部…

STL常用容器- set/ multiset 容器

set基本概念 set也叫做集合,它的特点就是所有的元素在插入的时候会自动完成排序(默认是升序排列)。 set在物理空间上也不是连续的,所以它就不支持随机存取(利用下标), 它的迭代器也不支持指针算术运算,只能进行和--。…

filebeat

1、作用 1、可以在本机收集日志2、也可以远程收集日志3、轻量级的日志收集系统,可以在非java环境运行。logstash是在jmv环境中运行,资源消耗很大,启动一个logstash要消耗500M左右的内存,filebeat只消耗10M左右的内存。收集nginx的…

在Jira中使用AI

Jira已经可以使用AI功能了。 如果您使用的是Jira Cloud,您需要请管理员在管理页面中打开AI功能开关。(AI功能在Standard版中未提供,请使用Premium或更高级的版本)如果您使用的是自己部署的Jira Data Center,您需要请管…

Java中实现文件上传

目录 1、文件上传本地 1.1 原理 1.2 如何使用文件上传 1.2.1 引入文件上传的依赖 1.2.2 配置文件上传拦截器 1.2.3 完成文件上传的代码 2、文件上传oss服务器 2.1 为什么需要上传到oss服务器 2.2 如何使用oss 2.2.1 开启oss服务 2.2.2 在Java中引入依赖 2.2.3 查看自…

HarmonyOS(47) onSizeChange和onAreaChange

onSizeChange和onAreaChange onSizeChangeonAreaChangeonAreaChange和onSizeChange的区别参考资料 onSizeChange 组件区域变化时触发该回调。仅会响应由布局变化所导致的组件尺寸发生变化时的回调。由绘制变化所导致的渲染属性变化不会响应回调,如translate、offse…

深度学习模型服务端部署——flask+gunicorn+supervisor+nginx+docker

前言:深度学习模型经过前期的训练调优评估,最终得到一个精度速度满足要求的模型(.pth, .ckpt,或者.onnx等等格式),但模型要实际用起来,还得部署起来,部署分为在移动端芯片上和服务器上。在移动端芯片部署通…

联邦学习开山之作论文解读与Pytorch实现FedAvg

参考文献:McMahan B, Moore E, Ramage D, et al. Communication-efficient learning of deep networks from decentralized data[C]//Artificial intelligence and statistics. PMLR, 2017: 1273-1282. 参考的文章: 1.联邦学习代码解读,超详细…

Object.defineProperty在Vue2双向绑定中的核心原理及应用

目录 1.Object.defineProperty方法 (1)介绍 (2)语法 (3)descriptor属性描述符 2.Object.defineProperty在Vue2双向绑定的核心原理 3.Object.defineProperty在vue2中的应用 (1&#xff09…