【问题标题】:Java design issue - Adding functionality to fixed classesJava 设计问题 - 向固定类添加功能
【发布时间】:2009-06-09 14:55:10
【问题描述】:

我有一组类都需要以某种(个人)方式执行。

通常我会创建一个带有单个doSomething() 方法的DoSomethingInterface,并让每个类以适合每个类需要的方式实现该方法。但是,在这种情况下我不能这样做,因为类定义是不可修改的(自动生成的)

所以,我认为我需要创建一组不同的类,每个类都采用一个自动生成的类并对它们执行操作。所以,假设我有 2 个自动生成的类,Class1Class2,我将首先定义一个通用的 Operator 接口:

public interface Operator <TYPE>{
  public void doSomething(TYPE type);
}

然后在每个类中实现其中一个

public class Class1Operator implements Operator<Class1>{
    public void doSomething(Class1 type){
      ...
      ...
    }
}

public class Class2Operator implements Operator<Class2>{
    public void doSomething(Class2 type){
      ...
      ...
    }
}

好的,到目前为止一切顺利。现在,鉴于我有一个Class1 类型的对象,有没有什么方法可以在不求助于它的情况下获得它的运算符:

public Operator getOperator(Object obj){
  if(obj instanceof Class1){
    return new Class1Operator();
  }else if(obj instanceof Class2{
    return new Class2Operator();
  }
  return null;
}

这对我来说似乎是一种不好的做法......

我能想到的唯一其他方法是创建一个运算符到类名的映射,如下所示:

Map<Class, Operator> allOperators = new HashMap<Class, Operator>();
allOperators.put(Class1.class, new Class1Operator());
allOperators.put(Class2.class, new Class2Operator());

然后使用返回运算符:

public Operator getOperator(Object obj){
  return allOperators.get(obj);
}

但这似乎不对(我不确定,将对象从其类中键控是否有任何问题......)

关于这两种方法是否“正确”的任何意见?还是有更优雅的解决方案??

谢谢

【问题讨论】:

    标签: java class


    【解决方案1】:

    您实现的(按类映射方法)是我在教授模式时谈到的 GoF 访问者模式的替代方案之一。即使在运行时,它也是高效且可扩展的。比 if/else if/else 硬连线方法要好得多。

    关闭类的唯一问题是实际实例是否实现了子类型而不是您提到的类类型;那么查找地图将不起作用。

    如果您需要识别子类型,我会推荐 Aaron 的方法(沿着超类链向上走),但您可能还想查看已实现的接口。如果您只需要“精确的类匹配”,请保持您的 getOperator 简单。

    请注意,您在 getOperator 中有一个错误——它应该如下所示:

    public Operator getOperator(Object obj){
        return allOperators.get(obj.getClass());
    }
    

    还有一件事...将您的地图隐藏在另一个类中并按如下方式管理它:

    private Map<Class<?>, Operator<?>> map = new HashMap<Class<?>, Operator<?>>();
    public <T> void register(Class<T> clazz, Operator<T> operator) {
        map.put(clazz, operator);
    }
    

    这可以防止任何人注册一个对它所针对的类不起作用的操作符。 (您可能希望使用 Operator 作为参数,以允许针对超类编写的运算符,但这可能不是必需的)

    【讨论】:

      【解决方案2】:

      构建地图的一个问题是它不支持子类,除非您专门注册它们或扩展您的 get 函数以专门查找超类。

      也就是说,如果 B 继承自 A 并且您已经在 A.class 中注册了一个运算符。使用 B.class 获取运算符将失败,除非您将 getOperator 更改为:

      public Operator getOperator(Object obj){
        Class<?> current = obj.getClass();
        Operator op;
      
        while((op = allOperators.get(current)) == null){
          current = current.getSuperclass();
      
          if(current == null){
            /* 
             * We've walked all the way up the inheritance hierarcy
             * and haven't found a handler. 
             */
            return null;
          }
        }
      
        return op;
      }
      

      一旦获得了合理的 getOperator 实现,将类映射到运算符似乎是一种合理的方法。

      【讨论】:

      • 谢谢 Aaron,我不认为子类型会是个问题,实际上情况可能正好相反,因为子类型需要注册不同的运算符...
      【解决方案3】:

      您可以使用 Class.isAssignableFrom 来解决子类型问题。我一直在使用它,虽然它不是“访客”优雅,但在实践中却相当不错。

      【讨论】:

        【解决方案4】:

        是否可以创建自己的类来扩展生成的类,然后让您的类实现接口?

        【讨论】:

          【解决方案5】:

          你考虑过吗:

          public Interface Operator {
              public void doSomething();
          }
          
          public class Class1Operator extends Class1 implements Operator {
              ...
          }
          
          public class Class2Operator extends Class2 implements Operator {
              ...
          }
          

          但是关于您的第二个问题,即在不需要真正需要执行“instanceof”mojo 的情况下将操作符获取到对象(我猜这看起来不干净):

          我建议,如果您不能根据您的确切需求修改您的类,请为它们编写一个包装器:

          public Interface Operator<T> {
              public void doSomething(T obj);
          }
          
          public Interface WrappedObject<T> {
              public Operator<T> getOperator();
          }
          public class WrappedClass1 extends Class1 implements WrappedObject<Class1> {
              ...
          }
          public class WrappedClass2 extends Class2 implements WrappedObject<Class2> {
              ...
          }
          public class Class1Operator implements Operator<Class1> {
              ...
          }
          public class Class2Operator implements Operator<Class2> {
              ...
          }    
          

          这能满足你的需要吗?

          编写与您的需求不完全匹配且无法由您控制的类的包装器始终是一个好习惯。即使这些狂野的类发生变化,它也可以帮助您保持代码健康。

          干杯,

          jrh.

          【讨论】:

          • 这最终可能会创建许多额外的对象(取决于他的对象图有多大),如果这些对象相互引用,则包装器也需要为引用创建包装器.但我建议总体上考虑适配器/装饰器。
          【解决方案6】:

          我要说的是,根据 Java 处理泛型的方式,仅使用接口本身是不可能的。

          在 Java 中,泛型在编译时被删除并被强制转换替换。

          我还没有真正检查过它内部是如何工作的,但猜测一下,你的界面变成了这样:

          public interface Operator {
            public void doSomething(Object type);
          }
          

          在哪里调用它:

          public class Class1Operator implements Operator{
              public void doSomething(Object type){
                Class1 oType = (Class1) type;
                ...
                ...
              }
          }
          

          这仍然不完全正确,因为类型在返回后也会被强制转换,而且 Java 字节码实际上并不像 Java,但您可能会明白一般的想法。

          instanceof 和 Map 方法应该可以工作,即使它们有点乱。

          【讨论】:

          • 为什么会影响地图?
          • 不会,我现在已经澄清了这一点。
          猜你喜欢
          • 2017-04-02
          • 2019-09-09
          • 1970-01-01
          • 1970-01-01
          • 2013-01-25
          • 2021-10-25
          • 2015-09-27
          • 2023-03-30
          • 2015-07-15
          相关资源
          最近更新 更多