变压器图案

Transformer模式是Java(以及可能仅具有使用场所差异和不变参数类型的其他OO语言)的设计模式,可帮助子类型层次结构内的对象将自己流畅地转换为任何类型的对象。

语境

我一直在关注与Jim Laskey发行的JDK-8203703有关的OpenJDK线程( 9月18-21日11月12-13日, 11月13-30日, 12月3-4日 ),然后我想到了一个主意。 让我回顾一下讨论的相关部分。

String.transform的建议

根据JDK-8203703的提案归结为以下新增内容:

public final class String implements /*...*/ CharSequence {
  // ...
  public <R> R transform(Function<? super String, ? extends R> f) {
    return f.apply(this);
  }
  // ...
}

如您所见,此方法仅自身调用给定的Function即可。 但是,它对于链接实用程序方法非常有用,例如Apache Commons的 StringUtils中的方法:

String result = string
        .toLowerCase()
        .transform(StringUtils::stripAccents)
        .transform(StringUtils::capitalize);

通常,我们必须写:

String result = StringUtils.capitalize(StringUtils.stripAccents(string.toLowerCase()));

考虑CharSequence.transform

在某个时候,艾伦·贝特曼(Alan Bateman) 提出了CharSequence中潜在定义transform 的问题

<R> R transform(Function<? super CharSequence, ? extends R> f)

这将具有能够在任何CharSequence上应用基于CharSequence的实用程序方法(例如StringUtils.isNumeric )的好处,例如:

boolean isNumeric = charSequence
        .transform(s -> StringUtils.defaultIfBlank('0'))
        .transform(StringUtils::isNumeric);

但是,正如RémiForax所指出的 ,此签名的问题在于:

  • 如果它是由String 继承的:大多数实用程序方法都将String作为参数–这样的方法将不起作用(例如StringUtils :: capitalize ),
  • 如果它被String 覆盖 :由于以下原因,无法进行有用的覆盖:
    • Function<? super String, R>

结果, CharSequence.transform的主题已被删除。

问题

总而言之,问题在于能够进行转换

  • 一个CharSequence ,使用Function即需要CharSequenceObject? super CharSequence ),
  • 一个String ,使用接受String或其任何父类型( ? super String )的Function

当我在这里查看这些 限时 ,我意识到我已经看到了这种问题(参见Filterer Pattern )。

因此,这个问题归结为:如何协变地指定Function 界。

Java不支持逆变参数类型 ,并且它的语法也没有提供一种方法来协变( ? extends )指定在单个声明中绑定的逆变( ? super )。 然而,有可能做到这一点在两个分开的宣言,通过中间辅助类型的装置。

假设我们要为泛型Function<? super T, ? extends R> Function<? super T, ? extends R> Function<? super T, ? extends R> ,我们需要:

  • 将上面的Function参数移动到参数为T辅助接口中
  • 将此辅助接口与上限? extends T )一起用作返回类型。

变压器接口

我定义了这样的帮助程序接口(我称之为Transformer ),如下所示:

@FunctionalInterface
interface Transformer<T> {
  <R> R by(Function<? super T, ? extends R> f);
}

可转换的接口

定义了Transformer ,我们可以定义以下称为Transformable基本接口:

interface Transformable {
  Transformer<?> transformed();
}

该接口本身并不能做很多事情,但我将其视为以下方面的规范

  • 子类型实现者 :它提醒他们使用适当的上限覆盖已transformed方法,并加以实现,
  • 子类型用户 :提醒他们可以调用transformed().by(f)

总结起来,这对( TransformerTransformable )让我们替换:

  • obj.transform(function)
  • 使用: obj.transformed().by(function)

样例实施

回到String之前,让我们看看实现这两个接口有多么容易:

class Sample implements Transformable {

  @Override
  public Transformer<Sample> transformed() {
    return this::transform; // method reference
  }

  private <R> R transform(Function<? super Sample, ? extends R> f) {
    return f.apply(this);
  }
}

