强制两个相似的类的行为就好像它们在Java中是多态的一样

时间:2014-10-06 08:27:06

标签: java oop inheritance polymorphism

摘要:

我想与两个类(' Item'和' Block')进行交互,它们共享许多类似的功能,就好像它们是从具有这些功能的接口实现的一样,但它们是不,我不能编辑它们。我有什么办法解决这个问题?我是不是写了超级hacky代码?

详细信息:

我正在使用Java编写一个Minecraft mod,使用minecraft的一部分是我无法编辑游戏的基本代码。游戏有两个基本类别; "座"和"项目",这两者共享许多功能,但开发人员没有让它们实现一个接口(我希望他们有)。

为了保持我的代码干净并避免在处理块和项目之间切换很多if语句,我希望将它们保存在通用列表中,最好尽可能少地转换为Block或Item,同时尝试保持可读性。

我目前的解决方案无法满足作为一个好的解决方案,它很难阅读,虽然它清理了一些代码重复,但仍然需要转换返回类型。

更新

根据Eran留下的答案,我已经更新了我对此问题的解决方案,现在我觉得这很令人满意:

我创建了与ItemOrBlockAdapter和扩展Block和Item接口的适配器(在评论中建议使用适配器名称):

public interface ItemOrBlockAdapter {
    public String myGetUnlocalizedName();
    public ItemOrBlockAdapter mySetCreativeTab(CreativeTabs tab);
}

public class BlockAdapter extends Block implements ItemOrBlockAdapter {
    protected BlockAdapter(String uid, Material m) {
        super(m);
        GameRegistry.registerBlock(this, uid);
    }

    public String myGetUnlocalizedName()
    {
        return this.getUnlocalizedName();
    }

    public ItemOrBlockAdapter mySetCreativeTab(CreativeTabs tab)
    {
        return (ItemOrBlockAdapter)this.setCreativeTab(tab);
    }
}

这比我之前使用的hacky解决方案要好得多(通过Block和Item方法查找每次调用所需的方法)!然而,它并非没有错,我现在必须编写我希望添加适配器三次的每个函数,如果其他东西继承了头适配器,则第四次。

我认为这已经解决了,但我对优秀的解决方案持开放态度,因为这涉及到相当多的代码重复(理解我必须添加很多方法来完成这些,而不仅仅是如上所示的两个)

2 个答案:

答案 0 :(得分:6)

您可以创建包装类 - BlockWrapperItemWrapper。两者都将实现相同的接口(包含BlockItem的常用方法)。 BlockWrapper将包含Block个实例,ItemWrapper将包含Item个实例。

示例:

public interface ItemOrBlock // think of a better name
{
    public void func1();

    public void func2();
}

public class BlockWrapper implements ItemOrBlock 
{
    private Block block;

    public BlockWrapper (Block block) {
        this.block = block;
    }

    public void func1()
    {
        block.func1();
    }

    public void func2()
    {
        block.func2();
    }

}

ItemWrapper会有类似的实现。

现在,如果您从BlockWrapperItemWrapper创建BlockItem,则可以将它们放入ItemOrBlock的集合中,并使用该接口调用其常用方法。

答案 1 :(得分:1)

虽然这个问题已经有了接受的答案,但我想提一下创建Dynamic Proxy Classes的选项。

有人可能会说,这只是一种“隐藏”丑陋反射代码的方式,它是一种简洁优雅的解决方案,与手动创建适配器/包装类相比,它对于类似的用例具有一个显着的优势:你可以为许多类创建一个接口的委托,而不必为每个“未实现”接口的类硬编码重复的样板代码。

以下是一个简单的例子,基于上述链接的委托人:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyExample
{
    public static void main(String[] args)
    {
        Block block = new Block();
        Item item = new Item();

        Entity blockEntity = asEntity(block);
        Entity itemEntity = asEntity(item);

        blockEntity.setName("Block");
        System.out.println(blockEntity.getName()+", "+blockEntity.computeSize());

        itemEntity.setName("Item");
        System.out.println(itemEntity.getName()+", "+itemEntity.computeSize());

    }

    private static Entity asEntity(Object object)
    {
        Class<?>[] ifs = new Class<?>[] { Entity.class };
        Entity entity = (Entity) Proxy.newProxyInstance(
            Entity.class.getClassLoader(), ifs, 
            new Delegator(object));
        return entity;
    }
}

class Delegator implements InvocationHandler
{
    private static Method hashCodeMethod;
    private static Method equalsMethod;
    private static Method toStringMethod;
    static
    {
        try
        {
            hashCodeMethod = Object.class.getMethod("hashCode",
                (Class<?>[]) null);
            equalsMethod = Object.class.getMethod("equals",
                new Class[] { Object.class });
            toStringMethod = Object.class.getMethod("toString",
                (Class<?>[]) null);
        }
        catch (NoSuchMethodException e)
        {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    private final Object delegate;

    Delegator(Object delegate)
    {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method m, Object[] args)
        throws Throwable
    {
        Class<?> declaringClass = m.getDeclaringClass();
        if (declaringClass == Object.class)
        {
            if (m.equals(hashCodeMethod))
            {
                return proxyHashCode(proxy);
            }
            else if (m.equals(equalsMethod))
            {
                return proxyEquals(proxy, args[0]);
            }
            else if (m.equals(toStringMethod))
            {
                return proxyToString(proxy);
            }
            else
            {
                throw new InternalError(
                    "unexpected Object method dispatched: " + m);
            }
        }
        else
        {
            try
            {
                Class<? extends Object> delegateClass = delegate.getClass();
                Method delegateMethod = delegateClass.getDeclaredMethod(
                    m.getName(),  m.getParameterTypes());
                return delegateMethod.invoke(delegate, args);
            }
            catch (InvocationTargetException e)
            {
                throw e.getTargetException();
            }
        }
    }

    protected Integer proxyHashCode(Object proxy)
    {
        return new Integer(System.identityHashCode(proxy));
    }

    protected Boolean proxyEquals(Object proxy, Object other)
    {
        return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
    }

    protected String proxyToString(Object proxy)
    {
        return proxy.getClass().getName() + '@' +
            Integer.toHexString(proxy.hashCode());
    }
}


class Item
{
    private String name;

    void setName(String name)
    {
        this.name = name;
    }

    String getName()
    {
        return name;
    }

    int computeSize()
    {
        return 12;
    }
}

class Block
{
    private String name;

    void setName(String name)
    {
        this.name = name;
    }

    String getName()
    {
        return name;
    }

    int computeSize()
    {
        return 23;
    }
}

interface Entity
{
    void setName(String name);

    String getName();

    int computeSize();

}

(当然,可以插入一些更好的错误处理,但它显示了基本方法)


编辑进一步详细说明,部分回应评论:

正如我所提到的,这可以被认为只是“隐藏”讨厌的反射部分。还有一个仍然需要处理通常的反射问题,比如更难处理的错误处理和缺少编译时错误检查。

但是对于更复杂的设置,它可能是有利的:想象一下,有两个以上的类,以及多个“未实现”的接口。然后以包装器/适配器类的形式手动编码胶水代码可能很麻烦。

但是,我喜欢动态代理类的简单性。它们是在Java中实现像Duck Typing这样的东西的唯一可行方法 - 它们只是神奇地工作。