根据情况如何选择动作取决于类型?

huangapple go评论72阅读模式
英文:

How do I choose action depending on type in this case?

问题

可以改进标题并添加标签。我不知道在这里使用什么术语。

我有如下的类:

abstract class A {
    void foo(Object obj) {
        pre();

        if(obj instanceof Map<String, String[]>) {
            methodA();
        } else if (obj instanceof Map<String, String>) {
            methodB();
        } else if (obj instanceof JSONObject) {
            ...

        post();
    }

    abstract protected void methodA();
    abstract protected void methodB();
}

class B extends A { /* Stuff */}

注意我有两种不同的映射。但让我们专注于第一个,Map<String, String[]>。if语句无法编译,所以我尝试了这个:

interface MyMap extends Map<String, String[]> {}

以及

if(obj instanceof MyMap) {

那样是可以编译的。然而,这似乎不起作用。

Map<String, String[]> map = new Hashtable<>();
B b = new B();
b.foo(map);

这会使上面的if语句失败,所以我尝试了这个:

MyMap map = new Hashtable<>();

但然后我得到了这个错误:

incompatible types: cannot infer type arguments for java.util.Hashtable<>

以及使用

MyMap map = new Hashtable<String, String[]>();

我得到了这个错误:

incompatible types: java.util.Hashtable<java.lang.String,java.lang.String[]> cannot be converted to MyMap

我在这里想要实现的是,以前我为每个对象都有一个单独的方法。但我不喜欢代码重复,因为pre()post()对于所有这些方法都是相同的。我应该如何解决这个问题?

英文:

Feel free to improve the title and to add tags. I did not know the terminology to use here.

I have classes like this:

abstract class A {
    void foo(Object obj) {
        pre();

        if(obj instanceof Map&lt;String, String[]&gt;) {
            methodA();
        } else if (obj instanceof Map&lt;String, String&gt;) {
            methodB();
        } else if (obj instanceof JSONObject) {
            ...

        post();
    }

    abstract protected void methodA();
    abstract protected void methodB();
}

class B extends A { /* Stuff */}

Note that I have two different maps. But let's focus on the first, Map&lt;String, String[]&gt;. The if statment did not compile, so I tried this:

interface MyMap extends Map&lt;String, String[]&gt; {}

and

if(obj instanceof MyMap) {

That compiled. However, this does not seem to work.

Map&lt;String, String[]&gt; map = new Hashtable&lt;&gt;();
B b = new B();
b.foo(map);

This makes the above if statement fail, so I tried this:

MyMap map = new Hashtable&lt;&gt;();

But then I get this:

incompatible types: cannot infer type arguments for java.util.Hashtable&lt;&gt;

and with

MyMap map = new Hashtable&lt;String, String[]&gt;();

I get this:

incompatible types: java.util.Hashtable&lt;java.lang.String,java.lang.String[]&gt; cannot be converted to MyMap

What I'm trying to achieve here is that previously, I had a separate method for each object. But I don't like the code duplication, because the pre() and post() are the same for all of them. How should I solve this?

答案1

得分: 2

你对Java有一些误解。

你似乎期望,鉴于以下代码:

interface MyMap extends Map<String, String[]> {}

你已经做出了一个本体声明:你似乎认为这意味着:

“我在此宣布,所有是 Map<String, String[]> 的东西也都是 MyMap。”

或者可能你认为这意味着:

“我创建了一个别名;MyMap 只是 Map<String, String[]> 的简写。”

但这两种都是不正确的。

正确的意思是:

我创建了一个全新的名为 MyMap 的接口。任何 MyMap 必然也是 Map<String, String[]>,但反过来未必成立。就现在而言,除了 Map 接口要求的方法之外,我没有进一步的要求。唯一的 MyMap 对象是通过编写 new X() 创建的,其中 X 是一个明确在类上带有 implements MyMap 的类,或者是一个做了这个声明的类的子类。

因此,在解决你的问题时,这是完全无用的。

你的第二个误解涉及泛型。

泛型主要是编译器想象出来的。它们是编译器检查的注释。认真看看:

Map<?, ?> = new HashMap<String, String>();

Map<?, ?> = new HashMap<Integer, Integer>();

都编译通过,并且对结果进行二进制比较。完全相同的字节。这清楚地证明了 &lt;String, String&gt; 部分在编译后 不会保留

这被称为擦除。在反射可访问的内容中出现泛型,比如方法的返回类型、方法的参数、扩展子句中的某些类型、字段的类型等,它会存储在类文件中,但作为注释存在(也就是说,JVM 本身甚至不会看它,对此毫不关心)。这个注释供 javac 使用,用于编译引用这些字段/方法/类的其他代码。

但在其他地方呢?javac 用它来生成警告、错误,并且偷偷地插入一些类型转换,然后它就 消失了

因此,这个代码:

x instanceof Map<String, String>

是不可能的。

你不能检查那个。x 知道它是一个 Map。但它不知道泛型是什么。

解决方案是退一步。

你遇到了某个未知的问题 X,然后你想:我知道了!我会编写一个方法,接受任何对象,然后我会检查传入的对象是否是 Map<String, String>,然后我会解决我的未知问题 X!但我不知道怎么检查,所以我会在 Stack Overflow 上问。

但这是一个死胡同。回去重新问问关于 '未知问题 X'。我敢肯定它是可以解决的,而且非常容易解决,但不是用这种方式。

对你可能的解决方案的一次猜测

如果你想消除代码重复,并且你要消除的重复代码是 prepost,比如:

public void someMethod(SomeSpecificType x) {
    pre();
    customStuffUniqueToThisType();
    post();
}

// 还有 20 多个类似的方法,同样的 pre 和 post

那么或许考虑使用 Lambda 表达式。你可以创建这个方法:

public <T> void apply(T obj, Consumer<T> runner) {
    pre();
    runner.consume(obj);
    post();
}

然后你可以调用它:

Map<String, String> obj = ....;
apply(obj, o -> {
   // o 是一个 Map<String, String>。
   // 在这里随便做任何你想做的事情。
});
英文:

You have some misunderstandings about java.

You seem to expect that, given:

> interface MyMap extends Map<String, String[]> {}

That you've made an ontological declaration: You seem to think this means:

"I henceforth decree that all things that are a Map&lt;String, String[]&gt; are also MyMaps."

Or possibly you think this means:

"I have created an alias; MyMap is just shorthand for Map&lt;String, String[]&gt;".

Neither is true.

The correct meaning is:

I created an entirely brand new interface called MyMap. Any MyMap is necessarily a Map<String, String[]>, but the reverse need not be true. Right now, other than what that Map interface demands you have as methods, I make no further demands. The only objects that are MyMaps are any objects created by writing new X(), where X is a class that explicitly has implements MyMap on it, or is a subclass of a class that did that.

And therefore, it is completely useless in trying to address your problem here.

Your second misunderstanding is generics.

Generics are primarily a figment of the compiler's imagination. They are compiler-checked comments. Seriously. Go check:

Map&lt;?, ?&gt; = new HashMap&lt;String, String&gt;();

and

Map&lt;?, ?&gt; = new HashMap&lt;Integer, Integer&gt;();

compile both, and do a binary diff on the result. Exactly the same bytes. Thus clearly proving that the &lt;String, String&gt; part does not survive compilation.

This is called erasure. There where generics appears in reflectively accessible stuff, such as in the return type of a method, or the parameter of a method, or some type in an extends clause, or a field's type - it is stored in the class file, but as a comment (as in, the JVM itself doesn't even look at it and cares absolutely not one iota about it). The comment is there for javac to look at, and use when compiling other code that references these fields/methods/classes.

But everywhere else? javac uses it to generate warnings, errors, and silently inject a few casts, and then it disappears.

Thus, this:

x instanceof Map&lt;String, String&gt;

is impossible.

You just can't check that. x knows that it is a Map. It has no idea what the generics are, though.

The solution then is to take a step back.

You had some unknown problem X, and you thought: I know! I'll write a method that takes any object, and then I'll check if the passed-in object is a Map&lt;String, String&gt;, and then I'll solve my unknown problem X! Except I don't know how to check for that so I'll ask on SO.

But this is a dead end. Go back and re-ask about 'unknown problem X'. I'm positive it can be solved, and quite easily at that, but not like this.

A wild stab at a possible solution for you

If you're looking to eliminate code duplication, and the duplicated code you're trying to deduplicate is the pre and post in:

public void someMethod(SomeSpecificType x) {
    pre();
    customStuffUniqueToThisType();
    post();
}

// and 20 more of these, with the same pre and post everywhere

Then perhaps consider lambdas. You could make this method:

public &lt;T&gt; void apply(T obj, Consumer&lt;T&gt; runner) {
    pre();
    runner.consume(obj);
    post();
}

and you can then call it:

Map&lt;String, String&gt; obj = ....;
apply(obj, o -&gt; {
   // o is a Map&lt;String, String&gt;.
   // do whatever you want here.
});

答案2

得分: 2

这是面向对象世界中已经多次解决的常见用例。

你正在寻找的是模板模式策略模式工厂模式的结合。

  1. 在了解了模板模式之后,你应该明白你的代码已经在某种程度上使用了这个模式。(尽管:foo 应该是 final
  2. 我会让类 A 接受一个类型参数(即 abstract class A&lt;T&gt;)。我会在 class A 中创建一个接受 T 作为输入的构造函数(即 A(T t) { this.t = t}
  3. 不要再有 methodAmethodB 这两个方法,我只会有一个叫做 doSomething(T t) 的抽象方法;我只会调用它一次,将 t 实例变量传递给它。我会移除所有的 if-else 条件。foo 的最终实现将是 foo() { pre(); doSomething(t); post() }
  4. B 然后可以扩展 A 并实现 doSomething(T t)。例如:class B extends A&lt;Map&lt;String, String[]&gt;&gt; { doSomething(&lt;Map&lt;String, String[]&gt;&gt; t) {...做些什么..} }
  5. 类似地,我可以分别为处理 Map&lt;String, String&gt;JSONObject 创建一个子类。
  6. 在这一点上,我有了不同的 A 实现。我需要一个名为 Context 的类,它将被传递一个 A,并调用 a.foo()。我需要一个名为 Factory 的类,它创建一个 Context 类并将合适的 A 子类实例根据某些运行时输入参数传递给它。
英文:

This is a common use case already solved multiple times in the object oriented world.

What you are looking for is a combination of Template Pattern, the Strategy Pattern and the Factory pattern.

  1. After reading up on the Template pattern, it should be clear that your code is somewhat already using this pattern. (Although : foo should be final)
  2. I would make class A take a type parameter (i.e abstract class A&lt;T&gt;). I would create a constructor in class A that takes T as input (i.e A(T t) { this.t = t})
  3. Instead of having 2 methods namely methodA and methodB, I would simply have one abstract method called doSomething(T t); I would call it only once, passing it the t instance variable. I would remove all the if-else conditions. The final implementation of foo would be foo() { pre(); doSomething(t); post() }
  4. class B can then extend A and implement doSomething(T t). Example : class B extends A&lt;Map&lt;String, String[]&gt;&gt; { doSomething(&lt;Map&lt;String, String[]&gt;&gt; t) {...do something..} }
  5. Similarly, I can create one subclass each for handling Map&lt;String, String&gt; and JSONObject respecrively.
  6. At this point, I have different implementations of A. I need to have a Context class that will be passed an A and it will call a.foo(). I need a Factory class that creates a Context class and passes it the right instance of a subclass A depending on some runtime input parameter.

huangapple
  • 本文由 发表于 2020年10月1日 22:53:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/64157934.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定