策略模式优雅实践

news2024/11/24 15:45:51

1 什么是策略模式

        策略模式(Strategy Pattern)是一种常用的面向对象设计模式,它定义了一系列可互相替换的算法或策略,并将每个算法封装成独立的对象,使得它们可以在运行时动态地替换。具体来说,策略模式定义了一系列算法,每个算法都封装在一个具体的策略类中,这些策略类实现了相同的接口或抽象类。在使用算法的时候,客户端通过一个上下文对象来调用策略类的方法,从而完成算法的执行。这样,客户端可以在运行时动态地选择不同的策略类,从而实现不同的行为。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

这个模式涉及到三个角色:

  ●  环境(Context)角色:持有一个Strategy的引用。

  ●  抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

  ●  具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

2 认识策略模式

  策略模式的重心:

  策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

  算法的平等性:

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

  所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。

  运行时策略的唯一性:

  运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。

  公有的行为:

  经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。

3 策略模式的优点和缺点

策略模式的优点

  (1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。

  (2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

策略模式的缺点

  (1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。

  (2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

4 策略模式适用场景:

        (1) 当一个系统中存在多个类只有它们的行为或算法不同时。

        (2) 当一个类定义了多种行为,而这些行为在这个类的操作中以多个条件语句的形式出现,可以将相关的条件分支移入它们各自的策略类中,以替换这些条件语句。

        (3) 当系统需要动态地在几种算法中选择一种时,如根据不同的配置、用户选择或者环境条件等。

5 策略模式的最佳实践(重点)

        策略模式是一个非常简单且常用的设计模式,策略模式最常见的作用就是解决代码中冗长的 if-else 或 switch 分支判断语句;同时也可以提高程序的扩展性和灵活性,避免代码重复

策略模式的使用包含三部分:策略的定义、创建和使用。

5.1 策略的定义、创建和使用

5.1.1 策略的定义

        策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。

public interface Strategy {
    void algorithmInterface();
}

public class ConcreteStrategyA implements Strategy {
    @Override
    public void  algorithmInterface() {
        //具体的算法...
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void  algorithmInterface() {
        //具体的算法...
    }
}

        如上述代码所示,定义了一个策略接口 Strategy,具体的策略实现类都实现 Strategy 接口,并重写 algorithmInterface 方法,最后客户端根据不同的策略即可调用不同实现类的 algorithmInterface 方法。

5.1.2 策略的创建

        可以将创建策略的代码逻辑抽象到工厂类中,提前在工厂类创建好所有策略类,缓存在 Map 中。Map 的 key 为策略类型,value 为具体的策略实现类。当需要使用策略时根据 type 去 Map 中 get 即可获取到相应的策略实现类。

public class StrategyFactory {
  // Map 的 key 为策略类型,value 为具体的策略实现类
  private static final Map<String, Strategy> strategies = new HashMap<>();

  // 提前创建好所有策略类,缓存到 Map 中
  static {
    strategies.put("A", new ConcreteStrategyA());
    strategies.put("B", new ConcreteStrategyB());
  }

  // 需要使用策略时根据 type 去 Map 中 get 即可获取到相应的策略实现类
  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return strategies.get(type);
  }
}

        这里重点在于使用”查表法”代替了大量的分支判断,即每次根据 type 去 Map 中获取,省略了大量的 if-else。

5.1.3 策略的使用

        具体使用 A、B、C 何种策略,在具体的场景,可以会根据系统的配置来选择。可以从配置文件中读取出配置,然后传递给策略工厂类 StrategyFactory 的 getStrategy 方法即可获取到相应的策略类。最后调用策略类的 algorithmInterface 方法去执行代码逻辑。

代码如下所示,省略了从配置文件中读取配置的流程。

// 根据 type 的不同,执行不同分支的代码逻辑,
private void process(String type){
    Strategy strategy = StrategyFactory.getStrategy(type);
// 调用策略类的 algorithmInterface 方法去执行代码逻辑
strategy.algorithmInterface();
}

5.1.4 当前设计是否容易扩展性

        最原始的 process 方法中,首先会判断各种 type,然后执行不同类型的代码逻辑。如果需要扩展新的 D、E、F 类型,需要大量修改 process 方法。

优化后,如果需要扩展新的 D、E、F 类型,流程如下:

  • 定义相应的 D、E、F 类型的策略实现类
  • 提前在策略工厂类 StrategyFactory 中创建相应的策略实现类,并添加到 Map 中
  • 客户端代码不用进行任何改动,即:process 方法不需要进行改动

        优化后的代码相对来说职责更加单一,且对调用方非常友好。调用方代码不需要任何改动即可使用新的策略。要做的可能就是在配置文件中配置新的策略即可。

5.2 有状态的策略类不能提前创建

        在策略类的创建部分,在类初始化时,将所有的策略类提前创建好,存放在 Map 中。当需要使用策略时根据 type 去 Map 中 get 即可获取到相应的策略实现类。

        假设策略类是有状态的,每次获取策略对象时,都要求创建新的策略类。此时,就不能使用 Map 缓存的方式来优化代码结构了。可以使用如下方式实现策略工厂类:

public class StrategyFactory {
    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }

        if (type.equals("A")) {
            return new ConcreteStrategyA();
        } else if (type.equals("B")) {
            return new ConcreteStrategyB();
        }

        return null;
    }
}

上述代码又退化回了 if-else 嵌套,当然也可以优化为 switch-case 的设计。

        极客时间-王争老师的《设计模式之美》课程第 60 节最后的课堂讨论中留下了一道题目:在策略工厂类中,如果每次都要返回新的策略对象,我们还是需要在工厂类中编写 if-else 分支判断逻辑,那这个问题该如何解决呢?

        笔者看到文末大家的回答,点赞数最高的评论是:仍然可以用查表法,只不过存储的不再是实例,而是class,使用时获取对应的class,再通过反射创建实例。

        反射在这里应该是可以实现,但是笔者感觉不是非常灵活,假设策略实现类需要在这里调用一些有参构造器,且不同的策略类的有参构造器需要传入的参数不同,那么反射实现起来不是非常灵活。

        例如 ConcreteStrategyA 的构造器需要传入 age,ConcreteStrategyB 的构造器需要传入 date。对于这样的 case,反射不太好实现,如果实现出来,也是一对 if-else 分支判断。

        文末也没有其他令笔者眼前一亮的回答,反倒是笔者在阅读 Flink 源码的过程中,发现了一个笔者感觉比较优秀的解决方案,下面就到了秀操作环节。

5.2.1 秀操作

首先分析一个问题来源:

  • 对于无状态的策略类,将所有的策略类提前创建好,存放在 Map 中。当需要使用策略时根据 type 去 Map 中 get 即可获取到相应的策略实现类。
  • 对于有状态的策略类,不能提前创建所有的策略类,所以没办法提前创建好将其存放在 Map 中

换种思路:

  • 给每个具体的策略类创建相应的策略类工厂。例如 ConcreteStrategyA 的工厂为 StrategyFactoryA, ConcreteStrategyB 的工厂为 StrategyFactoryB。
  • 虽然没办法提前创建好策略类放到 Map 中,但是可以将策略类的工厂类提前创建好放到 Map 中。根据传入的 type 就可以从 Map 中获取相应策略类的工厂类,然后执行工厂类的 create 方法即可创建出相应的策略类。

根据上述思路,实现相应代码。

首先定义策略工厂接口,并分别实现策略 A 和策略 B 的工厂类:

// 策略工厂接口
public interface StrategyFactory {
    Strategy create();
}

// 策略 A 的工厂类,用于创建策略 A
public class StrategyFactoryA implements StrategyFactory{
    @Override
    public Strategy create() {
        return new ConcreteStrategyA();
    }
}

// 策略 B 的工厂类,用于创建策略 B
public class StrategyFactoryB implements StrategyFactory{
    @Override
    public Strategy create() {
        return new ConcreteStrategyB();
    }
}

对外开放的工厂实现如下:

public class Factory {

    // Map 的 key 为策略类型,value 为 策略的工厂类
    private static final Map<String, StrategyFactory> 
    STRATEGY_FACTORIES = new HashMap<>();

    static {
        // 将各种实现类的工厂提前创建好放到 Map 中
        STRATEGY_FACTORIES.put("A", new StrategyFactoryA());
        STRATEGY_FACTORIES.put("B", new StrategyFactoryB());
    }

    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }
        // 根据 type 获取对应的策略工厂
        StrategyFactory strategyFactory = STRATEGY_FACTORIES.get(type);
        // 调用具体工厂类的 create 方法即可创建出相应的策略类
        return strategyFactory.create();
    }
}

        Factory 类中定义了 Map,Map 的 key 为策略类型,value 为 策略的工厂类。Factory 类初始化时,将各种实现类的工厂提前创建好放到 Map 中。

        Factory 类的静态方法 getStrategy 用于根据 type 创建相应的策略类,getStrategy 方法根据 type 从 Map 中获取 type 对应的策略类的工厂,调用具体工厂类的 create 方法即可创建出相应的策略类。

