【问题标题】:How to 'wrap' two classes with identical methods?如何用相同的方法“包装”两个类?
【发布时间】:2012-07-14 13:31:16
【问题描述】:

我必须处理两个具有相同方法的类,但它们没有实现相同的接口,也没有扩展相同的超类。我不能/不允许更改此类,并且我不构造此类的实例,我只获取此类的对象。 避免大量代码重复的最佳方法是什么?

类之一:

package faa;

public class SomethingA {

    private String valueOne = null;
    private String valueTwo = null;

    public String getValueOne() { return valueOne; }
    public void setValueOne(String valueOne) { this.valueOne = valueOne; }

    public String getValueTwo() { return valueTwo; }
    public void setValueTwo(String valueTwo) { this.valueTwo = valueTwo; }
}

还有一个……

package foo;

public class SomethingB {

    private String valueOne;
    private String valueTwo;

    public String getValueOne() { return valueOne; }
    public void setValueOne(String valueOne) { this.valueOne = valueOne; }

    public String getValueTwo() { return valueTwo; }
    public void setValueTwo(String valueTwo) { this.valueTwo = valueTwo; }
}

(实际上这些类更大)

我现在唯一的想法是创建一个包装类:

public class SomethingWrapper {

    private SomethingA someA;
    private SomethingB someB;

    public SomethingWrapper(SomethingA someA) {
        //null check..
        this.someA = someA;
    }

    public SomethingWrapper(SomethingB someB) {
        //null check..
        this.someB = someB;
    }

    public String getValueOne() {
        if (this.someA != null) {
            return this.someA.getValueOne();
        } else {
            return this.someB.getValueOne();
        }
    }

    public void setValueOne(String valueOne) {
        if (this.someA != null) {
            this.someA.setValueOne(valueOne);
        } else {
            this.someB.setValueOne(valueOne);
        }
    }

    public String getValueTwo() {
        if (this.someA != null) {
            return this.someA.getValueTwo();
        } else {
            return this.someB.getValueTwo();
        }
    }

    public void setValueTwo(String valueTwo) {
        if (this.someA != null) {
            this.someA.setValueTwo(valueTwo);
        } else {
            this.someB.setValueTwo(valueTwo);
        }
    }
} 

但我对这个解决方案并不满意。有没有更好/更优雅的方法来解决这个问题?

