“在软件开发中,怎么应对不断改变的业务需求和技能应战?战略形式或许是一种处理方案。本文将介绍战略形式的概念和运用场景,以及怎么在实践项目中运用战略形式来进步代码的可保护性、可扩展性和可测验性。假如您也关怀怎么更好地应对改变的业务需求,那么请持续阅览下去。”

为什么要运用战略形式

战略形式的布景来历于面向目标规划的SOLID准则,特别是敞开/封闭准则(OCP)。敞开/封闭准则指出,软件实体(类、模块、函数等)应该对扩展敞开,对修正封闭。换句话说,当需求添加新功用时,应该经过添加新代码来扩展现有实体的行为,而不是经过修正现有代码来改变其行为。

SOLID准则

  • S(单一责任准则):一个类应该只有一个单一的功用,而且该功用应该由这个类完全封装起来。
  • O(敞开/封闭准则):一个软件实体如类、模块和函数应该对扩展敞开,对修正封闭。
  • L(里氏替换准则):子类应该能够替换它们的父类而且依然保持原有的行为。
  • I(接口隔离准则):客户端不应该依靠它不需求的接口。
  • D(依靠回转准则):高层模块不应该依靠低层模块,两者都应该依靠其笼统;笼统不应该依靠细节,细节应该依靠笼统。

在实践运用中,经常会遇到需求在程序运行时依据不同情况挑选不同的算法或处理办法的需求。假如直接在代码中硬编码不同的算法或处理办法,就会破坏敞开/封闭准则,使得代码难以扩展和保护。

战略形式便是为了处理这个问题而诞生的。战略形式将不同的算法或处理办法封装成独立的战略目标,客户端能够依据不同的需求挑选不同的战略目标来完结相应的操作。这样能够防止直接在代码中硬编码不同的算法或处理办法,使得程序愈加灵敏、可扩展和易于保护。

什么是战略形式

官方文档:

战略形式界说了一系列算法,将每个算法都封装起来,使得它们能够相互替换。战略形式让算法的改变独立于运用算法的客户端。

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

官方文档强调战略形式的两个重要特点:一是将算法封装成独立的战略目标,使得客户端能够依据不同的需求动态地挑选相应的算法;二是将算法的改变独立于客户端,使得算法的修正不会影响到客户端的代码。这两个特点使得战略形式具有高度的灵敏性、可扩展性和易于保护性,一起还能够进步代码的复用性和可测验性。

战略形式的组成成分:

  1. 战略(Strategy):界说一切支持的算法的公共接口。

  2. 详细战略(Concrete Strategy):完结详细的算法,而且供给了算法的完结细节。

  3. 环境(Context):用一个详细战略目标来装备,并保护对战略目标的引证。它需求知道一切的详细战略,并依据需求挑选适宜的算法。

举一个日子中的比如:

以交通出行办法的挑选为例,假定现在有一个需求挑选交通工具的场景,客户端需求依据不同的出行需求挑选不同的交通办法,如到公司要赶时间,挑选出租车;周末出去玩,挑选自行车等。这里,挑选交通工具的进程就能够看做是战略形式中的算法挑选。

在战略形式中,算法的挑选进程一般是由客户端来完结的。客户端能够依据不同的需求来挑选适宜的算法,一起客户端不需求关怀详细算法的完结细节。这里的客户端就能够是需求挑选交通办法的人。

代码完结

战略(Strategy): 公共接口

public interface TravelStrategy {
//    游览办法
    public void travel();
}

详细战略(Concrete Strategy):完结详细的算法,而且供给了算法的完结细节

//公交出行
public class BusStrategy implements TravelStrategy{
    @Override
    public void travel() {
        System.out.println("乘坐公交车游览");
    }
}
//自行车出行
public class BikeStrategy implements TravelStrategy{
    @Override
    public void travel() {
        System.out.println("骑自行车游览");
    }
}
//步行
public class WalkStrategy implements TravelStrategy{
    @Override
    public void travel() {
        System.out.println("步行游览");
    }
}

环境(Context):用一个详细战略目标来装备,并保护对战略目标的引证

public class Transportation {
    //保护的战略
    private TravelStrategy travelStrategy;
    public Transportation(TravelStrategy travelStrategy) {
        this.travelStrategy = travelStrategy;
    }
    public void travel() {
        travelStrategy.travel();
    }
    public void setTravelStrategy(TravelStrategy travelStrategy) {
        this.travelStrategy = travelStrategy;
    }
}

Client : 决议运用哪个战略

public class Client {
    public static void main(String[] args) {
        Transportation transportation = new Transportation(new BikeStrategy()); // 挑选骑自行车游览
        transportation.travel(); // 骑自行车游览
        transportation.setTravelStrategy(new WalkStrategy()); // 挑选步行游览
        transportation.travel(); // 步行游览
    }
}