当策略类的构造方法比较复杂也没关系,封装在策略类相应的工厂中即可。

        旧方案对于每次要创建新策略类的场景,要搞一堆 if-else 分支判断,上述流程使用 Map 优化了 if-else 分支判断逻辑。但带来了一个新的问题,即:创建出了很多类,相比之前的实现来讲,多了 StrategyFactoryA 和 StrategyFactoryB 类。

        为了代码的简洁,可以利用 Java8 的 lambda 表达式将 StrategyFactoryA 和 StrategyFactoryB 类优化掉。截取上述部分代码实现:

// 策略工厂接口
public interface StrategyFactory {
    Strategy create();
}

// 策略 A 的工厂类,用于创建策略 A
public class StrategyFactoryA implements StrategyFactory{
    @Override
    public Strategy create() {
        return new ConcreteStrategyA();
    }
}

Map<String, StrategyFactory> STRATEGY_FACTORIES = new HashMap<>();
// 将各种实现类的工厂提前创建好放到 Map 中
STRATEGY_FACTORIES.put("A", new StrategyFactoryA());

上述代码使用 lambda 优化后:

// 策略工厂接口
public interface StrategyFactory {
    Strategy create();
}

Map<String, StrategyFactory> STRATEGY_FACTORIES = new HashMap<>();
// 将各种实现类的工厂提前创建好放到 Map 中
STRATEGY_FACTORIES.put("A", () -> new ConcreteStrategyA());

