大家好,我是刘望舒,腾讯最具价值专家,著有三本业内知名畅销书,连续五年蝉联电子工业出版社年度优秀作者,百度百科收录的资深技术专家。
前华为面试官、独角兽公司技术总监。
想要加入 BATcoder技术群,公号回复BAT
即可。
作者:leobert-lan
https://juejin.cn/user/2066737589654327/posts
前言
获得一个生成类图的工具,并通过文章快速了解是否方便且好用 了解一下我是如何折腾的
主要问题与方案
业务迭代后或者代码改进后,文档(uml图)未及时更新 手动维护耗时耗力
方便向他人介绍业务和代码 项目庞大或者复杂时,更容易找到需求对应的关注点,重新维护时日久远的业务时,状态来的快 图比代码亲切而且保护隐私🤣
编码时分析
编译时分析
运行时反射分析
问题分治与解决
分治1-简化输出产物
从源码、或者编译的中间产物中分析出类关系;ps:我们已经确定了要从编译中间产物出发 将类关系转变为图
分治2-确定分析的起始点
@Target(AnnotationTarget.CLASS)
annotation class GenerateClassDiagram {}
class Animal
@GenerateClassDiagram
class Dog : Animal()
@GenerateClassDiagram
class Cat : Animal()
为何不 "双向" 分析:继承和实现关系,双向分析会带来额外的复杂度,且在使用上规则不清晰,依赖关系难以双向分析。但是,如果使用规则上可以做到清晰明了,这一点值得实现 为何不标注在Animal上,进行反向分析:如果高层级的类在库包中,则需要修改库包,这不利于日常管理与维护 如果只标注了Cat而没有标注Dog,Dog将不会体现在图中?:是的 如果全部标注了,是否产生不良影响 :不会,但是没有必要
分治3-确定关系的分析方法
继承&实现
public interface TypeElement extends Element, Parameterizable, QualifiedNameable {
TypeMirror getSuperclass();
List<? extends TypeMirror> getInterfaces();
//其他无关代码略去
}
依赖&关联&聚合&组合
public interface TypeElement extends Element, Parameterizable, QualifiedNameable {
List<? extends Element> getEnclosedElements();
//无关代码略去
}
分治4-确定分析的终点
Relation(From,End) 表述 从From 到 End 的关系
分治5-分治3的补充,处理集合、数组、泛型
private abstract class CastingTypeVisitor<T> constructor(private val label: String) :
SimpleTypeVisitor6<T, Void?>() {
override fun defaultAction(e: TypeMirror, v: Void?): T {
throw IllegalArgumentException("$e does not represent a $label")
}
}
private class FetchClassTypeVisitor : CastingTypeVisitor<List<DeclaredType>>(label = "") {
override fun defaultAction(e: TypeMirror, v: Void?): List<DeclaredType> {
//ignore it
return emptyList()
}
override fun visitArray(t: ArrayType, p: Void?): List<DeclaredType> {
return t.componentType.accept(this, p)
}
override fun visitWildcard(t: WildcardType, p: Void?): List<DeclaredType> {
val ret = arrayListOf<DeclaredType>()
t.superBound?.let {
ret.addAll(it.accept(this, p))
}
t.extendsBound?.let {
ret.addAll(it.accept(this, p))
}
return ret
}
override fun visitDeclared(t: DeclaredType, p: Void?): List<DeclaredType> {
val ret = arrayListOf(t)
t.typeArguments?.forEach {
ret.addAll(it.accept(this, p))
}
return ret.toSet().toList()
}
override fun visitError(t: ErrorType, p: Void?): List<DeclaredType> {
return visitDeclared(t, p)
}
override fun visitTypeVariable(t: TypeVariable, p: Void?): List<DeclaredType> {
val ret = arrayListOf<DeclaredType>()
t.lowerBound?.let {
ret.addAll(it.accept(this, p))
}
t.upperBound?.let {
ret.addAll(it.accept(this, p))
}
return ret
}
}
fun TypeMirror.fetchDeclaredType(): List<DeclaredType> {
return this.accept(FetchClassTypeVisitor(), null)
}
分治6-关系的存储
分治7-类型的细节处理
abstract class UmlElement(val diagram: ClassDiagram?, val element: Element?) {
/**
* return: plant-uml 中相应的文本
* */
abstract fun umlElement(context: MutableSet<UmlElement>): String
abstract fun parseFieldAndMethod(diagram: ClassDiagram,
graph: DAG<UmlElement>,
cache: MutableSet<UmlElement>)
abstract fun drawField(fieldDrawer: FieldDrawer,
builder: StringBuilder,
context: MutableSet<UmlElement>)
abstract fun drawMethod(methodDrawer: MethodDrawer,
builder: StringBuilder,
context: MutableSet<UmlElement>)
}
UmlInterface:接口 UmlEnum:枚举 UmlClass:类 UmlStub:分治6中提到的虚拟顶点
interface IElementDrawer {
fun drawAspect(builder: StringBuilder, element: UmlElement, context: MutableSet<UmlElement>)
}
interface IJavaxElementDrawer {
fun drawAspect(builder: StringBuilder, element: Element, context: MutableSet<UmlElement>)
}
abstract class SuperClz : SealedI {
var superI: Int = 0
}
abstract class "SuperClz"{
.. fields ..
{field}-superI : int
.. methods ..
{method}+ getSuperI(): int
{method}+ setSuperI(int): void
}
分治8-输出为PlantUml文件
保持优雅
绘制多张ClassDiagram 增加配置,屏蔽一些输出,例如:不想看见private修饰的fields 包名是在是太长了,存在阅读干扰 等等
维持简单
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class ClassDiagram(
val qualifier: String = "",
val fieldVisible: Array<Visible> = [Visible.Private, Visible.Protected, Visible.Package, Visible.Public],
val methodVisible: Array<Visible> = [Visible.Private, Visible.Protected, Visible.Package, Visible.Public],
)
@ClassDiagram("Demo")
annotation class DemoDiagram
ClassDiagram : 标识注解表达分组,并且包含配置 GenerateClassDiagram :标识类图中的分析起始点
@GenerateClassDiagram
@DemoDiagram
class Clz : SuperClz(), SealedI {
val int: Int? = null
}
减少侵入
扩展能力
使用示例
添加依赖
implementation "io.github.leobert-lan:class-diagram-reporter:1.0.0"
annotationProcessor "io.github.leobert-lan:report-anno-compiler:1.1.4"
annotationProcessor "io.github.leobert-lan:class-diagram-reporter:1.0.0"
kapt {
arguments {
arg("module", "ktsample") //模块名称
arg("mode", "mode_file")
arg("active_reporter", "on")
}
}
自行定义注解
@ClassDiagram(qualifier = "BridgePattern")
annotation class BridgePatternDiagram
//or
@ClassDiagram(
qualifier = "AAAB",
fieldVisible = {Visible.Package, Visible.Public}
)
public @interface AAAB {
}
class BridgePattern {
@ClassDiagram(qualifier = "BridgePattern")
annotation class BridgePatternDiagram
interface MessageImplementor {
fun send(message: String, toUser: String)
}
abstract class AbstractMessage(private val impl: MessageImplementor) {
open fun sendMessage(message: String, toUser: String) {
impl.send(message, toUser)
}
}
@BridgePatternDiagram
@GenerateClassDiagram
class CommonMessage(impl: MessageImplementor) : AbstractMessage(impl)
@BridgePatternDiagram
@GenerateClassDiagram
class UrgencyMessage(impl: MessageImplementor) : AbstractMessage(impl) {
override fun sendMessage(message: String, toUser: String) {
super.sendMessage("加急:$message", toUser)
}
}
@BridgePatternDiagram
@GenerateClassDiagram
class MessageSMS : MessageImplementor {
override fun send(message: String, toUser: String) {
println("使用系统内短消息的方法,发送消息'$message'给$toUser")
}
}
@BridgePatternDiagram
@GenerateClassDiagram
class MessageEmail : MessageImplementor {
override fun send(message: String, toUser: String) {
println("使用邮件短消息的方法,发送消息'$message'给$toUser")
}
}
}
https://github.com/leobert-lan/ReportPrinter/
• 耗时2年,Android进阶三部曲第三部《Android进阶指北》出版!
为了防止失联,欢迎关注我的小号
微信改了推送机制,真爱请星标本公号👇