代码之家  ›  专栏  ›  技术社区  ›  pauljwilliams

如何避免耦合两个方法,这两个方法现在有一个类似的实现,但在未来可能会改变?

  •  2
  • pauljwilliams  · 技术社区  · 14 年前

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

    样品:

    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){...}
    }
    

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

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

    我的担心有两个方面:第一,不能保证这两种方法总是依赖于第三种方法,因此任何解决方案都应该从它们的POV中透明;第二,显而易见的解决方案(在某个expensivemethod中的cahcing)可能在不需要长期保存的结果的空间方面非常昂贵。

    5 回复  |  直到 14 年前
        1
  •  3
  •   Stephen C    14 年前

    我考虑过如何缓存 someExpensiveMethod ,但这似乎是浪费,因为对象往往会进入,被处理,然后被丢弃。

    我不认为那是浪费。这就是缓存的工作原理。您将传入的对象与最近处理的对象进行比较,当您得到“命中”时,可以避免调用 费用分摊法 .

    缓存是否真正适用于您的应用程序将取决于许多因素,如:

    • 可以保存在缓存中的对象/结果对数,
    • “命中”的概率,
    • 执行缓存探测的平均成本(在“命中”和“未命中”情况下),
    • 平均通话费用 费用分摊法
    • 维护缓存的直接成本;例如,如果您使用LRU或其他一些策略来删除不起作用的缓存条目,以及
    • 维护缓存的间接成本。

    (最后一点很难预测/测量,但它包括表示缓存结构所需的额外内存,GC必须处理缓存及其内容“可访问”这一事实,以及与弱引用相关联的GC开销。。。假设你使用它们。)

    最终,缓存解决方案的成功(或其他)取决于系统的 平均的 行为 现实的 工作量。一些缓存的结果再也不会被使用的事实与此并不相关。

    它看起来也很笨拙-方法需要知道缓存,或者我需要缓存结果 费用分摊法 .

    再说一次,我也不是“笨重”的。这是实现缓存的方法。

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

    再说一遍,我看不出你论点的逻辑。如果一天处理数百万个对象,而你保留(比方说)最后5分钟的价值,那就只有数万个要缓存的对象。这不是什么“坏消息”。

    如果你 真正地 每天要处理数百万个物体,然后:

    • 费用分摊法 不可能那么贵。。。除非你有 任何一个 高效的缓存和大量内存, 大量的处理器, 或者两者兼而有之 ,
    • 你对优雅(不整洁)和避免耦合的关注 必须 在设计应用程序以便它能够跟上的问题上处于次要地位,并且
    • 可能需要在多处理器上运行,因此需要处理缓存 可以 成为并发瓶颈。
        2
  •  0
  •   Jeen Broekstra    14 年前

    您总是调用流程方法吗(我的意思是,您从来没有直接调用get…Fact方法吗)?如果是这样,那么您肯定知道getFirstFact总是在getSecondFact之前调用。

    然后,您可以使用私有字段在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){...}
    }
    
        3
  •  0
  •   Cephalopod    14 年前

    从你的题目我猜你不想

    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 是同步的。)

        4
  •  0
  •   tkr    14 年前

    我会考虑引入一个工厂方法和一个封装预处理的新对象。这样,一旦对象超出范围,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
        }
    }
    
        5
  •  0
  •   nanda    14 年前

    除了斯蒂芬的回答,我建议你看看谷歌番石榴。有一个计算地图的概念,适合你在这里面临的问题。我写了一篇关于这个的文章 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) {
        }
    }