关于 lambda 这里就不多解释了。lambda 表达式还能优化为 Java8 的方法引用,代码如下所示:

STRATEGY_FACTORIES.put(“A”, ConcreteStrategyA::new);

5.2.1 小结

把上述整个代码的最终版贴在这里:

// 策略工厂接口
public interface StrategyFactory {
  Strategy create();
}

public class Factory {

    // Map 的 key 为策略类型,value 为 策略的工厂类
    private static final Map<String, StrategyFactory> 
      STRATEGY_FACTORIES = new HashMap<>();

    static {
       // 将各种实现类的工厂提前创建好放到 Map 中
        STRATEGY_FACTORIES.put("A", ConcreteStrategyA::new);
        STRATEGY_FACTORIES.put("B", ConcreteStrategyB::new);
    }

    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }
        // 根据 type 获取对应的策略工厂
        StrategyFactory strategyFactory = STRATEGY_FACTORIES.get(type);
       // 调用具体工厂类的 create 方法即可创建出相应的策略类
        return strategyFactory.create();
    }
}

        代码量相比之前的策略类可以共享的代码设计来讲,只是增加了一个 StrategyFactory 接口的设计,所以整体代码也是非常简洁的。

5.3 策略模式在项目中的真实实现

5.3.1 实际需求

        智慧停车的功能模块,由于各个地方项目上用到的第三方车厂厂商的设备不一致(如 科托,捷顺,红门,零壹等)对应需要要对接的接口也比较多;虽然对接的车厂不一样,但是在我们业务系统中要做的事都是一样的;如都需要通过我们app来开通月卡,续费,扫码出场,车辆预约等;只是这些功能的实现需要去调用不同车厂的提供的相似功能的接口去实现;

        该系统需要动态地在几种算法中选择一种时,如根据不同的配置来选择;因此选择策略模式是比较合适的;这样可以程序结构更灵活,具有更好的维护性和扩展性(后续如果要加入其他的第三方车厂就很方便拓展了);策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复.

