经过其他 StackOverflow 用户提供的精彩答案的一点思考和鼓励,我想我偶然发现了一个合理的解决方案。
场景
我们想收集一组Ingredients,并系统地解释可以从中复制的美味鸡尾酒食谱的范围。这些关系必须灵活并且能够访问我们通用数组规范中的数据成员。
技术
在这个应用程序中,我们将处理一组实例。在这方面,我们希望开发模式匹配代码,作为数据成员聚合集的函数,而不是让数据成员自己定义配方的逻辑。原因有两个;首先,Ingredient 不仅仅只用于Cocktail (这将使你们更容易参加聚会);它可以在Cake、Salad 甚至Colonoscopy 中;因此,我们有义务从基类规范中抽象出分析逻辑,以改进职责分离。这种方法激励我们开发一套通用工具,适用于各种不同的应用程序,而不仅仅是我想象的酒吧。其次,它使我们能够处理自动装箱的 Java Primitives 或 Java Beans,而无需构造任何接口。这种方法的另一个令人高兴的副产品是,它使Cocktails 模式的整体可视化变得更加清晰。类调用之间没有“隐藏”的交互。
因此,我决定将整个方法形式化为两个通用实体之间的有限交互,而不是处理不同 Ingredients 之间的关系; Alice 和 Bob。我决定将这些互动称为Liason,不仅因为我很伤心,还因为我很孤独。
设计
首先,我构建了一个名为ILiason 的Interface。我使用接口是因为这使设计中的现有类能够与模式匹配架构集成,而不会导致多重继承冲突。
/* Defines the basis of a ILiason. */
public interface ILiason<A, B> {
/* Defines whether a ILiason exists between Alice and Bob. */
public abstract boolean isLiason(final A pAlice);
/* Returns the source of comparison. */
public abstract B getBob();
}
在ILiason 中,我们定义了两个简单的方法,它们依赖于两个泛型类型,A (Alice) 和B (Bob)。 A 将是我们希望比较的运行时集合或对象,B 将是 getBob() 返回的比较源。在方法isLiason(final A pAlice)中,我们将定义如何正确比较Alice和Bob的具体逻辑。
在Cocktails 应用程序中,我决定将每个特定成分表示为IIngredient 接口的实现者。您将在下面看到我们定义了诸如Rum、Vodka 和呃……Gin 之类的东西。我还为IIngredient、ISpoonfuls 定义了一个附加扩展;这会枚举调用者选择的某种成分的勺数。 ISpoonfuls 的具体实现在 IIngredient.Sugar 类规范中定义。
`/* 基本成分接口。 */
公共接口 IIngredient {
/* Amount Interface. */
public static interface ISpoonfuls extends IIngredient {
/* Returns the Number of Teaspoons required for the concrete Ingredient. */
public abstract int getNumberOfTeaSpoons();
}
/* Define some example implementations. */
public static final class Rum implements IIngredient { };
public static final class Liquor implements IIngredient { };
public static final class Vodka implements IIngredient { };
public static final class Cream implements IIngredient { };
public static final class Gin implements IIngredient { };
public static final class Vermouth implements IIngredient { };
public static final class Orange implements IIngredient { };
public static final class Lime implements IIngredient { };
public static final class Soda implements IIngredient { };
public static final class Mint implements IIngredient { };
public static final class Sugar implements ISpoonfuls {
/* Member Variables. */
private final int mNumberOfTeaspoons;
/* Constructor. */
public Sugar(final int pNumberOfTeaspoons) {
/* Initialize Member Variables. */
this.mNumberOfTeaspoons = pNumberOfTeaspoons;
}
/* Getters. */
@Override public int getNumberOfTeaSpoons() { return this.mNumberOfTeaspoons; }
};
}`
从这个设计中可以清楚地看出,为了检查集合中是否存在某个IIngredient,我们希望测试它的类。所以,我们将定义我们的第一个 ILiason 类型,Assignable:
/* A ILiason used to determine whether a given instance is assignable from a given class. */
public static abstract class Assignable<A> implements ILiason<A, Class<?>> {
/* Default Implementation Stub. */
public static final class Impl<A> extends Assignable<A> { private final Class<?> mBob; public Impl(final Class<?> pBob) { this.mBob = pBob; } @Override public final Class<?> getBob() { return this.mBob; } };
/* Determines whether Alice is an assignable form of Bob. */
@Override public final boolean isLiason(final A pAlice) {
/* Checks whether the source class of Alice is compatible with the concretely-defined Bob. */
return (this.getBob().isAssignableFrom(pAlice.getClass()));
}
};
在抽象(不完整)Assignable 类中,我们将看到我们定义了Alice 类型的通用规范,但是我们断言Bob 必须是Class<?> 引用。在我们对isLiason(final A pAlice) 的具体定义中,我们将getBob() 返回的Class<?> 引用与isAssignableFrom(Class<?> c) 方法结合使用来调查Java 是否可以检测到Alice 和Bob 之间的现有层次结构。我们使用此指标作为Alice 和Bob 之间是否存在联系的结果。在Cocktails 中,我们可以使用ILiason 的这种基本形式来检测特定的类类型。
作为一个基本的实现,下面我演示如何检测某个实例是否是IIngredient的特定类型:
(new ILiason.Assignable<IIngredient>() { @Override public Class<?> getBob() { return Lime.class; } }).isLiason(Lime.class); // Returns true!
(new ILiason.Assignable<IIngredient>() { @Override public Class<?> getBob() { return Lime.class; } }).isLiason(Mint.class); // Returns false!
到目前为止,与使用instanceof 可以实现的目标相比,我们已经取得了很多开销,而且确实如此;但是当我们转向更复杂的关系时,好处很快就会显现出来,这些关系从根本上依赖于相同的架构。
我们设计的另一个要求是,对于给定的实例,我们希望查询它们的实例成员以确定ILiason 的有效性。为此,我们定义了ILiason 的第二个实现者Intuition。
到目前为止,与使用 instanceof 可以实现的目标相比,我们已经取得了很多开销,而且确实如此;但是当我们转向更复杂的关系时,好处很快就会显现出来,这些关系从根本上依赖于相同的架构。
我们设计的另一个要求是,对于给定的实例,我们希望查询它们的实例成员以确定ILiason 的有效性。为此,我们定义了ILiason 的第二个实现者Intuition。
/* A ILiason which uses informed knowledge of a type to determine the state of a ILiason. */
public static abstract class Intuition<A, B> implements ILiason<A, Class<?>> { /*** TODO: to B **/
/* First, we determine if Alice is an instance of Bob. */ /** TODO: We suppress the unchecked type cast, however Assignable guarantees us the check. **/
@Override @SuppressWarnings("unchecked") public final boolean isLiason(final A pAlice) {
/* Determine if we can use intuitive processing via an Assignable. If we can't, return the default result. */
return (new ILiason.Assignable.Impl<A>(this.getBob()).isLiason(pAlice) ? this.onSupplyIntuition((B)pAlice) : false);
}
/* Defines concrete methodology towards handling alice in a class-specific manner. */
public abstract boolean onSupplyIntuition(final B pA);
};
`
在Intuition 中,我们看到为了计算Liason 的状态,我们首先使用ILiason.Assignable 测试可分配性;如果实现了这一点,我们就可以安全地将Alice 类型转换为可分配类型,并允许具体实现者通过onSupplyIntuition 定义对实例的更深入的分析。下面,我们用这个来检查IIngredient.Sugar的NumberOfTeaspoons:
new ILiason.Intuition<IIngredient, Sugar>() {
/* Define the Number of Teaspoons of Sugar expected for a Mojito. (I made this up, it's probably a lot more.) */
@Override public final boolean onSupplyIntuition(final Sugar pSugar) { return (pSugar.getNumberOfTeaSpoons() == 2); }
/* Define the Searchable class reference. */
@Override public final Class<Sugar> getBob() { return Sugar.class; }
}; } }
通过这些定义,我们能够定义集合的通用分析和知情分析,并使用它们的结果来推断类的特定组合。例如,可以使用ILiason.Element 找到给定的ILiason 在单个数组索引中的存在:
/* Determines whether a particular Liason is present within a specified Array, Alice. */
public static abstract class Element<A, B> implements ILiason<A[], ILiason<A, B>> {
/* Here we iterate through the entire specification of Alice in search of a matching Liason. */
@Override public final boolean isLiason(final A[] pAlice) {
/* Define the Search Metric. */
boolean lIsSupported = false;
/* Iterate Alice. */
for(int i = 0; i < pAlice.length && !lIsSupported; i++) {
/* Fetch Alice at this index. */
final A lAlice = pAlice[i];
/* Update the Search metric using Alice's Liason with Bob. */
lIsSupported |= this.getBob().isLiason(lAlice);
}
/* Return the Search Metric. */
return lIsSupported;
}
};
使用类似的方法,可以使用ILiason.Cohort 处理ILiasons 数组对给定Alice 的适用性。在对getBob() 的调用中定义ILiasons 的顺序也定义了优先级:
/* Compares an array of Alice against an array of Liasons. Determines whether all Liasons are fully met. */
public static abstract class Cohort<A, B> implements ILiason<A[], ILiason<A[], B>[]> {
/* Iterates the array of Alice and returns true if all Liasons within the cohort are supported. */
@Override public final boolean isLiason(final A[] pAlice) {
/* Define the Search Metric. */
boolean lIsValid = true;
/* Fetch the associated Liasons. */
final ILiason<A[], B>[] lLiasons = this.getBob();
/* Iterate the Liasons whilst the delegation is Valid. */
for(int i = 0; i < lLiasons.length && lIsValid; i++) {
/* Fetch the Liason. */
final ILiason<A[], B> lLiason = lLiasons[i];
/* Update the search metric. */
lIsValid &= lLiason.isLiason(pAlice);
}
/* Return the search metric. */
return lIsValid;
}
};
使用这些工具,我们可以定义不同的Cocktails,如下所示::
/* 定义 Martini 规范。 */
private static final ILiason.Cohort<IIngredient, Class<?>> LIASON_COHORT_COCKTAIL_MARTINI = new ILiason.Cohort<IIngredient, Class<?>>() { @SuppressWarnings("unchecked") @Override public final ILiason<IIngredient[], Class<?>>[] getBob() { return ((ILiason<IIngredient[], Class<?>>[]) new ILiason<?, ?>[] {
/* Define the Dry-Gin. */
new ILiason.Element<IIngredient, Class<?>>() { @Override public final ILiason<IIngredient, Class<?>> getBob() { return new ILiason.Assignable<IIngredient>() { @Override public Class<?> getBob() { return Gin.class; } }; } },
new ILiason.Element<IIngredient, Class<?>>() { @Override public final ILiason<IIngredient, Class<?>> getBob() { return new ILiason.Assignable<IIngredient>() { @Override public Class<?> getBob() { return Vermouth.class; } }; } },
new ILiason.Element<IIngredient, Class<?>>() { @Override public final ILiason<IIngredient, Class<?>> getBob() { return new ILiason.Assignable<IIngredient>() { @Override public Class<?> getBob() { return Orange.class; } }; } },
}); } };
然后,通过对LIASON_COHORT_COCKTAIL_MARTINI 的isLiason(final IIngredient[] pAlice) 方法的简单调用,我们能够测试IIngredients 数组中是否存在适当的数据成员。
你有它!静态定义的模式定义,可以实现任意比较功能。
缺点
-- 创建新的ILiason 定义有相当大的编码开销。尽管该模式为我们提供了所有灵活性,但它在简单性方面并不完全符合标准。
——很可能很慢。这里实现的大多数比较是对每个 ILiason 类型的二维数组搜索。 instanceof 在简单情况下可能是更可取的选择。
-- ILiason 定义中的职责可能重叠。幸运的是,核心 ILiason 类型本身就是 ILiasons,并且通常可以嵌套以提高代码凝聚力。