我正在编写一个实用程序,它使用了我不控制的第三方库中定义的一些类。
我想知道什么是处理以下情况的好方法:第三方图书馆有一个基础抽象类"食品",由"开胃菜"、"主菜"、"饮料"one_answers"甜点"扩展而来
我正在写一个"服务员实用程序",里面有提供每种食物的方法。我想避免instanceof检查的无休止链。
`
Class WaiterUtility{
public serveItems(Food[] items)
{
for(Food aFood : items){
//how do i call the sub-class specific methods i wrote below?
}
}
private void serve(Appetizer aFood){//somecode}
private void serve(Entree aFood){//somecode}
private void serve(Beverage aFood){//somecode}
private void serve(Dessert aFood){//somecode}
}
`
如果可能的话,我恳请你不要像TBotV63在他的回答中那样使用反思(他甚至说要避免)。来自Oracle文档:
如果可以在不使用反射的情况下执行操作,则最好避免使用它
所以,很明显,我们倾向于说所有的Food
都可以服务,任何的Waiter
都可以服务任何类型的Food
。理想情况下,一个好的API将因此暴露出足以让serve(Food)
方法在不知道它是什么食物的情况下完成这项工作的方法。你的问题似乎意味着情况并非如此,因此需要做更多的事情
如果第三方库接受社区输入,那么您应该尝试打开问题或拉取请求来添加功能
显然,这并不总是可能的,所以下一个最好的方法是创建一个接口(类似于Serveable
),定义您需要的方法,然后在实现该接口时对不同类型的食物进行子类化。那么你就有Waiter.serve(Serveable)
了。
这比反射或instanceof
的许多使用要多,但它是更好的OO设计。
为什么反思不好
反射文档指出了反射的3个缺点
- 内部构件暴露
- 性能
- 安全
虽然你可能不在乎2或3,但1尤其糟糕。
使用反射可以。。。使代码功能失调,并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。
为什么instanceof
坏(在这种情况下)
serveItems(Food[])
向调用者暗示,如果您向它传递几个Food
项,它将为每个项提供服务。然而事实并非如此。我们只能为Food
的某些子类提供服务,如果我们尝试其他操作,就会出现运行时错误。Java是一种很好的类型安全语言,我们更喜欢编译时错误而不是运行时错误
另一个缺点是,每次添加或更改Food
的新子类时,都需要向Waiter
添加额外的代码。这成为一个跨领域的问题,并使代码从开发的角度不可扩展
这些并不是唯一的缺点/问题,只是几个例子。
您可以尝试以下代码:
Class WaiterUtility{
private Map<Class<? extends Food>, Waiter> waiters = new HashMap<>();
WaiterUtility() {
waiters.put(Appetizer.class, new AppetizerWaiter());
waiters.put(Entree.class, new EntreeWaiter());
waiters.put(Beverage.class, new BeverageWaiter());
waiters.put(Dessert.class, new DessertWaiter());
}
public serveItems(Food[] items)
{
for(Food aFood : items){
waiter.get(aFood.getClass()).serve(aFood);
}
}
private static abstract interface Waiter {
private void serve(Food aFood);
}
private static class AppetizerWaiter implements Waiter {
private void serve(Food aFood){
Appetizer appetizer = (Appetizer) aFood;
//somecode
}
}
private static class EntreeWaiter implements Waiter {
private void serve(Food aFood){//somecode}
}
private static class BeverageWaiter implements Waiter {
private void serve(Food aFood){//somecode}
}
private static class DessertWaiter implements Waiter {
private void serve(Food aFood){//somecode}
}
}
尝试类似以下内容:
public serveItems(Food[] items)
{
for(Food aFood : items){
Class<?> foodClass = aFood.getClass(); // Get the food's class
Method serve = WaiterUtility.class.getMethod("serve", foodClass); // Get the method by name and argument types
try {
serve.invoke(this, aFood);
} catch (IllegalArgumentException e) { // Should never occur, we're matching it up.
} catch (IllegalAccessException e) { // Shouldn't occur, we're in the same class.
} catch (InvocationTargetException e) {
// Handle errors possibly thrown by the serve method.
}
}
还没有测试过这个tho。
请注意,您应该避免这种情况,这是一种糟糕的设计。