5.3.2 实践

策略的定义

        策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。

public interface ThirdParkVersionService {

    /**--------------------------------科托车场厂商相关接口对接--------------------------------------------*/
    KeyTopResp<CarsCardVOs> getCarCardList(CarCardDTOs dto);
    
    /**--------------------------------捷顺车场厂商相关接口对接--------------------------------------------*/
    JsstResp<DelaybymodeVO> delaybymode(DelaybymodeDTO delaybymodeDTO);
}
/**
 * @Description 聚合不同厂商的默认实现 
 * @version1.0 利用接口默认方法进行空实现,防止下层接口需要实现过多方法
 */
public interface ThirdParkDefaultService extends ThirdParkVersionService{

    static String getThirdConfigKey(String thirdParkId,Long projectId){
        return projectId.toString().concat(thirdParkId);
    }

    /**--------------------------------------科托厂商接口默认实现-------------------------------------------------*/
    /**
     * @Description:
     * @Param: [carCardDTOs]
     * @return: com.bzcst.bop.iot.car.manager.entity.common.response.KeyTopResp<com.bzcst.bop.iot.car.manager.entity.keytopvo.CarsCardVOs>
     * @Author: xiongguoshuang
     * @Exception: 获取固定车列表
     * @Date: 2022/5/9 13:51
     */
    @Override
    default KeyTopResp<CarsCardVOs> getCarCardList(CarCardDTOs dto) {
        return null;
    }
......................
    
}

public interface KeyTopService extends ThirdParkDefaultService{
 //实现科托相关接口
    
}
public interface JsstService extends ThirdParkDefaultService {
    // 实现捷顺相关接口
}

@Component(ThirdParkConstants.BUSINESS_TYPE_KEYTOP_VER_FIVE)
public class KeyTopFiveServiceImpl implements KeyTopService{
    //接口实现类
}
@Component(ThirdParkConstants.BUSINESS_TYPE_JSST_VER_THIRD)
public class JsstThirdServiceImpl implements JsstService {
    //接口实现类
}
策略的创建

        可以将创建策略的代码逻辑抽象到工厂类中,提前在工厂类创建好所有策略类,缓存在 Map 中。Map 的 key 为策略类型,value 为具体的策略实现类。当需要使用策略时根据 type 去 Map 中 get 即可获取到相应的策略实现类。这里的Map 通过服务类自定义的类名自动注入实现

@Component
public class ThirdParkFactorySelector {

    @Resource
    private Map<String, ThirdParkFactoryService> handlers;

    public ThirdParkFactoryService thirdParkFactoySelector(String providerCode) {
        ThirdParkFactoryService thirdParkFactoryService = handlers.get(providerCode);
        return thirdParkFactoryService;
    }
}

@Component
public class ThirdParkFactoryServiceSelector {

    @Resource
    DeviceProviderApiService deviceProviderApiService;

    @Resource
    CarYardConfigService carYardConfigService;

    @Resource
    ThirdParkFactorySelector thirdParkFactorySelector;

    /**
     * 根据车场ID获取对应的车场厂商实例 调用需要实现的业务逻辑
     */
    public ThirdParkFactoryService getThirdParkFactoryService(Long carParkId) {
        CarYardConfig carYardConfig = getCarYardConfigByParkId(carParkId);
        Long partnerId = carYardConfig.getPartnerId();
        if(partnerId == null){
            throw new BusinessException("根据carParkId未找到对应的partnerId");
        }
        DeviceProviderVO provider = deviceProviderApiService.get(partnerId);
        return thirdParkFactorySelector.thirdParkFactoySelector(provider.getCode());
    }

}
策略的使用

        具体在业务代码中调用那个车厂厂商的接口,是根据系统配置表中具体的配置来选择,从数据表中读取具体的配置,调用getThirdParkFactoryService获取对应的策略类;最后调用策略类的中具体的方法去执行代码逻辑。

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

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

