动态运动基元_通过粘性仙人掌基元进行延迟加载和缓存

动态运动基元

动态运动基元

您显然知道什么是延迟加载,对吗? 而且您毫无疑问知道缓存。 据我所知,Java中没有一种优雅的方法来实现它们中的任何一个。 这是我在Cactoos原语的帮助下为自己找到的。

Matteo Garrone的《 Reality》(2012年)

假设我们需要一个可以加密某些文本的对象。 以一种更加面向对象的方式讲,它将封装文本并成为其加密形式。 这是我们将如何使用它(首先创建测试):

interface Encrypted {
  String asString() throws IOException;
}
Encrypted enc = new EncryptedX("Hello, world!");
System.out.println(enc.asString());

现在,让我们以一种非常原始的方式用一个主要的构造函数来实现它。 加密机制只会在传入数据中的每个字节上加上+1 ,并且会假设加密不会破坏任何内容(一个非常愚蠢的假设,但是对于本示例而言,它将起作用):

class Encrypted1 implements Encrypted {
  private final String text;
  Encrypted1(String txt) {
    this.data = txt;
  }
  @Override
  public String asString() {
    final byte in = this.text.getBytes();
    final byte[] out = new byte[in.length];
    for (int i = 0; i < in.length; ++i) {
      out[i] = (byte) (in[i] + 1);
    }
    return new String(out);
  }
}

到目前为止看起来正确吗? 我测试了它,而且效果很好。 如果输入是"Hello, world!" ,输出将为"Ifmmp-!xpsme\""

接下来,假设我们希望我们的类接受InputStreamString 。 我们想这样称呼它,例如:

Encrypted enc = new Encrypted2(
  new FileInputStream("/tmp/hello.txt")
);
System.out.println(enc.toString());

这是最明显的实现,具有两个主要的构造函数(同样,实现是原始的,但是可以工作):

class Encrypted2 implements Encrypted {
  private final String text;
  Encrypted2(InputStream input) throws IOException {
    ByteArrayOutputStream baos =
      new ByteArrayOutputStream();
    while (true) {
      int one = input.read();
      if (one < 0) {
        break;
      }
      baos.write(one);
    }
    this.data = new String(baos.toByteArray());
  }
  Encrypted2(String txt) {
    this.text = txt;
  }
  // asString() is exactly the same as in Encrypted1
}

从技术上讲,它是可行的,但流读取是在构造函数内部进行的,这是不正确的做法主要构造函数只能执行属性分配,而次要构造函数只能创建新对象。

让我们尝试重构并引入延迟加载:

class Encrypted3 {
  private String text;
  private final InputStream input;
  Encrypted3(InputStream stream) {
    this.text = null;
    this.input = stream;
  }
  Encrypted3(String txt) {
    this.text = txt;
    this.input = null;
  }
  @Override
  public String asString() throws IOException {
    if (this.text == null) {
      ByteArrayOutputStream baos =
        new ByteArrayOutputStream();
      while (true) {
        int one = input.read();
        if (one < 0) {
          break;
        }
        baos.write(one);
      }
      this.text = new String(baos.toByteArray());
    }
    final byte in = this.text.getBytes();
    final byte[] out = new byte[in.length];
    for (int i = 0; i < in.length; ++i) {
      out[i] = (byte) (in[i] + 1);
    }
    return new String(out);
  }
}

效果很好,但看起来很丑。 最丑陋的部分当然是这两行:

this.text = null;
this.input = null;

它们使对象可变,并且使用NULL 。 丑陋,相信我。 不幸的是,延迟加载和NULL引用总是在经典示例并存。 但是,有一种更好的方法来实现它。 让我们重构类,这次使用Cactoos的Scalar

class Encrypted4 implements Encrypted {
  private final IoCheckedScalar<String> text;
  Encrypted4(InputStream stream) {
    this(
      () -> {
        ByteArrayOutputStream baos =
          new ByteArrayOutputStream();
        while (true) {
          int one = stream.read();
          if (one < 0) {
            break;
          }
          baos.write(one);
        }
        return new String(baos.toByteArray());
      }
    );
  }
  Encrypted4(String txt) {
    this(() -> txt);
  }
  Encrypted4(Scalar<String> source) {
    this.text = new IoCheckedScalar<>(source);
  }
  @Override
  public String asString() throws IOException {
    final byte[] in = this.text.value().getBytes();
    final byte[] out = new byte[in.length];
    for (int i = 0; i < in.length; ++i) {
      out[i] = (byte) (in[i] + 1);
    }
    return new String(out);
  }

现在看起来好多了。 首先,只有一个主要构造函数和两个次要构造函数。 其次,对象是不可变的。 第三,还有很多改进的余地:我们可以添加更多的构造函数来接受其他数据源,例如File或byte数组。

简而言之,应该以“惰性”方式加载的属性在对象内部表示为“功能”(Java 8中的lambda表达式)。 在我们触摸该属性之前,不会加载该属性。 一旦我们需要使用它,函数便会执行并得到结果。

但是,此代码存在一个问题。 每当我们调用asString() ,它将读取输入流,这显然是行不通的,因为只有第一次流才会有数据。 在每个后续调用中,流都将为空。 因此,我们需要确保this.text.value()仅执行一次封装的Scalar 。 所有以后的调用都必须返回先前计算的值。 因此,我们需要对其进行缓存。 方法如下:

class Encrypted5 implements Encrypted {
  private final IoCheckedScalar<String> text;
  // same as above in Encrypted4
  Encrypted5(Scalar<String> source) {
    this.data = new IoCheckedScalar<>(
      new StickyScalar<>(source)
    );
  }
  // same as above in Encrypted4

StickyScalar将确保仅对其方法value()的第一次调用将传递给封装的Scalar 。 所有其他呼叫将接收第一个呼叫的结果。

要解决的最后一个问题是关于并发性。 我们上面的代码不是线程安全的。 如果我创建Encrypted5的实例并将其传递给同时调用asString()两个线程,则结果将是不可预测的,这仅仅是因为StickyScalar不是线程安全的。 不过,还有另一个可以帮助我们的原语,称为SyncScalar

class Encrypted5 implements Encrypted {
  private final IoCheckedScalar<String> text;
  // same as above in Encrypted4
  Encrypted5(Scalar<String> source) {
    this.data = new IoCheckedScalar<>(
      new SyncScalar<>(
        new StickyScalar<>(source)
      )
    );
  }
  // same as above in Encrypted4

现在我们很安全,设计优雅。 它包括延迟加载和缓存。

我现在在许多项目中都使用这种方法,而且看起来很方便,清晰且面向对象。

您可能还会发现这些相关的帖子有趣:为什么InputStream设计不正确尝试。 最后。 如果。 不。 空值。 ; 每个私有静态方法都是新类的候选人; 我将如何重新设计equals() ; 对象行为不可配置;

翻译自: https://www.javacodegeeks.com/2017/10/lazy-loading-caching-via-sticky-cactoos-primitives.html

动态运动基元

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值