Mixins
The term mixin seems to have acquired numerous meanings over time, but the fundamental concept is that of mixing in capabilities from multiple classes in order to produce a resulting class that represents all the types of the mixins. This is often something you do at the last minute, which makes it convenient to easily assemble classes.
One value of mixins is that they consistently apply characteristics and behaviors across multiple classes. As a bonus, if you want to change something in a mixin class, those changes are then applied across all the
classes where the mixin is applied. Because of this, mixins have part of the flavor of aspect-oriented programming (AOP), and aspects are often suggested to solve the mixin problem.
Mixins in C++
One of the strongest arguments made for multiple inheritance in C++ is for the use of mixins. However, a more interesting and elegant approach to mixins is using parameterized types, whereby a mixin is a class that inherits from its type parameter. In C++, you can easily create mixins because C++ remembers the type of its template parameters.
Here's a C++ example with two mixin types: one that allows you to mix in the property of having a time stamp, and another that mixes in a serial number for each object instance:
//: generics/Mixins.cpp #include <string> #include <ctime> #include <iostream> using namespace std; template<class T> class TimeStamped : public T { long timeStamp; public: TimeStamped() { timeStamp = time(0); } long getStamp() { return timeStamp; } }; template<class T> class SerialNumbered : public T { long serialNumber; static long counter; public: SerialNumbered() { serialNumber = counter++; } long getSerialNumber() { return serialNumber; } }; // Define and initialize the static storage: template<class T> long SerialNumbered<T>::counter = 1; class Basic { string value; public: void set(string val) { value = val; } string get() { return value; } }; int main() { TimeStamped<SerialNumbered<Basic> > mixin1, mixin2; mixin1.set("test string 1"); mixin2.set("test string 2"); cout << mixin1.get() << " " << mixin1.getStamp() << " " << mixin1.getSerialNumber() << endl; cout << mixin2.get() << " " << mixin2.getStamp() << " " << mixin2.getSerialNumber() << endl; } /* Output: (Sample) test string 1 1129840250 1 test string 2 1129840250 2 *///:~
In main( ), the resulting type of mixini and mixin2 has all the methods of the mixed-in types. You can think of a mixin as a function that maps existing classes to new subclasses. Notice how trivial it is to create a mixin using this technique; basically, you just say, "Here's what I want," and it happens:
TimeStamped<SerialNumbered<Basic> > mixini , mixin2;
Unfortunately, Java generics don't permit this. Erasure forgets the base-class type, so a generic class cannot inherit directly from a generic parameter.
Mixing with interfaces
A commonly suggested solution is to use interfaces to produce the effect of mixins, like this:
//: generics/Mixins.java import java.util.*; interface TimeStamped { long getStamp(); } class TimeStampedImp implements TimeStamped { private final long timeStamp; public TimeStampedImp() { timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } interface SerialNumbered { long getSerialNumber(); } class SerialNumberedImp implements SerialNumbered { private static long counter = 1; private final long serialNumber = counter++; public long getSerialNumber() { return serialNumber; } } interface Basic { public void set(String val); public String get(); } class BasicImp implements Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } class Mixin extends BasicImp implements TimeStamped, SerialNumbered { private TimeStamped timeStamp = new TimeStampedImp(); private SerialNumbered serialNumber = new SerialNumberedImp(); public long getStamp() { return timeStamp.getStamp(); } public long getSerialNumber() { return serialNumber.getSerialNumber(); } } public class Mixins { public static void main(String[] args) { Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); mixin1.set("test string 1"); mixin2.set("test string 2"); System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber()); System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber()); } } /* Output: (Sample) test string 1 1132437151359 1 test string 2 1132437151359 2 *///:~
The Mixin class is basically using delegation, so each mixed-in type requires a field in Mixin, and you must write all the necessary methods in Mixin to forward calls to the appropriate object. This example uses trivial classes, but with a more complex mixin the code grows rapidly.
Using the Decorator pattern
When you look at the way that it is used, the concept of a mixin seems closely related to the Decorator design pattern. Decorators are often used when, in order to satisfy every possible combination, simple subclassing produces so many classes that it becomes impractical.
The Decorator pattern uses layered objects to dynamically and transparently add responsibilities to individual objects. Decorator specifies that all objects that wrap around your initial object have the same basic interface. Something is decoratable, and you layer on functionality by wrapping other classes
around the decoratable. This makes the use of the decorators transparent-there are a set of common messages you can send to an object whether it has been decorated or not. A decorating class can also add methods, but as you shall see, this is limited.
Decorators are implemented using composition and formal structures (the decoratable/decorator hierarchy), whereas mixins are inheritance-based. So you could think of parameterized-type-based mixins as a generic decorator mechanism that does not require the inheritance structure of the Decorator
design pattern.
The previous example can be recast using Decorator:
//: generics/decorator/Decoration.java package generics.decorator; import java.util.*; class Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } class Decorator extends Basic { protected Basic basic; public Decorator(Basic basic) { this.basic = basic; } public void set(String val) { basic.set(val); } public String get() { return basic.get(); } } class TimeStamped extends Decorator { private final long timeStamp; public TimeStamped(Basic basic) { super(basic); timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } class SerialNumbered extends Decorator { private static long counter = 1; private final long serialNumber = counter++; public SerialNumbered(Basic basic) { super(basic); } public long getSerialNumber() { return serialNumber; } } public class Decoration { public static void main(String[] args) { TimeStamped t = new TimeStamped(new Basic()); TimeStamped t2 = new TimeStamped( new SerialNumbered(new Basic())); //! t2.getSerialNumber(); // Not available SerialNumbered s = new SerialNumbered(new Basic()); SerialNumbered s2 = new SerialNumbered( new TimeStamped(new Basic())); //! s2.getStamp(); // Not available } } ///:~
The class resulting from a mixin contains all the methods of interest, but the type of the object that results from using decorators is the last type that it was decorated with. That is, although it's possible to add more than one layer, the final layer is the actual type, so only the final layer's methods are visible,whereas the type of the mixin is all the types that have been mixed together. So a significant drawback to Decorator is that it only effectively works with one layer of decoration (the final one), and the mixin approach is arguably more natural. Thus, Decorator is only a limited solution to the problem addressed by mixins.
Mixins with dynamic proxies
It's possible to use a dynamic proxy to create a mechanism that more closely models mixins than does the Decorator (see the Type Information chapter for an explanation of how Java's dynamic proxies work). With a dynamic proxy, the dynamic type of the resulting class is the combined types that have been mixed in.
Because of the constraints of dynamic proxies, each class that is mixed in must be the implementation of an interface:
//: generics/DynamicProxyMixin.java import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Tuple.*; class MixinProxy implements InvocationHandler { Map<String,Object> delegatesByMethod; public MixinProxy(TwoTuple<Object,Class<?>>... pairs) { delegatesByMethod = new HashMap<String,Object>(); for(TwoTuple<Object,Class<?>> pair : pairs) { for(Method method : pair.second.getMethods()) { String methodName = method.getName(); // The first interface in the map // implements the method. if (!delegatesByMethod.containsKey(methodName)) delegatesByMethod.put(methodName, pair.first); } } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Object delegate = delegatesByMethod.get(methodName); return method.invoke(delegate, args); } @SuppressWarnings("unchecked") public static Object newInstance(TwoTuple... pairs) { Class[] interfaces = new Class[pairs.length]; for(int i = 0; i < pairs.length; i++) { interfaces[i] = (Class)pairs[i].second; } ClassLoader cl = pairs[0].first.getClass().getClassLoader(); return Proxy.newProxyInstance( cl, interfaces, new MixinProxy(pairs)); } } public class DynamicProxyMixin { public static void main(String[] args) { Object mixin = MixinProxy.newInstance( tuple(new BasicImp(), Basic.class), tuple(new TimeStampedImp(), TimeStamped.class), tuple(new SerialNumberedImp(),SerialNumbered.class)); Basic b = (Basic)mixin; TimeStamped t = (TimeStamped)mixin; SerialNumbered s = (SerialNumbered)mixin; b.set("Hello"); System.out.println(b.get()); System.out.println(t.getStamp()); System.out.println(s.getSerialNumber()); } } /* Output: (Sample) Hello 1132519137015 1 *///:~
Because only the dynamic type, and not the static type, includes all the mixed-in types, this is still not quite as nice as the C++ approach, because you're forced to downcast to the appropriate type before you can call methods for it. However, it is significantly closer to a true mixin.
There has been a fair amount of work done towards the support of mixins for Java, including the creation of at least one language add-on, the Jam language, specifically for supporting mixins.
相关推荐
《Thinking in Java》是Bruce Eckel的经典之作,第四版涵盖了Java编程语言的广泛主题,适合初学者和有经验的程序员。这本书深入浅出地讲解了Java的核心概念和技术,旨在帮助读者建立坚实的编程基础,并理解面向对象...
《Thinking in Java》是Java编程领域的一本经典著作,由Bruce Eckel撰写,深受程序员喜爱。这本书分为第三版和第四版,提供了英文版和中文版,适合不同语言背景的学习者。书中内容详实且深入,从基础知识到高级概念...
很抱歉,但根据您给出的信息,这似乎是一个音乐文件列表,而非与"Thinking in Java"相关的IT知识内容。"Thinking in Java"是一本著名的编程书籍,通常与Java编程语言的学习和实践相关。如果您的目标是获取这方面的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它深入浅出地介绍了Java语言的核心概念和技术。这本书通过实例代码来讲解理论,使读者能够更好地理解和掌握Java编程。在这个压缩包中,我们很可能会找到与书中的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它涵盖了Java语言的核心概念和技术,深受程序员和初学者喜爱。这本书分为第三版和第四版,每个版本都有其独特的知识点和更新内容。 第三版是针对Java 2 Platform...
《Thinking in Java(英文原版第4版)》作为一本经典Java编程思想书籍,其内容涵盖了面向对象的叙述方式,并针对Java SE5/6版本新增了示例和章节。本书适合作为初学者的入门教材,同时也包含了足够的深度,适合专业...
《Thinking in Java》会详细解释如何使用这些集合以及迭代器(Iterator)和泛型(Generics)的概念,这些对于编写高效且可维护的代码至关重要。 多线程是并发编程的基础,Java提供了强大的支持。书中会讨论线程的...
### 《Thinking in Java》第四版关键知识点综述 #### 一、书籍概述与评价 《Thinking in Java》第四版是一本备受推崇的经典Java编程教材,由Bruce Eckel撰写,适用于初学者及进阶读者。本书自出版以来,便成为众多...
《Thinking in Java》是Java编程领域的一本经典之作,由Bruce Eckel撰写,深受程序员喜爱。这本书深入浅出地介绍了Java语言的核心概念和技术,旨在帮助读者建立起坚实的基础,并提升编程思维能力。书中不仅包含了...
《Thinking in Java》是Bruce Eckel的经典之作,第四版更是深受全球Java开发者喜爱的教材。这本书深入浅出地讲解了Java编程语言的核心概念和技术,旨在为初学者提供全面且深入的Java学习指导。 首先,书中的第一章...
《Thinking in Java 4th Edition with Annotated Solution Guide》是一本经典的Java编程教材,由Bruce Eckel撰写。这本书深入浅出地介绍了Java编程的核心概念和技术,对于初学者和经验丰富的开发者来说,都是一个...
《Thinking in Java 4th 习题答案》涵盖了多个关键的Java编程概念和技术,包括深入的容器理解、输入/输出(I/O)处理、枚举类型(Enumerated Types)、注解(Annotations)以及并发编程(Concurrency)。这些章节是Java学习...
《Thinking In Java》是Bruce Eckel的经典之作,它深入浅出地介绍了Java编程语言的核心概念和技术,被广大程序员视为学习Java的权威指南。第四版更是加入了更多现代Java特性,如Generics、Annotations等,使读者能够...
Java 泛型详解 Java 中的泛型是 Java 5(JDK 1.5)中引入的一项新特性,旨在解决类型安全和代码重用的问题。泛型允许程序员对类型进行抽象,使得代码更加灵活和可维护。 泛型的优点: 1. 类型安全:泛型可以在...
《Thinking in Java》是Bruce Eckel的经典著作,它在Java编程领域享有极高的声誉,是许多初学者和专业开发者的必备参考书籍。该书的第三版中文电子版PDF提供了全面而深入的Java学习资源,旨在帮助读者理解Java语言的...
本资料 "[Java泛型和集合].(Java.Generics.and.Collections).Maurice.Naftalin&Philip.Wadler.文字版" 由知名专家Maurice Naftalin和Philip Wadler编著,提供了关于这些主题的深入理解。 **Java泛型** 是自Java...