AlexeiBarnes
AlexeiBarnes

Reputation: 87

Forcing two similar classes to behave as if they were polymorphic in Java

Abstract:

I would like to interact with two classes ('Item' and 'Block') that share many similar functions as if they were implemented from an interface with these functions, however they are not and I can not edit them. What are my options for dealing with this? Am I stuck writing super hacky code?

Details:

I am working on a minecraft mod in Java, part of working with minecraft is that I can not edit the base code of the game. There are two fundamental classes to the game; "Block" and "Item", both of these share a number of functions however the developers did not make them implement an interface (as I wish they had).

To keep my code clean and avoid a lot of if statements switching between handling Blocks and Items I would like to keep both in a generic list, preferably casting to Block or Item as little as possible, and while attempting to maintain readability.

My current solution fails to satisfy as a good one, it's hard to read and while it cleans up some code duplication I still have to cast return types.

Update:

Based on the answer left by Eran I have updated my solution to this problem and I now find this to be satisfactory:

I created adapter (taking the adapter name as suggested in comments) classes that interfaced with an ItemOrBlockAdapter and extended Block and 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);
    }
}

This is a lot better than the hacky solution I used before (looking through Block and Item methods to find the desired method every call)! However it is not without fault, I now have to write every function I wish to add an adapter for three times, and a fourth time if something else inherits the head adapter.

I consider this solved but I'm open to superior solutions since this involves quite a lot of code duplication (Understand that I will have to add a lot of methods to make this complete, not just two as shown above).

Upvotes: 6

Views: 383

Answers (2)

Marco13
Marco13

Reputation: 54659

Although the question already has an accepted answer, I'd like to mention the option of creating Dynamic Proxy Classes.

One could argue that this is only a way of "hiding" the ugly reflection code, it is a neat and elegant solution, and compared to manually creating adapter/wrapper classes, it has one striking advantage for similar use cases: You can create delegates of one interface for many classes, without having to hard-code the repetitive boilerplate code for each class that is "not-implementing" the interface.

The follwing is a simple example, based on the delegator from the above mentioned link:

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();

}

(of course, some better error handling could be inserted, but it shows the basic approach)


EDIT To elaborate this further, partially in response to the comment:

As I mentioned, this could be considered as only "hiding" the nasty reflection parts. And one still has to cope with the usual reflection issues, like a more difficult error handling and missing compile-time error checks.

But for more complex setups, it could be advantageous: Imagine there are more than two classes, and more than one "not-implemented" interface. Then manually coding the glue code in form of wrapper/adapter classes could be cumbersome.

However, I like the simplicity of Dynamic Proxy Classes. They are the only feasible way to achieve something like Duck Typing in Java - and they just work magically.

Upvotes: 1

Eran
Eran

Reputation: 393936

You can Create wrapper classes - BlockWrapper and ItemWrapper. Both would implement the same interface (which would contain the common methods of Block and Item). BlockWrapper would contain a Block instance and ItemWrapper would contain an Item instance.

Example :

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 would have a similar implementation.

Now, if you create BlockWrappers and ItemWrappers from the Blocks and Items, you can put them in a Collection of ItemOrBlock, and use that interface to call their common methods.

Upvotes: 6

Related Questions