在战略形式中,一般是将不同的算法完结封装到不同的战略类中,客户端依据需求挑选适宜的战略类来完结相应的功用。假如需求依据不同的条件挑选不同的算法完结,能够经过工厂形式或者装备文件等办法来动态创立相应的战略类,然后防止运用if-else句子。

class Transportation {
    private TravelStrategy strategy;
    //运用map存储
    private Map<String, TravelStrategy> strategies = new HashMap<>();
    public Transportation() {
        // 初始化战略映射
        strategies.put("walk", new WalkStrategy());
        strategies.put("bus", new BusStrategy());
        strategies.put("bike", new BikeStrategy());
    }
    public void setStrategy(String key) {
        this.strategy = strategies.get(key);
    }
    public void travel() {
        strategy.travel();
    }
}

Client

public class Client {
    public static void main(String[] args) {
        Transportation trans = new Transportation();
        // 依据不同的条件挑选出行办法
        trans.setStrategy("bus");
        trans.travel();
        trans.setStrategy("walk");
        trans.travel();
        trans.setStrategy("bike");
        trans.travel();
    }
}

Java源码中的表现

  • Java中的调集结构中的排序(Comparator)接口便是一个典型的战略形式的运用。用户能够经过传入不同的比较器目标,完结不同的排序战略。
// 界说一个Person类
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

比较器

// 界说一个按名字排序的比较器
public class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());
    }
}
// 界说一个按年纪排序的比较器
public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getAge() - o2.getAge();
    }
}

Client

// 运用示例
public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 25));
        people.add(new Person("Bob", 20));
        people.add(new Person("Charlie", 30));
        // 运用按名字排序的比较器进行排序
        Collections.sort(people, new NameComparator());
        for (Person p : people) {
            System.out.println(p.getName() + " " + p.getAge());
        }
        // 运用按年纪排序的比较器进行排序
        Collections.sort(people, new AgeComparator());
        for (Person p : people) {
            System.out.println(p.getName() + " " + p.getAge());
        }
    }
}
  • Comparator是战略(Strategy)
  • NameComparatorAgeComparator是详细战略(Concrete Strategy)
  • Collections类是充当了环境类的角色

Collections.sort()办法中,我们需求传入一个List目标和一个Comparator目标作为参数,其间Comparator目标便是详细战略目标,它的详细完结决议了排序的战略。List目标则是需求排序的数据调集。在排序时,Collections类依据传入的Comparator目标的详细完结来进行排序,并将排序结果运用到原有的List目标中,完结排序的进程。

Spring中的表现

  1. Resource接口: Spring的Resource接口是一个典型的战略形式。Resource接口界说了资源拜访的统一笼统,针对不同来历的资源,Spring供给了如ClassPathResource、FileSystemResource、UrlResource等不同的完结。

  2. Bean生命周期办理: Spring结构中,Bean的初始化和销毁进程有多种战略可选,如:InitializingBean和DisposableBean接口、@PostConstruct和@PreDestroy注解、以及经过XML或Java装备指定init-method和destroy-method。

  3. Spring AOP: 在Spring的AOP(面向切面编程)中,运用战略形式来界说切点(Pointcut)和告诉(Advice),以及不同的署理创立战略(如JDK动态署理和CGLIB署理)。

  4. Spring业务办理: Spring业务办理器(PlatformTransactionManager)接口界说了业务办理的战略,针对不同的数据拜访技能,Spring供给了如JdbcTransactionManager、HibernateTransactionManager、JpaTransactionManager等不同完结。

  5. Spring MVC中的处理器映射(HandlerMapping)和处理器适配器(HandlerAdapter): 处理器映射和处理器适配器都选用战略形式。例如,Spring MVC供给了依据注解的RequestMappingHandlerMapping和依据XML装备的BeanNameUrlHandlerMapping等不同映射战略;处理器适配器方面,如RequestMappingHandlerAdapter和SimpleControllerHandlerAdapter等。

  6. Spring中的缓存笼统: Spring供给了Cache接口和CacheManager接口来完结缓存战略的笼统,针对不同的缓存技能,Spring供给了如EhCache、Redis等不同完结。

