敞开成长之旅!这是我参加「日新计划 12 月更文挑战」的第2天,点击查看活动详情

一般做事务开发,不太简单有大量运用规划形式的场景。这里总结一下在事务开发中运用较为频繁的规划形式。当然言语为Java,基于Spring框架。

1 适配器形式(Adapter Pattern)

已存在的接口、服务,跟咱们所需、意图接口不兼容时,咱们需求经过必定的办法将二者进行兼容适配。一个常见的比如,家用电源(国标)220V,而手机标准输入一般为5V,此时咱们便需求一个适配器来将220V转换为5V运用。

适配器形式一般有3个角色:

  • Target: 方针接口
  • Adaptee: 需求进行适配的类(受改造者)
  • Adapter: 适配器(将Adaptee转为Target)

这个呈现的场景其实挺多,但实践彻底按照适配器形式编写代码的场景或许并不多。简单事务场景,直接就将适配、兼容代码混杂在事务代码中了。并没有将其摘出来处理。大部分事务代码,或许后续并不会再做扩展之类,过度规划反而会下降可读性并增加代码的复杂性。

适配器形式一般分为类适配器方针适配器
类适配器的话,运用承继方法完结:class Adapter extends Adaptee implements Target;而方针适配器的话,则运用组合方法完结。这种状况更灵敏一些。究竟咱们都引荐多用组合少用承继。

在学生产生约课完课等事情时,咱们需求将部分数据同步到外部CRM系统中。课程的话,按班级类型分为:1v1,小班课、大班课。不同的班级类型课程数据有所不同。事情上报时,并不是全量数据,有些数据需求消费者按需查询。如课程课程编号、称号、预定上课时刻等。

不同班级类型的课程由三个不同的FeignClient(Adaptee)供给服务,而咱们想要的便是查询课程相关信息(Target)。

为了模仿服务供给者,咱们Mock如下服务。

@Data
@Builder
public class OneClass {
    // 课程编号
    private String lessonNo;
    // 课程称号
    private String lessonName;
    // 其他信息
    private String one;
}
@Data
@Builder
public class SmallClass {
    // 课程编号
    private String lessonNo;
    // 课程称号
    private String lessonName;
    // 其他信息
    private String small;
}
@Data
@Builder
public class BigClass {
    // 课程编号
    private String lessonNo;
    // 课程称号
    private String lessonName;
    // 其他信息
    private String big;
}
public interface RemoteClassClient {
    default OneClass getOne() {
        return OneClass.builder().lessonNo("one").lessonName("1V1").build();
    }
    default SmallClass getSmall() {
        return SmallClass.builder().lessonNo("small").lessonName("小班课").build();
    }
    default BigClass getBig() {
        return BigClass.builder().lessonNo("big").lessonName("大班课").build();
    }
}
public class RemoteClassClientImpl implements RemoteClassClient {
}

该服务一致由RemoteClassClient对外供给各个班级类型的查询服务。

ClassService (Target方针接口、及方针方针)

/**
 * 课程信息
 */
@Data
@Builder
public class ClassInfoBO {
    // 课程类型 1:1v1 2:small 3:big
    private String type;
    // 班级ID
    private String classId;
    // 课程编号
    private String lessonNo;
    // 课程称号
    private String lessonName;
}
/**
 * 方针接口
 */
public interface ClassService {
    boolean match(String classType);
    ClassInfoBO getClassInfo(String classId);
}

下面咱们就需求几个适配器来完结适配。

1.1 方针适配器

OneClassAdapter

/**
 * 1v1适配器
 */
@Component
@RequiredArgsConstructor
public class OneClassAdapter implements ClassService {
    private static final String TYPE = "1";
    private final RemoteClassClient classClient;
    @Override
    public boolean match(String classType) {
        return TYPE.equals(classType);
    }
    @Override
    public ClassInfoBO getClassInfo(String classId) {
        final OneClass one = classClient.getOne();
        return ClassInfoBO.builder()
                .type("1")
                .classId(classId)
                .lessonNo(one.getLessonNo()).lessonName(one.getLessonName())
                .build();
    }
}

SmallClassAdapter

/**
 * 小班课适配器
 */