如您所见,所需要的只是对transform方法引用

transform方法被设为私有,因此当子类型定义自己的(适当地, 下界transform时,它们之间不会发生冲突。

上下文中的解决方案

上下文中的实现

它如何应用于CharSequenceString ? 首先,我们将CharSequence扩展为Transformable

public interface CharSequence extends Transformable {
  // ...
  @Override
  Transformer<? extends CharSequence> transformed();
  // ...
}

然后,我们transformedString实现transformed ,返回对public transform方法的方法引用(已在JDK 12中添加 ):

public final class String implements /*...*/ CharSequence {
  // ...
  @Override
  public Transformer<String> transformed() {
    return this::transform;
  }
  // ...
}

请注意,我们对transformed的返回类型进行了协变更改: Transformer<? extends CharSequence> Transformer<? extends CharSequence>Transformer<String>

相容性风险

我认为添加CharSequence.transformed的兼容性风险很小。 仅对于那些已经具有无参数transformed方法的CharSequence子类,它可能会破坏向后兼容性(这似乎不太可能)。

上下文中的用法

对于使用String不会改变,因为有呼吁没有一点transformed().by()transform()

但是,通用CharSequence的用法将需要诉诸transformed().by()因为它可能有许多实现,因此transform方法必须是private

boolean isNumeric = charSequence
        .transformed().by(s -> StringUtils.defaultIfBlank('0'))
        .transformed().by(StringUtils::isNumeric);

性能

如果您不熟悉JVM (最常表示HotSpot )及其JIT编译器的工作方式,那么您可能想知道这种明显的额外对象创建( Transformer in transformed )是否不会影响性能。

幸运的是,由于有了转义分析 *标量替换 ,该对象从未在堆上分配。 答案是:不会,不会。

* 此Wikipedia条目包含错误的陈述:“ 因此,编译器可以安全地在堆栈上分配这两个对象。 ”正如 AlekseyShipilёv解释的那样 ,Java不会在堆栈上分配整个对象。

基准测试

如果您需要证明,这里有一些基准(使用AlekseyShipilёv出色的JMH基准线束 )。 因为我不能(容易),添加必要的方法,以String ,我创建了一个简单的包装过String ,并实现了在它之上的标杆。

基准测试toLowerCase()操作:

  • 在两个字符串上:
    1. "no change" (无操作)
    2. "Some Change"
  • 使用三种通话类型:
    1. 直接(基准)
    2. transform()
    3. transformed().by()

您可以在GitHub gist中找到此基准测试的完整源代码。

结果如下(在Oracle JDK 8上运行,花费了50分钟):

Benchmark                            (string)  Mode  Cnt   Score   Error  Units

TransformerBenchmark.baseline       no change  avgt   25  22,215 ± 0,054  ns/op
TransformerBenchmark.transform      no change  avgt   25  22,540 ± 0,039  ns/op
TransformerBenchmark.transformed    no change  avgt   25  22,565 ± 0,059  ns/op

TransformerBenchmark.baseline     Some Change  avgt   25  63,122 ± 0,541  ns/op
TransformerBenchmark.transform    Some Change  avgt   25  63,405 ± 0,196  ns/op
TransformerBenchmark.transformed  Some Change  avgt   25  62,930 ± 0,209  ns/op

如您所见,对于这两个字符串,这三种调用类型之间没有性能差异。

摘要

我意识到, Transformable可能太“奢侈”了,无法真正将其纳入JDK。 实际上,即使仅由CharSequenceString返回的Transformer也不值得。 这是因为对CharSequence的一元运算似乎并不常见(例如StringUtils仅包含少数几个)。

但是,我发现“ Transformer和“ Transformable的基本概念很诱人。 因此,我希望您喜欢阅读,并在某些情况下会发现它很有用

翻译自: https://www.javacodegeeks.com/2019/02/transformer-pattern.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值