简单聊聊各种语言的函数扩展
背景
最近有同事反应,我们运营后台下载的 CSV 文件出现错乱的情况。问题的原因是原始数据中有 CSV 中非法的字符,比如说姓名字段,因为是用户填写的,内容有可能包含了 ,
、"
等字符,会导致 CSV 文件内容错乱。
于是我就想用一个简单的方式来解决这个问题。一个简单粗暴的解决方案就是导出时对字符串进行处理,将一些特殊字符替换掉,或者前后用"
包起来。但是这样的话,需要所有下载 CSV 的地方都要改写,会比较麻烦。如果我们可以简单的给 String 增加一个方法(如 String.csv()
)直接就把字符串处理成 CSV 兼容的格式,就会方便很多。我们的运营后台是使用 Scala 语言开发的,所幸的是,Scala 里提供了一个非常强大的功能,可以满足我们的需求,那就是隐式转换。
Scala 的隐式转换
在 Scala 里可以通过 implicit
隐式转换来实现函数扩展。
编译器在碰到类型不匹配或是调用一个不存在的方法的时候,会去搜索符合条件的隐式类型转换,如果找不到合适的隐式转换方法则会报错。
下面是处理 CSV 下载字符串的代码:
trait CsvHelper {
implicit def stringToCsvString(s: String) = new CsvString(s)
}
class CsvString(val s: String){
def csv = s"""${s.replaceAll(",", " ").replaceAll("\"", "'")}"""
}class Controller extends CsvHelper {
def dowload(){
...
",foo,".csv //foo
}
}
在
Controller
中我调用 String.csv
方法,但是 String
没有 csv
方法。这时候编译器就会去找 Controller
中有没有隐式转换的方法,发现在其父类 CsvHelper
中有方法把 String
转换成 CsvString
,而 CsvString
中实现了 csv
方法。所以编译器最终会调用到 CsvString.csv
这个方法。隐式转换是一个很强大,但是也很容易误用的功能。Scala 里隐式转换有一些基本规则:
- 优先规则:如果存在两个或者多个符合条件的隐式转换,如果编译器不能选择一条最优的隐式转换,则提示错误。具体的规则是:当前类中的隐式转换优先级大于父类中的隐式转换;多个隐式转换返回的类型有父子关系的时候,子类优先级大于父类。
- 隐式转换只会隐式的调用一次,编译器不会调用多个隐式方法,不会产生调用链。
- 如果当期代码已经是合法的,不需要隐式转换则不会使用隐式转换。
下面就是一个我们就用
AspectJ
来实现一个动态扩展,用于分页查询后获取数据的总条数。@Aspect
@Component
public class PaginationAspect {
@AfterReturning(
pointcut = "execution(* com.xingren..*.*ByPage(..))",
returning = "result"
)
public void afterByPage(JoinPoint joinPoint, Object result) {
//根据result获取sql信息,再查询总条数封装到result中。
}
}
其中
AfterReturning
注解表明在被注解方法返回后的一些后续动作。pointcut
定义切点的表达式,可以用通配符 *
表示;returning
指定返回的参数名。然后就可以对返回的结果进行处理。这样就可以达到动态的修改原始函数功能。当然除了
AspectJ
也可以使用 CGLib
来代理来实现简单的 AOP。public class FooService {
public Page findByPage(){
return new Page();
}
public Page findPage(){
return new Page();
}
}
@Data
public class Page {
private String sql = "";
private List
创建一个对象
FooService
用来模拟查询分页方法。public class CGLibProxyFactory implements MethodInterceptor {private Object object;
public CGLibProxyFactory(Object object){
this.object = object;
}@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method! do something...");
Object result = methodProxy.invoke(object, objects);
//进行方法判断,是否需要处理
if (method.getName().contains("ByPage")) {
if (result instanceof Page) {
System.out.println("after method! do something...");
((Page) result).setTotal(100);
}
}
return result;
}
}
创建一个代理类实现
MethodInterceptor
接口,手动调用 invoke
方法,用来动态的修改被代理的实现方法。可以在执行之前做一些参数校验,或者一些参数的预处理。也可以获取修改执行的结果,或者干脆不调用 invoke
方法,自定义实现。也可以在调用后做一些后续动作。public class ObjectFactoryUtils {
public static Optional getProxyObject(Class clazz) {
try {
T obj = clazz.newInstance();
CGLibProxyFactory factory = new CGLibProxyFactory(obj);
Enhancer enhancer=new Enhancer();
//利用`Enhancer`来创建被代理类的代理实例
enhancer.setSuperclass(clazz);
//设置目标class
enhancer.setCallback(factory);
//设置回调代理类
return Optional.of((T)enhancer.create());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return Optional.empty();
}
}public static void main(String[] args) {
Optional proxyObject = ObjectFactoryUtils.getProxyObject(FooService.class);
if(proxyObject.isPresent()) {
FooService foo = proxyObject.get();
System.out.println("findByPage:");
System.out.println(foo.findByPage().getTotal());
System.out.println("findPage:");
System.out.println(foo.findPage().getTotal());
}
}
最后打印的输出是:
findByPage:
before method! do something...
after method! do something...
100
findPage:
before method! do something...
0
当然除了 CGLIB 代理也可以使用 Proxy 动态代理,同样的逻辑也可以达到动态的修改原始方法的目的,从而间接的实现函数扩展。不过 Proxy 动态代理是基于接口的代理。
其它语言的函数扩展
其实除了 Scala 的隐式转换和 Java 的动态代理,其他很多语言也能支持各种不同的函数扩展。
Swift 【简单聊聊各种语言的函数扩展】在 Swift 中可以通过关键词
extension
对已有的类进行扩展,可以扩展方法、属性、下标、构造器等等。extension Int {
func times(task: () -> Void) {
for _ in 0..
比如说我给 Int 增加一个 times 方法。即执行任务的次数。就可以如下使用:
2.times({
print("Hello!")
})
上面的代码会执行 2 次打印方法。
Go 在 Go 中可以通过在方法名前面加上一个变量,这个附加的参数会将该函数附加到这种类型上。即给一个方法加上接收器。
func (s string) toUpper() string {
return strings.ToUpper(s)
}"aaaaa".toUpper //输出 AAAAA
Kotlin Kotlin 的函数扩展非常简单,就是定义的时候,函数名写成
接收器
+ .
+ 方法名
就行了。class C {}
fun C.foo() { println("extension") }C().foo() //输出extension
注意当给一个类扩展已有的方法的时候,默认使用的是类自带的成员函数。如下:
class C {
fun foo() { println("member") }
}fun C.foo() { println("extension") }C().foo() //输出member
可以通过函数重载的方式区分成员函数(
fun C.foo(i:Int) { println("extension") }
),在调用的地方显示的区分。JavaScript 在 JavaScript 中也可以很方便的给一个对象扩展函数。写法就是
对象
+ .
+ 函数名
。var date = new Date();
date.format = function() {
return this.toISOString().slice(0, 10);
}
date.format();
//"2017-11-29"
也可以给一个 Object 进行扩展:
Date.prototype.format = function() {
return this.toISOString().slice(0, 10);
}
new Date().format();
//"2017-11-29"
总结
其实了解不同语言对于函数扩展的实现挺有意思的,本文只是粗略的介绍了一下。合理的使用这些语言的扩展,可以帮助我们提高代码质量和工作效率。我们还可以通过函数扩展来对第三方类库进行修改或者扩展,从而更灵活的调用第三方类库。
推荐阅读
- JS中的各种宽高度定义及其应用
- 科学养胃,别被忽悠,其实真的很简单
- opencv|opencv C++模板匹配的简单实现
- 松软可口易消化,无需烤箱超简单,新手麻麻也能轻松成功~
- 中国MES系统软件随工业化成长
- 简单心理2019春A期+32+张荣
- 《算法》-图[有向图]
- android防止连续点击的简单实现(kotlin)
- 机器学习一些简单笔记
- 想聊聊SA,聊聊手帐,也想和你们分享自己