@Component
@RequiredArgsConstructor
public class SmallClassAdapter implements ClassService {
    private static final String TYPE = "2";
    private final RemoteClassClient classClient;
    @Override
    public boolean match(String classType) {
        return TYPE.equals(classType);
    }
    @Override
    public ClassInfoBO getClassInfo(String classId) {
        final SmallClass small = classClient.getSmall();
        return ClassInfoBO.builder()
                .type("2")
                .classId(classId)
                .lessonNo(small.getLessonNo()).lessonName(small.getLessonName())
                .build();
    }
}

BigClassAdapter

/**
 * 大班课适配器
 */
@Component
@RequiredArgsConstructor
public class BigClassAdapter implements ClassService {
    private static final String TYPE = "3";
    private final RemoteClassClient classClient;
    @Override
    public boolean match(String classType) {
        return TYPE.equals(classType);
    }
    @Override
    public ClassInfoBO getClassInfo(String classId) {
        final BigClass big = classClient.getBig();
        return ClassInfoBO.builder()
                .type("3")
                .classId(classId)
                .lessonNo(big.getLessonNo()).lessonName(big.getLessonName())
                .build();
    }
}

至此,适配器完结。能够依据具体场景选择不同的适配器,去适配当前的场景。再来个适配器工厂类。

ClassAdapterFactory

/**
 * 课程信息适配器工厂
 */
@Service
@RequiredArgsConstructor
public class ClassAdapterFactory {
    private final List<ClassService> classServiceList;
    ClassService getAdapter(String classType) {
        return classServiceList.stream()
                .filter(cs -> cs.match(classType)).findFirst()
                .orElse(null);
    }
}

1.2 类适配器

仅以其一举例。这种适配器不如方针适配器灵敏。

/**
 * 小班课适配器(类适配器)
 */
@Component
public class SmallClassAdapter2 extends RemoteClassClientImpl implements ClassService {
    private static final String TYPE = "2";
    @Override
    public boolean match(String classType) {
        return TYPE.equals(classType);
    }
    @Override
    public ClassInfoBO getClassInfo(String classId) {
        final SmallClass small = super.getSmall();
        return ClassInfoBO.builder()
                .type("2")
                .classId(classId)
                .lessonNo(small.getLessonNo()).lessonName(small.getLessonName())
                .build();
    }
}

能够看到,咱们经过承继的方法,使SmallClassAdapter2具备了RemoteClassClientImpl查询课程信息的才能。跟SmallClassAdapter也没有啥太大差异。

1.3 单测

@SpringBootTest
class ClassServiceTest {
    @Autowired
    private ClassAdapterFactory adapterFactory;
    @Test
    void testOne() {
        String classType = "1";
        String classId = "11111111";
        Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
            final ClassInfoBO classInfo = ad.getClassInfo(classId);
            assertEquals("one", classInfo.getLessonNo());
        });
    }
    @Test
    void testSmall() {
        String classType = "2";
        String classId = "22222222";
        Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
            final ClassInfoBO classInfo = ad.getClassInfo(classId);
            assertEquals("small", classInfo.getLessonNo());
        });
    }
    @Test
    void testBig() {
        String classType = "3";
        String classId = "33333333";
        Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
            final ClassInfoBO classInfo = ad.getClassInfo(classId);
            assertEquals("big", classInfo.getLessonNo());
        });
    }
}

2 思考

适配器形式能带来什么?

  1. 感觉最主要的还是带了一种解决方案(当然规划形式原本便是如此)。其次是对某些场景供给了一种标准,参加没有这个规划形式,咱们也能有解决方案,但具体完结或许千奇百怪(当然规划形式原本便是从千奇百怪中提炼出来的)。
  2. 规划形式不是银弹。避免滥用。

跟战略形式有啥差异?

  1. 单从完结方法(套路)来说。不能说毫无差异,几乎便是如出一辙。
  2. 细想一下,Adaptee在战略形式中,并不是必定存在的。它的意图是不同战略的具体完结和调用分开。而适配器形式的要点在于Adaptee -> Target的适配。

  • 工作中常用的规划形式–战略形式
  • 工作中常用的规划形式–适配器形式
  • 工作中常用的规划形式–责任链形式
  • 工作中常用的规划形式–享元形式

封面图来源: refactoring.guru/design-patt…