相关文章

【uniapp】小程序开发6:自定义状态栏

一、自定义状态栏 可以设置某个页面的状态栏自定义或者全局状态栏自定义。 这里以首页状态栏为例。 1&#xff09;pages.json 中配置"navigationStyle": "custom"&#xff0c;代码如下&#xff1a; {"pages": [ {"path": "pa…

记录在搭建Jenkins时,所遇到的坑,以及解决方案

项目场景&#xff1a; 记录在搭建Jenkins时,所遇到的坑,以及解决方案.问题描述1 在使用Jenkins构建时,报错如下&#xff1a; cp: cannot stat /project/xx/xxxx/dist/: No such file or directory Build step Execute shell marked build as failure Finished: FAILURE解决方…

CRMEB开发小程序无法访问 突然提示系统错误 pc端进入也是空白 无提示, 还有权限问题

删除一下runtime/cache文件夹&#xff0c;然后重新给站点权限。 然后检查一下ssl证书 参考&#xff1a;https://www.crmeb.com/ask/thread/35073 我实际操作删除的是runtime/temp下的文件&#xff0c;就好了。 还有权限问题&#xff0c;> 直接在 文件夹哪里给777权限就可以了…

【408常用数据结构】Continually Updating

文章目录 前言数组链表单向链表双向链表双向链表 树二叉搜索树红黑树B树B树 堆优先队列排序算法&#xff08;重点&#xff09; 栈栈实现单调栈算法&#xff08;可不学&#xff09; 图 前言 这一篇文章是因为有几个玩的比较好的朋友觉得我DS学的还行&#xff0c;然后他们也要准…

解决Error in rawToChar(block[seq_len(ns)]) :

今天运行harmony的tutorial时&#xff0c;发现有一个错误&#xff0c;就是 singlecellmethods包需要安装&#xff0c;该包的网址在于 https://github.com/immunogenomics/singlecellmethods 但是我使用 install.packages("/Volumes/Elements SE/单细胞数据集/harmony201…

go语言中结构体tag使用

go中的tag在结构体中使用的。有两个作用&#xff1a; &#xff08;1&#xff09;给结构体属性起别名&#xff0c;字段映射。对于不同类型的类型对应不同的值&#xff0c;比如xml&#xff0c;yaml&#xff0c;json等分别对应着不同的区域&#xff0c;当然标签也不同。比如json&…

运行在移动设备上的ML机器学习任务——基于MediaPipe的手势识别

前期的文章我们介绍了MediaPipe的人手关键点检测。其检测的21个点的坐标位置如下: 当检测到其关键点后,我们就可以利用此关键点来进行人手手势识别。MediaPipe 手势识别器任务可实时识别手势,并提供识别的手势结果。我们可以使用此任务来识别用户的特定手势,并调用与这些手…

用python写代码:pip list,列出:pip install 库1 库2库3...,方便一次性安装错误

彻底解决pip安装库问题 用python写代码&#xff1a;pip list 列出&#xff1a;pip install 库1 库2 库3 这种输出以下是使用Python编写代码来实现类似pip list和pip install package1 package2 package3输出的示例&#xff1a; import pkg_resources# 列出已安装的包和版本 i…

LLMs 库尔贝克-莱布勒散度 KL Kullback-Leibler Divergence

KL-散度&#xff0c;或称为库尔巴克-莱布勒散度&#xff0c;是在强化学习领域经常遇到的概念&#xff0c;特别是在使用Proximal Policy Optimization&#xff08;PPO&#xff09;算法时。它是两个概率分布之间差异的数学度量&#xff0c;帮助我们了解一个分布与另一个分布的差异…

BGP在运营商专线业务下的部署

1. 为什么说BGP是网工的分水岭&#xff1f; 2. BGP的路由黑洞如何产生&#xff1f; 3. BGP协议在什么场景下使用&#xff1f; --- BGP - 边界网关协议 - 一种动态路由协议 --- 路由协议 - 运行在路由器上的软件 - 路由器和路由器彼此之间交换路由信息 --- 同步路…