笼统战略类 详细战略类 环境类 解释
Resource ClassPathResource, FileSystemResource, UrlResource ApplicationContext Spring中的资源拜访统一笼统。不同的完结允许拜访不同来历的资源,例如类路径资源、文件体系资源、URL资源等。
InitializingBean, DisposableBean, @PostConstruct, @PreDestroy, init-method, destroy-method BeanFactory, ApplicationContext Spring结构中,Bean的生命周期办理供给多种初始化和销毁战略,使得开发者能够依据需求挑选恰当的战略来办理Bean的创立和销毁。
Pointcut, Advice RegexpMethodPointcut, AspectJExpressionPointcut, BeforeAdvice, AfterAdvice ProxyFactoryBean, AspectJ Spring AOP中,经过切点和告诉战略界说横切重视点。切点确定运用告诉的方位,告诉界说在切点处履行的操作。这使得代码愈加模块化和易于保护。
PlatformTransactionManager DataSourceTransactionManager, HibernateTransactionManager, JpaTransactionManager TransactionTemplate Spring业务办理战略。为不同的数据拜访技能供给恰当的业务办理器完结,如JDBC、Hibernate、JPA等。这使得业务办理与数据拜访技能解耦。
HandlerMapping, HandlerAdapter RequestMappingHandlerMapping, BeanNameUrlHandlerMapping, RequestMappingHandlerAdapter, SimpleControllerHandlerAdapter DispatcherServlet Spring MVC中,处理器映射和处理器适配器战略用于处理HTTP恳求。不同的映射战略将恳求映射到相应的处理器,适配器负责履行恳求处理逻辑。这使得恳求处理愈加灵敏和可扩展。
Cache, CacheManager ConcurrentMapCache, EhCacheCacheManager, RedisCacheManager CacheInterceptor Spring中的缓存笼统战略。为不同的缓存技能供给完结,如ConcurrentMap、EhCache、Redis等。这使得缓存战略与缓存技能解耦,便于更换和扩展。

战略形式的优缺点

优点:

  1. 进步代码的复用性和可保护性:战略形式将算法封装在独立的战略类中,使得它们能够在不影响客户端的情况下进行修正和扩展。这有助于下降保护成本和进步代码的复用性。

  2. 简化客户端:战略形式将杂乱的算法逻辑封装在战略类中,使得客户端代码变得简洁,客户端只需引证战略类即可运用对应的算法,无需关怀算法的详细完结。

  3. 遵从开闭准则:战略形式遵从了开闭准则,即软件实体应该对扩展敞开,对修正封闭。在战略形式中,能够经过添加新的战略类来扩展算法,而不需求修正现有代码。

  4. 进步测验性:因为战略类一般是独立的可替换的组件,这使得对这些组件的测验变得愈加容易。每个战略类能够单独进行测验,而不会影响其他战略类的测验。

缺点:

  1. 添加类的数量:战略形式为每个算法都供给了一个单独的战略类,这或许导致体系中类的数量添加,添加了体系的杂乱性。

  2. 客户端需求了解战略类:尽管战略形式简化了客户端代码,但客户端仍需求了解各个战略类以便正确运用它们。这或许会添加客户端代码的杂乱性,尤其是在战略类较多时。

  3. 添加运行时开支:因为战略形式经过委托的办法调用战略类,这或许会导致运行时开支的添加。不过,这种开支一般能够承受,因为战略形式带来的优点远大于这点额外的开支。

怎么权衡战略形式的优缺点

在运用战略形式时,我们需求权衡其优缺点,依据实践项目需求和场景来决议是否选用战略形式。以下是一些主张,能够协助您在实践开发中做出决策:

  1. 剖析项目的需求和场景:首先,需求剖析项目的需求和场景,了解是否存在多个相互替换的算法或战略。假如存在这种情况,战略形式或许是一个适宜的挑选。

  2. 评估算法的杂乱性:考虑算法的杂乱性以及它们之间的关系。假如算法之间有许多共享的逻辑,战略形式或许不是最佳挑选,因为它会导致许多重复代码。在这种情况下,能够考虑其他规划形式,如模板办法形式。

  3. 项目的可扩展性需求:假如项目中的算法需求频频地扩展或修正,战略形式能够协助遵从开闭准则,使得扩展变得愈加容易。

  4. 考虑运行时性能开支:尽管战略形式或许会带来必定的运行时性能开支,但一般这种开支是能够承受的。假如性能是关键因素,能够在完结战略形式时选用一些优化办法,例如运用享元形式来减少战略目标的创立。

  5. 评估客户端的杂乱性:在运用战略形式时,需求权衡客户端代码的杂乱性。假如战略类较多,客户端或许需求了解这些战略类以便正确运用它们。在这种情况下,能够考虑运用简略工厂形式或笼统工厂形式来下降客户端对战略类的依靠。

战略形式的运用场景

  1. 多个算法相互替换:当一个问题能够用多种算法或战略来处理时,能够考虑运用战略形式。将不同的算法封装在独立的战略类中,使它们能够相互替换。

  2. 算法的运用者与完结者需求解耦:假如希望将算法的运用者与算法的详细完结进行解耦,能够运用战略形式。这样,算法的运用者只需求重视战略接口,而不需求知道详细的完结。

  3. 需求依据运行时条件挑选不同算法:当在运行时需求依据不同的条件来挑选不同的算法时,能够运用战略形式。经过将算法封装在战略类中,能够在运行时依据条件动态地挑选适宜的战略。

  4. 算法需求方便地扩展和修正:假如希望能够方便地扩展和修正算法,能够考虑运用战略形式。经过将算法封装在独立的战略类中,能够在不影响客户端代码的情况下对算法进行扩展和修正。

  5. 遵从开闭准则:当希望遵从开闭准则来规划一个软件体系时,能够考虑运用战略形式。战略形式能够让算法对扩展敞开,对修正封闭,这符合开闭准则的要求。