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).
Like other programming languages say Java, C+, polymorphism is also implemented in python for different purpose commonly Duck Typing, Operator overloading and Method overloading, and Method overriding. This polymorphism process can be achieved in two main ways namely overloading and overriding.
Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance. Like we specified in the previous chapter; Inheritance lets us inherit attributes and methods from another class. Polymorphism uses those methods to perform different tasks.
Each interface is considered as a type. An object of a class can be casted to the type of each interface it implements. This is how polymorphism via interfaces work.
Sure. In Java, you can have two classes implement the same interface, and their results are polymorphic. No functionality is inherited.
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 BlockWrapper
s and ItemWrapper
s from the Block
s and Item
s, you can put them in a Collection of ItemOrBlock
, and use that interface to call their common methods.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With