引言
又到了需求评审的日子,组长如约来到了会议室,与产品同学开启了融洽的沟通 …
产品 A:“本次需求重点是货架页改版。”
组长:“好的。” (内心:也不知这次要改啥,先听听!)
产品 A:“本次改动重点在于丰软件商店富基金货卡的形式和内容。”
组长:微笑 + 点头。
产品 A:龚俊“基于内容丰富度,设计美观性,我们整理了九种卡片样式,大家请看..编辑器怎么打开..”
组长:“嗯。”(内心:看起来不错的样子。)
产品 B:“补充一下,我们期望基金卡片可以随处展示,九种卡片可以随意组合。这个需要技术评估下。”
组长:“这个没问题,技术上完全可行,计划什么时候上线?”
产品们:“越快越好。”
组长:“额,好的。”
一、需求拆解
会议结束后,组长立即着手对需求进行拆解,发现有两处关键点:
- 基金 View 承载着基础信息 + 卡片信息,可以自由的展示在不同位置
- 多种卡片与基金 View 自由组合
依据拆解,组长提出了设计目标:
- 卡片进行抽象定义,与基金 View 隔离
- 卡片注入动态化,扩展无需改动现有代码
二、“卡片创建”实现方案演进
设计目软件技术专业标确定后,组编辑器小说长把卡片创建需求交给了小何:
组长:“小何,卡片创建的需求交给你负责了。”(内心:要求设计优秀,代码优雅)
小何:“放心吧组长,肯定没问题。”(公积金内心:这需求简单,好做)
于是小何兴冲冲地开启了编辑器怎么打开需求开发…阿里嘎多 同时开启了代码迭代之路…
2.1 控制流
根据卡片类型,创建不同卡阿里云片,思路再清晰不过了,于是小何轻松地写出了下面的代码,并自认为很好地实现了业务需求。
// CardOutView.kt
fun getCard(type: Int): View? {
var card: View = null
if (type == 1) {
card = Card1()
} else if (type == 2) {
...
} else {
throw IllegalArgumentException("type error!")
}
return card
}
不过很快,针对上面的代码,同事提出了几个疑问,让我们一起思考下:
同事 A: “假如我们新增一种卡片,这是否意味着你阿里嘎多必须要修改这部分沟通代码 ?”
小何一愣,还没来得及解释,有人接编辑器和ide的区别着问
同事 B:“假设卡片设计沟通的感悟变动,例如修改名称,这段逻辑是否需要同步改动?”
同事 C:“实际google业务中,所有卡片都需要设置数据,如何对卡片设计者进行规则限制?”
小何陷入了思考之中,一时间没法回答。
其实,上面的设计存在几个明显的问题 :
- 不符合 开闭原则,可以预见,如果后续新增卡片的话,需要直接修改主干代码,而我们提倡代码应该是对修改封闭的
- 不沟通能力符合 单一责任原则, 卡片创建与
CardOutView
耦合严重,我们提倡代码是低耦合高内聚的 - 没有对卡片进行 抽象,无法对卡片设计者提供有效约束,并导致可读性、扩展性差
最后,小何结合各位同事提出的问题和建议,着手对程序设计进行改进 …
2.2 抽象 & 工厂模式 & 反射
没有抽象的代码设计,其可读性和扩展性都会受到限制。同时抽象是程序解耦的枸杞必备前提。因此优化的第一步就是对卡片进行抽象,公积金定义接口编辑器手机版,并结合业务逻辑对接口内容进行补充。
在接口定义好后工龄差一年工资差多少,小何引入 工厂模式 来创建卡片,这里采用简单工厂方法,接收卡片类型,返回抽象卡片接口。
进一步优化,小何用反射替换 new Card()
创建具体对象。
优化后的代码结阿里巴巴1688货源批发官网构如下:
// 卡片接口
interface ICard {
fun setData(data: CardData)
fun getView(): View?
}
// CardOutView.kt
fun getCardViewAndSetData(type: Int, data: CardData): View? {
val card: ICard? = CardFactory.getCard(type)
card?.setData(data)
return card?.getView()
}
// CardFactory.kt
fun getCard(type: Int): ICard?{
val clazz = classMap[index] // 获取 class 信息。
val c: Constructor<out ICard>? = clazz?.getConstructor(Context::class.java)
return c?.newInstance()
}
优化后的结构类图如下:
代码经过优化后,虽然结构和设软件工程师计上比之前要复杂不少,但考虑到健壮性和拓展性,还是非常值得的。 此方案可以有效避免控制流实现的缺点,其优点主要有:
- 符合 开闭原则,新增卡片无需改动
CardOutView
类和CardFactory.ge编辑器哪个好用tCard()
方法 - 符合 单一责任原则, 卡阿里云盘片创建由
CardFactory
统一管理 - 对卡片进行 抽象,对卡片设计者提供有龚俊效约束,增强代码可读性,可扩展性
至此,经过同事的反馈沟通的感悟与建议,小何终于阿里得到工商银行了一套低耦合高内聚,同时符合开软件闭原则的设计。
小何:“各位同事觉得这次优化软件技术专业做的怎么样?”
同事们:“合格。但是龚俊,依编辑器和ide的区别然要戒骄戒躁。”
几天之后…
组长:“我看了代码实现,新增类型虽然无需改动主干代码,但是仍然需要手动维护 CardFactory
里面的一个 classMap
啊,能不能编辑器下载改成自动注入?”
小何再次陷入了思考之中 …
组长:“我觉得可以试试,思路,原理讲解中 …”
小何:“听起来挺厉害的,可以先试试。阿里嘎多”
2.3 An沟通的重要性的名言nota编辑器tion Processing Tool
在方案 2 中,当新增卡片时,仅需要在 classMap
中添加编辑器手机版新的键值对,这似乎是传统方案不得不保留的逻辑。那么我们要彻底实现动态化(新增卡片无需改动任何已公积金有代码),应该如何编辑器手机版做呢?在这里我们需要摒弃传统的设计思路,从另一个技术角度来思考,利用新的工具来迈出这关键的一步。
这一步的实现原理就是本沟通的重要性的名言文的核心: 利用 APT 实现卡片的动态注入。
很多第三方库在使用 APT 技术,如 Dagger2沟通的三要素是什么、ButterKnife编辑器小说,以及雪球公司自研的 snb-router。APT 技术可以在编译时软件根据 Annotation 信息生成相关的代码。工具使用容易(Easy),实现的功能却并不简单(Simple)。
技术方案确定后,小何对现有类图做了修改,结果如下
可以看到,改动范围并不大,那么上面的设计方案是软件工程师如何做到自动注入的呢?
其中的关键点是新增了 FundCard_Aut编辑器小说oGen
,这个类需要我们利沟通用 APT 技术自动编辑器英语生成,并令其继承于 CardFactory
。同时,我们修改 CardFactory
的 classMap
为 sta阿里员工离职感言tic沟通能力
方便后续扩展。
在程序编译时,Annotation Processor 会获取所有卡片注解以及关联的 Class 信息,而这些信息最终会被写入 c编辑器手机版lassMap
对象。最后我们还需要在 Application
添加一行代码来保证生成的代码逻辑得到有效执行:
// Application.kt
override fun onCreate() {
super.onCreate()
FundCard_AutoGen.init()
}
在组长的帮助(催促软件测试)下,小何整理了设计思路和技术方案,具体实现将在下一章详细介绍。
三、动态注入框架实现
本章讲述利用 APT 实现雪球卡片动态注入框架 CardInject 的详细原理和步骤。
3.1 前置知识
本节介绍 Java 注解的基础编辑器英语知识,了解的读者可以直接跳过。
-
3.1.1 Annotations in Java
Annotations, a form of meta编辑器和ide的区别data, provide data ab软件工程专业out a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.。沟通技巧和方法
- 注解以 @ 开头
- 注解作用是将元数据(metadata)与程序元素(实例变量、构造编辑器手机版函数、方法、类)关联起来
- 注解不只是注释,注解可以改变编译器处理程序的方式
注解可以实现多种功能,主要包含以下方面:
- 为编译器提供信息 – 编译器可以利用注解来检测编辑器下载错误或抑制警告
- 编译时和部署时处理 – 软件工具可以处理注释信息以生成代码、XML工资超过5000怎么扣税文件等等
- 运行时处理 – 有些注释可以在运行时进行检阿里嘎多查
Java 注解的层次结构如下:
如上图所示, @Override阿里云盘
是一个常用的标记注解,下面的代码利用 @Override
来展示注解的作用:
- 编辑器可以利用注解来检测错误或抑制警告(Line 9)
- 编译时,软件工具会借助注解检查错误
// OverWriteTest.java
public class OverWriteTest {
static class Base {
public void display() {
System.out.println("Base display()");
}
}
static class Extend extends Base {
@Override // Error hint
public void display(int a) {
System.out.println("OverWriteTest display()");
}
}
public static void main(String[] args) {
Base base = new Extend();
base.display();
}
}
在 jdk11 环境,运行 jav工商银行a OverWriteTest.java
命令,程序会抛出编译错误,因为我们并没有重写而是重载了 display
方法:
3.1.2 A公司让员工下班发手机电量截图PT 流程示意图
4.1.1 中详细介绍了 Annotation 知识。因此 Annotation Processing Tool 也就很好理解了。主要内容就是对注解的一种应用: 软件工具可以处理注释信息以生成代google码、XML文件等宫颈癌等。
CardInject 实现原理就是建立在 APT 之上的。主要包含以下步骤:
- 自定义注沟通的重要性解来标记基金货卡 – 对应图中的
Input Model
部分 - 利用处软件开发理工具阿里处编辑器软件理注解信息 – 对应图中
APT
部分 - 自动生成代码(
Type - Card软件工程
映射表classMap
)- 对应图中Out Model
部分
3.2 动态注入具体实现步骤
-
新软件开发建两个 Java Library
可以只阿里拍卖创建一个或者不创建从而把代码编辑器和ide的区别写在 App 目录下,这里按照软件商店下载惯例把注解和注解处理器分别放置在单独的 Library 中。如下:
-
在
apt-annotaion
中定义卡片注解
package com.example.apt_annotation;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface FundSubView {
String type();
}
创建自定义注解就是 apt-annotation Library 的唯一改动。
-
在
apt-processor
中处理注编辑器和ide的区别解
为了能够处理上面自定义的注解 FundSubView
。我们需要做些前期准备:软件商店下载
- 在 apt-processor 的 build.阿里gra阿里巴巴股票dle 增加如下改编辑器小说动 :
// build.gradle(:apt-processor)
dependencies {
...
// 引入 Google 的注解处理工具。
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
- 在 App 中做如下改动:
// build.gradle(:app)
implementation project(path: ':apt-annotation')
kapt project(":apt-processor")
- 完成上面两步,基础准备工作就完成了。接下来进行具体的业务处理。首先用注解标记基金卡片:
@FundSubView(type = "1")
public class CardView extends View{
...
}
-
接下来创建注解处理类 (动态注入实现的核心逻辑)
- 创建
FundSubProcess编辑器135or
继承于AbstractProcessor
- 用
@AutoServic编辑器e阿里巴巴批发网官网(Processor.class)
标记 - 重写
getSupport编辑器和ide的区别ed软件技术AnnotationTypes
,确定要处理的注解类型 - 必须重写
process
,这里可以软件技术获取注解信息,然后根据信息生成相关代码
- 创建
package com.example.apt_processor;
@AutoService(Processor.class)
public class FundSubProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(FundSubView.class.getCanonicalName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
Set<? extends Element> temp = roundEnv.getElementsAnnotatedWith(FundSubView.class);
for (Element element : temp) {
// 根据注解生成 Java 类
}
return true;
}
}
在生成 java 源文件时,我们利用 Javapoet 工具帮助我们完成,并且让生成的类继承 Ca阿里巴巴股票rdFactory
, 因为这样可以少写很多自动生成代码的逻辑。源码可查看文末的 Demo 地址。
3.3 动态注入最终实现成果
-
动态生成的代码
public class FundCard_AutoGen extends CardFactory {
public static void init() {
register("1", FundRateLineView.class);
register("2", FundCardContimuousWinHsView.class);
...
}
}
-
使用方式
✅ 当新增卡片时,组内同事只需要软件测试在新的卡片类上引沟通的三要素是什么入 @FundSubView(type = "newType")
即可。无需改动、新增任何代码。
@FundSubView(type = "5")
class FundSubViewNew5 : View {
...
}
✅ 获取卡片逻辑在 CardFactor公积金y
中,仍然利用反射获取,并且软件工程师无需维护 classMap
映射表 。
// CardFactory.kt
fun getCard(type: Int):Card?{
val clazz = classMap[index] // 获取 class 信息。
val c: Constructor<out Card>? = clazz?.getConstructor(Context::class.java)
return c?.newInstance()
}
至此,小何通过自己的思考分析 ,沟通能力结合组内同事们的帮助,终于完成了卡片的动态google注入,从此以后再也阿里拍卖不用担心修改现有代工龄差一年工资差多少码啦。
3.4 场景进阶
小何利用 APT 对原有方案进行了比较大的改动。那么除了实现动态注入,还有其他的优点吗?
答案是有的,而且是实际中经常遇到的场景:跨组件调用。目前 Android 项目普编辑器软件遍采用组件化设计,在实现功能隔离的同时,龚俊也要额外考虑组件通信与交互。常用的组件通信框架 EventBus 以及阿里的组件路由框架 ARouter 其实现原理都依赖了 APT。同理,基于沟通的重要性的名言 APT 实现的卡片动态注入也是可以做到的,在文末的 Demo 中,我们实现了submodule 调用 App 中卡片的情景,实际开发中由于业务耦合,需要根据具体情况分析,但是原理殊途同归,大家有兴趣可以查看。
四 小结
本文从具体项目出发,通过小何与同事之间的对话呈现出实际开发中解决问题和技术演进过程。为沟通的艺术了追求“绝对的自动化”,引出了本文的核心 : APT,然后利用此技术一步步实现卡片的动态注入框架。文章一方面给读者提供了从业务出发,对代码进行逐步优化从而衍生出框架的思路;另一方面团队成员在一起积极的对方案设计的讨论也能很好的调用起团队的技术氛围以及鼓励追求极致的匠心精神。
本文对APT 的应用还比较基础。很多其他的优秀设计,仍然需要读者们去研究学习,从而做到举一反阿里众包三、学以致用。我们也希望通过本文介绍,激发大家的想象沟通技巧和方法空间,帮助大家实现更多优秀的功能。
附:
Demo 地址
FundCardDemo
雪球 App 下载
AppDownLoad
参阿里巴巴1688货源批发官网考文献
APT 实现简易的 Fin工龄越长退休金越多吗dViewById
Java Annotation
JavaPoet