首页 文章 精选 留言 我的

精选列表

搜索[赛博朋克],共10000篇文章
优秀的个人博客,低调大师

每日一博 | AOP 那点事儿

又是一个周末,刚给宝宝喂完牛奶,终于让她睡着了。所以现在我才能腾出手来,坐在电脑面前给大家写这篇文章。 今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之。翻译过来就是“面向方面编程”,可我更倾向于翻译为“面向切面编程”。它听起有些的神秘,为什么呢?当你看完这篇文章的时候,就会知道,我们做的很重要的工作就是去写这个“切面” 。那么什么是“切面”呢? 没错!就是用一把刀来切一坨面。注意,相对于面而言,我们一定是横着来切它,这简称为“横切”。可以把一段代码想象成一坨面,同样也可以用一把刀来横切它,下面要做的就是如何去实现这把刀! 需要澄清的是,这个概念不是由Rod Johnson(老罗)提出的。其实很早以前就有了,目前最知名最强大的 Java 开源项目就是 AspectJ 了,然而它的前身是AspectWerkz(该项目已经在 2005 年停止更新),这才是 AOP 的老祖宗。老罗(一个头发秃得和我老爸有一拼的天才)写了一个叫做Spring 框架,从此一炮走红,成为了 Spring 之父。他在自己的IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。 那么 AOP 到底是什么?如何去使用它?本文将逐步带您进入 AOP 的世界,让您感受到前所未有的畅快! 不过在开始讲解 AOP 之前,我想有必要回忆一下这段代码: 1. 写死代码 先来一个接口: publicinterfaceGreeting{ voidsayHello(Stringname); } 还有一个实现类: publicclassGreetingImplimplementsGreeting{ @Override publicvoidsayHello(Stringname){ before(); System.out.println("Hello!"+name); after(); } privatevoidbefore(){ System.out.println("Before"); } privatevoidafter(){ System.out.println("After"); } } before() 与 after() 方法写死在 sayHello() 方法体中了,这样的代码的味道非常不好。如果哪位仁兄大量写了这样的代码,肯定要被你的架构师骂个够呛。 比如:我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢? 再比如:我们要写一个 JDBC 程序,那是不是也要在方法的开头去连接数据库,方法的末尾去关闭数据库连接呢? 这样的代码只会把程序员累死,把架构师气死! 一定要想办法对上面的代码进行重构,首先给出三个解决方案: 2. 静态代理 最简单的解决方案就是使用静态代理模式了,我们单独为 GreetingImpl 这个类写一个代理类: publicclassGreetingProxyimplementsGreeting{ privateGreetingImplgreetingImpl; publicGreetingProxy(GreetingImplgreetingImpl){ this.greetingImpl=greetingImpl; } @Override publicvoidsayHello(Stringname){ before(); greetingImpl.sayHello(name); after(); } privatevoidbefore(){ System.out.println("Before"); } privatevoidafter(){ System.out.println("After"); } } 就用这个 GreetingProxy 去代理 GreetingImpl,下面看看客户端如何来调用: publicclassClient{ publicstaticvoidmain(String[]args){ GreetinggreetingProxy=newGreetingProxy(newGreetingImpl()); greetingProxy.sayHello("Jack"); } } 这样写没错,但是有个问题,XxxProxy 这样的类会越来越多,如何才能将这些代理类尽可能减少呢?最好只有一个代理类。 这时我们就需要使用 JDK 提供的动态代理了。 3. JDK 动态代理 publicclassJDKDynamicProxyimplementsInvocationHandler{ privateObjecttarget; publicJDKDynamicProxy(Objecttarget){ this.target=target; } @SuppressWarnings("unchecked") public<T>TgetProxy(){ return(T)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } @Override publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ before(); Objectresult=method.invoke(target,args); after(); returnresult; } privatevoidbefore(){ System.out.println("Before"); } privatevoidafter(){ System.out.println("After"); } } 客户端是这样调用的: publicclassClient{ publicstaticvoidmain(String[]args){ Greetinggreeting=newJDKDynamicProxy(newGreetingImpl()).getProxy(); greeting.sayHello("Jack"); } } 这样所有的代理类都合并到动态代理类中了,但这样做仍然存在一个问题:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。有什么方法可以解决呢? 4. CGLib 动态代理 我们使用开源的 CGLib 类库可以代理没有接口的类,这样就弥补了 JDK 的不足。CGLib 动态代理类是这样玩的: publicclassCGLibDynamicProxyimplementsMethodInterceptor{ privatestaticCGLibDynamicProxyinstance=newCGLibDynamicProxy(); privateCGLibDynamicProxy(){ } publicstaticCGLibDynamicProxygetInstance(){ returninstance; } @SuppressWarnings("unchecked") public<T>TgetProxy(Class<T>cls){ return(T)Enhancer.create(cls,this); } @Override publicObjectintercept(Objecttarget,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{ before(); Objectresult=proxy.invokeSuper(target,args); after(); returnresult; } privatevoidbefore(){ System.out.println("Before"); } privatevoidafter(){ System.out.println("After"); } } 以上代码中了 Singleton 模式,那么客户端调用也更加轻松了: publicclassClient{ publicstaticvoidmain(String[]args){ Greetinggreeting=CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class); greeting.sayHello("Jack"); } } 到此为止,我们能做的都做了,问题似乎全部都解决了。但事情总不会那么完美,而我们一定要追求完美! 老罗搞出了一个 AOP 框架,能否做到完美而优雅呢?请大家继续往下看吧! 5.Spring AOP:前置增强、后置增强、环绕增强(编程式) 在 Spring AOP 的世界里,与 AOP 相关的术语实在太多,往往也是我们的“拦路虎”,不管是看那本书或是技术文档,在开头都要将这些术语逐个灌输给读者。我想这完全是在吓唬人了,其实没那么复杂的,大家放轻松一点。 我们上面例子中提到的 before() 方法,在 Spring AOP 里就叫 Before Advice(前置增强)。有些人将 Advice 直译为“通知”,我想这是不太合适的,因为它根本就没有“通知”的含义,而是对原有代码功能的一种“增强”。再说,CGLib 中也有一个Enhancer 类,它就是一个增强类。 此外,像 after() 这样的方法就叫 After Advice(后置增强),因为它放在后面来增强代码的功能。 如果能把 before() 与 after() 合并在一起,那就叫 Around Advice(环绕增强),就像汉堡一样,中间夹一根火腿。 这三个概念是不是轻松地理解了呢?如果是,那就继续吧! 我们下面要做的就是去实现这些所谓的“增强类”,让他们横切到代码中,而不是将这些写死在代码中。 先来一个前置增强类吧: publicclassGreetingBeforeAdviceimplementsMethodBeforeAdvice{ @Override publicvoidbefore(Methodmethod,Object[]args,Objecttarget)throwsThrowable{ System.out.println("Before"); } } 注意:这个类实现了org.springframework.aop.MethodBeforeAdvice 接口,我们将需要增强的代码放入其中。 再来一个后置增强类吧: publicclassGreetingAfterAdviceimplementsAfterReturningAdvice{ @Override publicvoidafterReturning(Objectresult,Methodmethod,Object[]args,Objecttarget)throwsThrowable{ System.out.println("After"); } } 类似地,这个类实现了org.springframework.aop.AfterReturningAdvice 接口。 最后用一个客户端来把它们集成起来,看看如何调用吧: publicclassClient{ publicstaticvoidmain(String[]args){ ProxyFactoryproxyFactory=newProxyFactory();//创建代理工厂 proxyFactory.setTarget(newGreetingImpl());//射入目标类对象 proxyFactory.addAdvice(newGreetingBeforeAdvice());//添加前置增强 proxyFactory.addAdvice(newGreetingAfterAdvice());//添加后置增强 Greetinggreeting=(Greeting)proxyFactory.getProxy();//从代理工厂中获取代理 greeting.sayHello("Jack");//调用代理的方法 } } 请仔细阅读以上代码及其注释,您会发现,其实 Spring AOP 还是挺简单的,对吗? 当然,我们完全可以只定义一个增强类,让它同时实现MethodBeforeAdvice 与AfterReturningAdvice 这两个接口,如下: publicclassGreetingBeforeAndAfterAdviceimplementsMethodBeforeAdvice,AfterReturningAdvice{ @Override publicvoidbefore(Methodmethod,Object[]args,Objecttarget)throwsThrowable{ System.out.println("Before"); } @Override publicvoidafterReturning(Objectresult,Methodmethod,Object[]args,Objecttarget)throwsThrowable{ System.out.println("After"); } } 这样我们只需要使用一行代码,同时就可以添加前置与后置增强: proxyFactory.addAdvice(newGreetingBeforeAndAfterAdvice()); 刚才有提到“环绕增强”,其实这个东西可以把“前置增强”与“后置增强”的功能给合并起来,无需让我们同时实现以上两个接口。 publicclassGreetingAroundAdviceimplementsMethodInterceptor{ @Override publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{ before(); Objectresult=invocation.proceed(); after(); returnresult; } privatevoidbefore(){ System.out.println("Before"); } privatevoidafter(){ System.out.println("After"); } } 环绕增强类需要实现 org.aopalliance.intercept.MethodInterceptor接口。注意,这个接口不是 Spring 提供的,它是 AOP 联盟(一个很牛逼的联盟)写的,Spring 只是借用了它。 在客户端中同样也需要将该增强类的对象添加到代理工厂中: proxyFactory.addAdvice(newGreetingAroundAdvice()); 好了,这就是 Spring AOP 的基本用法,但这只是“编程式”而已。Spring AOP 如果只是这样,那就太傻逼了,它曾经也是一度宣传用Spring 配置文件的方式来定义 Bean 对象,把代码中的 new 操作全部解脱出来。 6. Spring AOP:前置增强、后置增强、环绕增强(声明式) 先看 Spring 配置文件是如何写的吧: <?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--扫描指定包(将@Component注解的类自动定义为SpringBean)--> <context:component-scanbase-package="aop.demo"/> <!--配置一个代理--> <beanid="greetingProxy"class="org.springframework.aop.framework.ProxyFactoryBean"> <propertyname="interfaces"value="aop.Greeting"/><!--需要代理的接口--> <propertyname="target"ref="greetingImpl"/><!--接口实现类--> <propertyname="interceptorNames"><!--拦截器名称(也就是增强类名称,SpringBean的id)--> <list> <value>greetingAroundAdvice</value> </list> </property> </bean> </beans> 一定要阅读以上代码的注释,其实使用 ProxyFactoryBean 就可以取代前面的 ProxyFactory,其实它们俩就一回事儿。我认为interceptorNames 应该改名为 adviceNames 或许会更容易让人理解,不就是往这个属性里面添加增强类吗? 此外,如果只有一个增强类,可以使用以下方法来简化: ... <beanid="greetingProxy"class="org.springframework.aop.framework.ProxyFactoryBean"> <propertyname="interfaces"value="aop.Greeting"/> <propertyname="target"ref="greetingImpl"/> <propertyname="interceptorNames"value="greetingAroundAdvice"/><!--注意这行配置--> </bean> ... 还需要注意的是,这里使用了 Spring 2.5+ 的特性“Bean 扫描”,这样我们就无需在 Spring配置文件里不断地定义 <bean id="xxx" class="xxx"/> 了,从而解脱了我们的双手。 看看这是有多么的简单: @Component publicclassGreetingImplimplementsGreeting{ ... } @Component publicclassGreetingAroundAdviceimplementsMethodInterceptor{ ... } 最后看看客户端吧: publicclassClient{ publicstaticvoidmain(String[]args){ ApplicationContextcontext=newClassPathXmlApplicationContext("aop/demo/spring.xml");//获取SpringContext Greetinggreeting=(Greeting)context.getBean("greetingProxy");//从Context中根据id获取Bean对象(其实就是一个代理) greeting.sayHello("Jack");//调用代理的方法 } } 代码量确实少了,我们将配置性的代码放入配置文件,这样也有助于后期维护。更重要的是,代码只关注于业务逻辑,而将配置放入文件中。这是一条最佳实践! 除了上面提到的那三类增强以外,其实还有两类增强也需要了解一下,关键的时候您要能想得到它们才行。 7.Spring AOP:抛出增强 程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是Throws Advice(抛出增强),它确实很强,不信你就继续往下看: @Component publicclassGreetingImplimplementsGreeting{ @Override publicvoidsayHello(Stringname){ System.out.println("Hello!"+name); thrownewRuntimeException("Error");//故意抛出一个异常,看看异常信息能否被拦截到 } } 下面是抛出增强类的代码: @Component publicclassGreetingThrowAdviceimplementsThrowsAdvice{ publicvoidafterThrowing(Methodmethod,Object[]args,Objecttarget,Exceptione){ System.out.println("----------ThrowException----------"); System.out.println("TargetClass:"+target.getClass().getName()); System.out.println("MethodName:"+method.getName()); System.out.println("ExceptionMessage:"+e.getMessage()); System.out.println("-------------------------------------"); } } 抛出增强类需要实现org.springframework.aop.ThrowsAdvice 接口,在接口方法中可获取方法、参数、目标对象、异常对象等信息。我们可以把这些信息统一写入到日志中,当然也可以持久化到数据库中。 这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的! 8. Spring AOP:引入增强 以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。建议您一开始千万不要去看《Spring Reference》,否则您一定会后悔的。因为当您看了以下的代码示例后,一定会彻底明白什么才是引入增强。 定义了一个新接口 Apology(道歉): publicinterfaceApology{ voidsaySorry(Stringname); } 但我不想在代码中让GreetingImpl 直接去实现这个接口,我想在程序运行的时候动态地实现它。因为假如我实现了这个接口,那么我就一定要改写 GreetingImpl 这个类,关键是我不想改它,或许在真实场景中,这个类有1万行代码,我实在是不敢动了。于是,我需要借助 Spring 的引入增强。这个有点意思了! @Component publicclassGreetingIntroAdviceextendsDelegatingIntroductionInterceptorimplementsApology{ @Override publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{ returnsuper.invoke(invocation); } @Override publicvoidsaySorry(Stringname){ System.out.println("Sorry!"+name); } } 以上定义了一个引入增强类,扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor 类,同时也实现了新定义的Apology 接口。在类中首先覆盖了父类的 invoke() 方法,然后实现了 Apology 接口的方法。我就是想用这个增强类去丰富GreetingImpl 类的功能,那么这个 GreetingImpl类无需直接实现 Apology 接口,就可以在程序运行的时候调用Apology 接口的方法了。这简直是太神奇的! 看看是如何配置的吧: <?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scanbase-package="aop.demo"/> <beanid="greetingProxy"class="org.springframework.aop.framework.ProxyFactoryBean"> <propertyname="interfaces"value="aop.demo.Apology"/><!--需要动态实现的接口--> <propertyname="target"ref="greetingImpl"/><!--目标类--> <propertyname="interceptorNames"value="greetingIntroAdvice"/><!--引入增强--> <propertyname="proxyTargetClass"value="true"/><!--代理目标类(默认为false,代理接口)--> </bean> </beans> 需要注意proxyTargetClass 属性,它表明是否代理目标类,默认为 false,也就是代理接口了,此时Spring 就用 JDK 动态代理。如果为 true,那么 Spring 就用 CGLib 动态代理。这简直就是太方便了!Spring 封装了这一切,让程序员不在关心那么多的细节。我们要向老罗同志致敬,您是我们心中永远的 idol! 当您看完下面的客户端代码,一定会完全明白以上的这一切: publicclassClient{ publicstaticvoidmain(String[]args){ ApplicationContextcontext=newClassPathXmlApplicationContext("aop/demo/spring.xml"); GreetingImplgreetingImpl=(GreetingImpl)context.getBean("greetingProxy");//注意:转型为目标类,而并非它的Greeting接口 greetingImpl.sayHello("Jack"); Apologyapology=(Apology)greetingImpl;//将目标类强制向上转型为Apology接口(这是引入增强给我们带来的特性,也就是“接口动态实现”功能) apology.saySorry("Jack"); } } 没想到 saySorry() 方法原来是可以被 greetingImpl 对象来直接调用的,只需将其强制转换为该接口即可。 我们再次感谢 Spring AOP,感谢老罗给我们提供了这么强大的特性! 其实,Spring AOP 还有很多精彩的地方,下一篇将介绍更多更有价值的 AOP 技术,让大家得到更多的收获。 未完,待续... AOP 那点儿事(续集) 源码下载

优秀的个人博客,低调大师

每日一博 | 探索 Design Token

前言 近几年中台化业务兴起,各个业务团队为了能快速响应业务需求,提升研发效能,引入「设计系统」来解决模块化和规模化的问题。 回顾一下什么是设计系统,设计系统是由设计语言和模式库构成,在设计原则的指导下,通过统一的协作语言和科学的管理方法组织起来,并创建体验一致的用户界面的系统。 设计语言:设计系统的基础,与品牌识别和情感有关,包含颜色、字体、图标等基础设计原子; 模式库:一系列由设计原子组成的可复用的组件、模板等; 作为「设计系统」执行方的设计师与前端工程师,日常工作分别是在两个差异化较大的工作流中进行的。常规流程是设计师在设计工具(Sketch、Figma)中完成页面设计后,前端再参照绘制好的原型稿和标注稿,在代码环境中还原视觉稿 UI/UX。但在这个过程中时常会遇到以下问题: 前端如何高效的获取上游设计的更新? 视觉稿中可复用的设计系统原子,如何准确地传达给下游? 视觉还原工作还能提效吗? 前端如何高效的获取上游设计的更新? 设想这么一个场景,在产研交付的过程中,设计师在视觉稿中做的每次修改,都希望能快速响应到最终的产品中,尽可能做到敏捷。而实际工作中,设计上游变更后会告知前端(存在少数变更不告知的情况),前端再打开包含标注信息的设计工具和代码编辑工具完成需求修改。在这个场景中,设计师会通过口头或文字罗列视觉变更点,存在一定的沟通成本和信息丢失问题。在最终产品交付上线前,还会经过一轮「设计走查」环节(敏捷开发中经常忽略的一环),可能又会产生新的设计上游变更,如此反复。 视觉稿中可复用的设计系统原子,如何传达给下游? 一个成熟的项目,往往会有自己的设计系统,设计原子作为系统中可复用程度较高的模块,已深度整合到设计工作流中。但由于设计和前端领域的概念不互通,导致可复用的信息不能有效传达。虽然设计师会整理包含字号层级,品牌色板,卡片阴影等信息的设计规范文件,但这些信息往往不能在视觉交付稿中很好的展现。要理解视觉稿中的设计原子,前端需要了解设计工具中的概念,如 Sketch 的图层样式、Symbols,Figma 的 Variants 等等。设计师是最了解页面样式复用逻辑的,但真正实现页面样式的却是前端工程师,这不可避免产生视觉还原误差。 视觉还原工作还能提效吗? 设计师在绘制好页面视觉稿后,前端需要将视觉稿还原成可交互的页面,按古早的分工,这里需要三种角色参与,分别是视觉设计师、页面重构工程师(负责 HTML + CSS 等 UI/UX 逻辑)和前端工程师(负责数据渲染等逻辑)。本质上,视觉还原就是将设计工具中的视觉稿描述转换为 Web 能理解的数据描述,即 HTML + CSS。而这一块的信息转换工作正是团队近一年来尝试攻克的点,团队立项的「Deco 智能代码项目」通过设计工具插件从视觉稿原始信息中提取结构化的数据描述(D2C Schema),然后结合规则系统、计算机视觉、智能布局、深度学习等技术对 D2C Schema 进行处理,转换为布局合理且语义化的 D2C Schema JSON 数据,最后再借助 DSL 解析器转换为多端代码。 智能代码解决了视觉还原工作整体的效能问题,但具体怎么让设计系统完美衔接研发工作流,降低设计研发协作成本,提升最终产出代码的可维护度,正是 DesignToken 可以发挥作用的地方。 什么是 Design Token? Design Token 是一种以平台无关的方式来表达设计决策的方法,以便在不同领域、工具和技术之间共享。在设计系统中的, Design Token 代表了构成视觉风格的,可复用的设计属性,例如颜色、间距、字体大小等等。 Token 被赋予一个特定的名称(color.brand),该名称对应于某个设计决策定义的值(#3271FE)。 但有别于设计变量(Design Variables), Design Token 是一个具有平台无关性的抽象层,该抽象层的命名约定为设计属性创建了一种通用语言,可支持跨应用,跨平台,跨框架使用。 使用 Design Token 的工作流程图如下所示: Design Token 相关术语 按照 W3C Design Token 兴趣组最新拟定的草案,里面提到以下术语: 1. 令牌(Token) 与 Token 关联的信息,至少是一个键值对,如: color-text-primary: #000000; font-size-heading-level-1: 44px; 2. 设计工具(Design Tool) Figma, Sketch, AdobeXD 等。 3. 翻译工具(Translation Tool) 翻译工具是将 Design Token 从一种格式转换为另一种格式的工具,如:JSON to CSS Theo, Salesforce Style Dictionary, by Amazon Diez, by Haiku Specify 4. 分类(Type) 应用于 Token 的预定义分类,如设计系统中的样式属性分类: Color Size Typeface Border Style 示例如下: { "color": { "acid green": { "value": "#00ff66" }, "hot pink": { "value": "#dd22cc" } }, "typeface": { "primary": { "value": "Comic Sans MS" }, "secondary": { "value": "Times New Roman" } } } 5. 集合(Groups) 指代特定类别的 Tokens 集合,例如 Brand, Component 等等 { "brand": { "color": { "acid green": { "value": "#00ff66" }, "hot pink": { "value": "#dd22cc" } }, "typeface": { "primary": { "value": "Comic Sans MS" }, "secondary": { "value": "Times New Roman" } } } } 通过 Style Dictionary 翻译工具,可将上述标记文件转换为以下 Sass 变量: $brand-color-acid-green: #00ff66; $brand-color-hot-pink: #dd22cc; $brand-typeface-primary: 'Comic Sans MS'; $brand-typeface-secondary: 'Times New Roman'; 6. 别名 / 引用(Alias / References) Token 可以是别的 Tokens 的别名,而不是明确的值,这样带来的好处是: 有利于表达设计决策; 消除重复的 Token Values; 7. 复合(Composite) 前面提到,Token 对应的值至少是一个键值对,也可以由多个键值对组成的复合类型。一个典型的例子是 Sketch 设计工具中的文本样式和图层样式: 文本样式:由表达文本样式的字体名称、文字粗细、颜色组合; 图层样式:由边框样式、颜色、容器背景色和阴影组合; Design Token 的优势 Design Token 作为设计规范在工程化中的承接方式,为设计系统的迭代、维护和落地提供了很大的帮助。另外 Design Token 在设计师和工程师之间起到了协议规范的作用,而 Token 正是这套协议中的编码语言。 单一事实来源:设计和研发双方如果严格按协议内容使用进行设计和编码,是能够让设计系统拥有单一的事实来源,即最终的产品视觉呈现以上游设计师输出的 Token 为准。同时也提供了一种用于记录和跟踪设计决策变更的存储库,也就是说上游设计师的视觉变更是可追溯的。 产品一致性:当使用 Tokens 进行设计和实现时,样式变更可以更快地在整个产品套件中得到一致化的更新。 上下文驱动:由于 Tokens 是可复用,可自由组合的,因此它们可以根据上下文和主题进行局部范围内的更新。例如页面背景色可根据系统主题进行颜色取值,如下图: Design Token 实践 在「Deco 智能代码」项目中,研发可以给项目关联特定的设计系统(DSM),如「京东 APP 设计系统」,其中包含文本样式,图层样式,调色板等设计原子。Deco 在做布局样式还原时,会优先使用设计系统中已有的 Design Tokens 并进行替换,并会标记设计系统中暂未录入的设计变量,例如不在设计规范中的色值,字体大小等。 Design Token 的引入除了可以给布局还原的代码做样式精简外,还能为视觉走查提供便利。因为 D2C(DesignToCode)的技术方案中,产出的代码视觉还原度可以达到将近 100%,设计师可以更多地关注自身视觉稿的设计系统覆盖度,借助上述流程中被标记的「设计变量」列表,可以十分方便的确认设计误差,例如设计规范中规定的背景色为 $brand-color-bg: F7F7F7,但代码还原后的背景色未被替换为 $brand-color-bg,而是 #F6F6F6。 总结 Design Token 作为一种比较新型的设计决策表达方案,目前主流的设计工具如 Figma、Sketch、AdobeXD 已支持给设计属性做变量标记或引用共享值,再借助第三方的翻译工具如 Theo,将 Tokens 转换为开发人员直接使用的特定平台的代码。 虽说 Design Token 应用的并不广泛,但随着公司业务的扩张,必定会需要一套完善的设计系统,以一致的设计语言和视觉风格,帮助用户形成连续、统一的体验认知。到时候,Design Token 作为设计系统落地的承接方式,定会得到更广泛的使用。Figma 将前端工程化的思想(如:Variants)带入设计领域,我们未尝不能继续探索 Design Token 的可能性呢。 参考资料 DTCG Glossary Material Design Tokens Building better products with a design token pipeline A guide to design tokens 现代 Web 开发困局

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册