【问题讨论】:

    标签: java oop design-patterns


    【解决方案1】:

    更好的解决方案是创建一个接口来表示两个类的统一接口,然后编写两个实现该接口的类,一个包装 A,另一个包装 B:

    public interface SomethingWrapper {
        public String getValueOne();
        public void setValueOne(String valueOne);
        public String getValueTwo();
        public void setValueTwo(String valueTwo);
    };
    
    public class SomethingAWrapper implements SomethingWrapper {
    
        private SomethingA someA;
    
        public SomethingWrapper(SomethingA someA) {
            this.someA = someA;
        }
    
        public String getValueOne() {
            return this.someA.getValueOne();
        }
    
        public void setValueOne(String valueOne) {
            this.someA.setValueOne(valueOne);
        }
    
        public String getValueTwo() {
            return this.someA.getValueTwo();
        }
    
        public void setValueTwo(String valueTwo) {
            this.someA.setValueTwo(valueTwo);
        }
    };
    

    然后是SomethingBWrapper 的另一个类。

    【讨论】:

    • 听起来已经很乱了。为什么不现在止血?
    • 仍然有很多相同的代码,是的。最好是改变原来的课程,但我们必须接受他的话,他不能接受 OP。我建议首先切换到鸭子类型的语言,这种保持编译器快乐的废话无论如何都闻所未闻。
    • OP 实际上可以通过反射在 Java 中进行鸭式输入。也许这不是一个坏主意。
    • 嗯,好的。这看起来仍然不是一个“不错”的解决方案,但至少在将来会有更多相同的类的情况下进行扩展会更好。
    • @NedBatchelder 我不确定,由于所有蹩脚的检查异常,解决方案变得非常丑陋。如果它真的只有两个类,你的方法可能仍然会赢。如果有更多类,则天平会向反射倾斜。
    【解决方案2】:

    在那里,一个鸭式解决方案。这将接受具有valueOnevalueTwo 属性的任何对象,并且可以轻松扩展为进一步的道具。

    public class Wrapper
    {
      private final Object wrapped;
      private final Map<String, Method> methods = new HashMap<String, Method>();
      public Wrapper(Object w) {
        wrapped = w;
        try {
          final Class<?> c = w.getClass();
          for (String propName : new String[] { "ValueOne", "ValueTwo" }) {
            final String getter = "get" + propName, setter = "set" + propName;
            methods.put(getter, c.getMethod(getter));
            methods.put(setter, c.getMethod(setter, String.class));
          }
        } catch (Exception e) { throw new RuntimeException(e); }
      }
      public String getValueOne() {
        try { return (String)methods.get("getValueOne").invoke(wrapped); }
        catch (Exception e) { throw new RuntimeException(e); }
      }
      public void setValueOne(String v) {
        try { methods.get("setValueOne").invoke(wrapped, v); }
        catch (Exception e) { throw new RuntimeException(e); }
      }
      public String getValueTwo() {
        try { return (String)methods.get("getValueTwo").invoke(wrapped); }
        catch (Exception e) { throw new RuntimeException(e); }
      }
      public void setValueTwo(String v) {
        try { methods.get("setValueTwo").invoke(wrapped, v); }
        catch (Exception e) { throw new RuntimeException(e); }
      }
    }
    

    【讨论】:

    • 感谢这种非常通用的方法。不确定这是否会是我的解决方案,因为 by team 并不是refelction 解决方案的忠实拥护者。总是存在这种性能问题。看起来这只是一个丑陋的问题,没有真正好的解决方案......
    • 这必须每秒至少调用十万次(这是一个非常保守的阈值),性能才会成为问题。
    【解决方案3】:

    您可以使用动态代理在您定义的接口与符合但未实现您的接口的类之间创建“桥梁”。

    一切从界面开始:

    interface Something {
      public String getValueOne();
      public void setValueOne(String valueOne);
      public String getValueTwo();
      public void setValueTwo(String valueTwo);
    }
    

    现在你需要一个InvocationHandler,它只会将调用转发到与调用的接口方法匹配的方法:

    class ForwardInvocationHandler implements InvocationHandler {
      private final Object wrapped;
      public ForwardInvocationHandler(Object wrapped) {
        this.wrapped = wrapped;
      }
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        Method match = wrapped.getClass().getMethod(method.getName(), method.getParameterTypes());
        return match.invoke(wrapped, args);
      }
    }
    

    然后您可以创建您的代理(将其放在工厂中以便于使用):

    SomethingA a = new SomethingA();
    a.setValueOne("Um");
    
    Something s = (Something)Proxy.newProxyInstance(
        Something.class.getClassLoader(), 
        new Class[] { Something.class }, 
        new ForwardInvocationHandler(a));
    
    System.out.println(s.getValueOne()); // prints: Um
    

    另一种选择更简单,但需要您对每个类进行子类化并实现创建的接口,就像这样:

    class SomethingAImpl extends SomethingA implements Something {}
    class SomethingBImpl extends SomethingB implements Something {}
    

    (注意:您还需要创建任何非默认构造函数)

    现在使用子类代替超类,并通过接口引用它们:

    Something o = new SomethingAImpl(); // o can also refer to a SomethingBImpl
    o.setValueOne("Uno");
    System.out.println(o.getValueOne()); // prints: Uno
    

    【讨论】:

      【解决方案4】:

      我认为你原来的包装类是最可行的选择......但是它可以使用反射来完成,你真正的问题是应用程序是一团糟......反射可能不是你正在寻找的方法

      我有另一个建议,这可能会有所帮助:创建一个包装类,它对每种类型的类都有特定的功能......它主要是复制粘贴,但它迫使你使用类型化的东西作为参数

      class X{
          public  int asd() {return 0;}
      }
      class Y{
          public  int asd() {return 1;}
      }
      class H{
          public  int asd(X a){
              return  a.asd();
              }
          public  int asd(Y a){
              return  a.asd();
              }
      }
      

      用法:

      System.out.println("asd"+h.asd(x));
      System.out.println("asd"+h.asd(y));
      

      我想指出一个接口也可以由祖先实现,如果你正在创建这些类 - 但不能修改它的源代码,那么你仍然可以从外部重载它们:

      public  interface II{
          public  int asd();
      }
      class XI extends X implements II{
      }
      class YI extends Y implements II{
      }
      

      用法:

      II  a=new XI();
      System.out.println("asd"+a.asd());
      

      【讨论】:

        【解决方案5】:

        您可能可以利用外观和反射 - 在我看来,它简化了您访问遗留系统的方式并且也是可扩展的!

        class facade{
        
         public static getSomething(Object AorB){
            Class c = AorB.getClass();
            Method m = c.getMethod("getValueOne");
            m.invoke(AorB);
         }
         ...
        
        }
        

        【讨论】:

          【解决方案6】:

          我编写了一个类来封装日志框架 API。可惜太长了,放不进去。

          该程序是http://www.github.com/bradleyross/tutorials 项目的一部分,文档位于http://bradleyross.github.io/tutorials。模块 tutorials-common 中的 bradleyross.library.helpers.ExceptionHelper 类的代码位于 https://github.com/BradleyRoss/tutorials/blob/master/tutorials-common/src/main/java/bradleyross/library/helpers/ExceptionHelper.java

          我的想法是,我可以拥有我想要使异常语句更有用的附加代码,并且我不必为每个日志记录框架重复它们。包装器不是您消除代码重复的地方。代码重复的消除在于不必编写调用包装器和底层类的代码的多个版本。见https://bradleyaross.wordpress.com/2016/05/05/java-logging-frameworks/

          bradleyross.helpers.GenericPrinter 类是另一个包装器,使您能够编写与 PrintStream、PrintWriter 和 StringWriter 类和接口一起使用的代码。

          【讨论】:

            猜你喜欢
            • 2020-03-20
            • 1970-01-01
            • 2014-03-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-21
            • 1970-01-01
            • 2013-10-27
            相关资源
            最近更新 更多