YOLOv5入门实践(1)— 基础环境介绍及搭建

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLOv5基础知识入门系列和YOLOv5源码中的参数超详细解析系列学习完成之后&#xff0c;接着就进入YOLOv5入门实践系列了。为了让大家能够清楚地了解如何用YOLOv5去进行实践&#xff0c;本篇文章就简单给大家介绍一下YOLOv5基…

红队专题-从零开始VC++远程控制软件RAT-C/S-[2]界面编写及上线

红队专题 招募六边形战士队员1.课前回顾unicode编码 字符串 2.界面编程(下)对话框重载消息函数更改对话框同步更改 3.服务端上线&#xff0c;下线&#xff0c;以及客户端的资源销毁(上)添加socket 变量添加 socket 消息填补config信息创建线程函数 并运行添加Addhost添加 getIt…

C盘或用户名文件夹变成桌面后改不回去问题解决

问题&#xff1a;为了给C盘腾出空间&#xff0c;会把桌面文件夹移动到别的盘。系统自带的功能使得右击这些文件夹&#xff0c;属性&#xff0c;位置里就可以简单完成移动。 然而看似简单的操作依旧有不少人弄出问题。比如&#xff1a; 把桌面文件夹移动到某盘根目录&#xff0…

Python开源项目周排行 2023年第35周

#2023年第35周2023年10月3日1vizro一个用于创建模块化数据可视化应用程序的工具包。在几分钟内快速自助组装定制仪表板 - 无需高级编码或设计经验 - 创建灵活且可扩展、支持 Python 的数据可视化应用程序。使用几行简单的配置来创建复杂的仪表板&#xff0c;这些仪表板是利用 P…

ElfBoard,为嵌入式学习爱好者创造更具价值的学习体验

ElfBoard是飞凌嵌入式面向学习者推出的全新子品牌&#xff0c;旨在为嵌入式学习爱好者创造更具价值的学习体验。 ELF是"Embedded Learning Fans"嵌入式学习爱好者的首字母缩写&#xff0c;同时ELF也是“精灵”的意思&#xff0c;ElfBoard以灵动的精灵形象作为品牌Lo…

公安机关警务vr综合实战模拟训练提高团队合作能力

公安出警VR虚拟仿真培训软件是VR公司利用VR虚拟现实和web3d开发技术&#xff0c;对警务执法过程中可能发生的各种场景进行还原、模拟、演练&#xff0c;结合数据分析&#xff0c;实施量化考核&#xff0c;提高学员的心理承压、应急处突、遇袭反应和临危处置综合能力。 公安出警…

什么是 SRE?一文详解 SRE 运维体系

目录 可观测性系统 故障响应 故障复盘 测试与发布 容量规划 自动化工具开发 用户体验 可观测性系统 在任何有一定规模的企业内部&#xff0c;一旦推行起来整个SRE的运维模式&#xff0c;那么对于可观测性系统的建设将变得尤为重要&#xff0c;而在整个可观测性系统中&a…

QT:工业软件开发的首选“

QT&#xff1a;工业软件开发的首选&#xff0c;强大的GUI框架与多功能扩展" 在工业软件开发领域&#xff0c;主要从事自动化、机械自动化和电气自动化的人员通常使用C或C作为主要编程语言。然而&#xff0c;在当今互联网发展的背景下&#xff0c;Qt成为了C中唯一可靠的G…

项目成员积分规则

在当下的项目/团队管理种&#xff0c;如何让成员能清晰的看到&#xff0c;自己的工作、努力在团队种属于那个段位&#xff0c;通过这个形式&#xff0c;并配合其他方式去点燃成员的进步之心。以积分的形式&#xff0c;代替绩效考核&#xff0c;一些零散的想法&#xff0c;欢迎各…

使用Jenkins自动化部署项目

Jenkins的同类产品 Jenkins 是一款功能强大的开源持续集成/持续交付 (CI/CD) 工具&#xff0c;但也有一些替代品可供选择&#xff0c;以下是其中一些&#xff1a; Travis CI&#xff1a;Travis CI 是另一款流行的持续集成工具&#xff0c;可与 GitHub 集成&#xff0c;支持多种…