如何避免耦合现在具有类似实现的两种方法,但将来可能会发生变化?

时间:2010-09-27 09:24:50

标签: java

我在一个类中有两个方法,它们目前共享一个非常相似的实现,但是在性能方面实现成本非常高。

样品:

class Example
{
    public void process(Object obj)
    {
        boolean someFact = getFirstFact(obj);
        boolean someOtherFact = getSecondFact(obj);

        //use the two facts somehow
    }

    public boolean getFirstFact(Object obj)
    {
         boolean data = someExpensiveMethod(obj);
         //return some value derived from data
    }

    public boolean getSecondFact(Object obj)
    {
         boolean data = someExpensiveMethod(obj);
         //return some other value derived from data
    }

    public boolean someExpensiveMethod(Object obj){...}
}

我曾想过以某种方式缓存someExpensiveMethod的结果,但这看起来很浪费,因为对象倾向于进入,被处理然后被丢弃。它似乎很笨 - 方法需要知道缓存,或者我需要在ssomeExpensiveMethod中缓存结果。

即使是短期缓存也可能是坏消息,因为每天都会有数百万个对象被处理。

我的担忧是双重的 - 首先,不能保证这两种方法总是依赖于第三种方法,所以任何解决方案都应该从他们的POV透明,其次,明显的解决方案(在某些昂贵的方法中)可能非常在空间方面成本高昂,不需要长期保存。

5 个答案:

答案 0 :(得分:3)

  

我曾想过以某种方式缓存someExpensiveMethod的结果,但这看起来很浪费,因为对象倾向于进入,被处理然后被丢弃。

我不明白这是多么浪费。这基本上就是缓存的工作方式。您将进入的对象与您最近处理的对象进行比较,当您获得“点击”时,可以避免调用someExpensiveMethod的费用。

缓存实际上是否适用于您的应用程序取决于许多因素,如:

  • 您可以保留在缓存中的对象/结果对的数量,
  • “击中”的概率,
  • 执行缓存探测的平均成本(在“点击”和“未命中”情况下),
  • 调用someExpensiveMethod
  • 的平均费用
  • 维护缓存的直接成本;例如如果您使用LRU或其他策略来摆脱没有帮助的缓存条目,
  • 维护缓存的间接成本。

(最后一点难以预测/测量,但它包含了表示缓存结构所需的额外内存,GC必须采取的工作来处理缓存及其内容“可达”的事实,和弱引用相关的GC开销......假设你使用它们。)

最终,缓存解决方案的成功(或其他方面)将根据系统对实际工作负载的平均行为进行判断。一些缓存结果从未再次使用的事实并不真正相关。

  

它似乎很笨 - 方法需要了解缓存,或者我需要在someExpensiveMethod中缓存结果。

同样,IMO不是“笨重”的。这是您实现缓存的方式。

  

即使是短期缓存也可能是坏消息,因为每天都会有数百万个对象被处理。

同样,我没有看到你论证的逻辑。如果每天处理数百万个对象并保持(比方说)最后5分钟的价值,那么只需要数万个对象进行缓存。这不是“坏消息”。

如果真的每天处理“数百万”对象,那么:

  • someExpensiveMethod不能那么昂贵......除非你高效缓存和大量内存大量处理器, 或两者
  • 你对优雅(不强烈)的担忧和避免耦合必须成为设计应用程序的问题的次要问题,以便它能够跟上,
  • 您可能需要在多处理器上运行,因此您需要处理缓存可能成为并发瓶颈这一事实。

答案 1 :(得分:0)

你总是在调用流程方法(我的意思是,你是不是直接调用get ... Fact方法)?如果是这种情况,那么您肯定知道在getSecondFact之前总是调用getFirstFact。

然后,您可以使用私有字段在getFirstFact方法中缓存someExpensiveMethod的布尔输出,并在getSecondFact方法中重用该值:

class Example
{
    private boolean _expensiveMethodOutput;

    public void process(Object obj)
    {
        boolean someFact = getFirstFact(obj);
        boolean someOtherFact = getSecondFact(obj);

        //use the two facts somehow
    }

    private boolean getFirstFact(Object obj)
    {
         _expensiveMethodOutput = someExpensiveMethod(obj);
         //return some value derived from data
    }

    private boolean getSecondFact(Object obj)
    {
         boolean data = _expensiveMethodOutput;
         //return some other value derived from data
    }

    private boolean someExpensiveMethod(Object obj){...}
}

答案 2 :(得分:0)

从你的问题标题我想你不想做

class Example
{
    public void process(Object obj)
    {
        boolean expensiveResult = someExpensiveMethod(obj);
        boolean someFact = getFirstFact(expensiveResult);
        boolean someOtherFact = getSecondFact(expensiveResult);

        //use the two facts somehow
    }
    ...

因为这意味着在更改其中一种方法时,您无法再访问obj。此外,您希望尽可能避免执行昂贵的方法。一个简单的解决方案是

private Object lastParam = null;
private boolean lastResult = false;
public boolean someExpensiveMethod(Object obj){
    if (obj == lastParam) return lastResult;
    lastResult = actualExpensiveMethod(obj);
    lastParam = obj;
    return lastResult ;
}

当然这不适用于多线程。 (至少确保process已同步。)

答案 3 :(得分:0)

我会考虑引入一个工厂方法和一个封装预处理的新对象。这样,只要对象超出范围,jvm就可以丢弃预处理的数据。

class PreprocessedObject {
    private ... data;

    public static PreprocessedObject  create(Object obj) {
        PreprocessedObject pObj = new PreprocessedObject();
        // do expensive stuff
        pObj.data = ...
        return pObj;
    }

    public boolean getFirstFact() {
         //return some value derived from data
    }

    public boolean getSecondFact() {
         //return some other value derived from data
    }
}

答案 4 :(得分:0)

除了Stephen的回答,我建议你看一下Google Guava。有一个计算地图的概念适合您在这里遇到的问题。我写了一篇关于here的文章。

在代码方面,我建议这样做:

class Example {

    private ConcurrentMap<Object, Boolean> cache;

    void initCache() {
        cache = new MapMaker().softValues()
                    .makeComputingMap(new Function<Object, Boolean>() {

            @Override
            public Boolean apply(Object from) {
                return someExpensiveMethod(from);
            }
        });
    }

    public void process(Object obj) {
        boolean someFact = getFirstFact(obj);
        boolean someOtherFact = getSecondFact(obj);

        // use the two facts somehow
    }

    public boolean getFirstFact(Object obj) {
        boolean data = cache.get(obj);
        // return some value derived from data
    }

    public boolean getSecondFact(Object obj) {
        boolean data = cache.get(obj);
        // return some other value derived from data
    }

    public boolean someExpensiveMethod(Object obj) {
    }
}