《Java编程思想 Generics》读书笔记——Mixin

何谓Mixin

Mixin即mix in,混入的意思。
和多重继承类似,但通常混入Mixin的类和Mixin类本身不是is-a的关系,混入Mixin类是为了添加某些(可选的)功能。

自由地混入Mixin类就可以灵活地为被混入的类添加不同的功能。

传统的接口并不包含实现,而Mixin包含实现。实际上Mixin的作用和Java中的众多以able结尾的接口很相似。不同的是Mixin提供了(默认)实现,而 Java 中实现了able接口的类需要类自身来实现这些混入的功能(Serializable 接口是个例外)。

mixing in capabilities from multiple classes in order to produce a resulting class that represents all the types of the mixins.(最后生成的类:把多个类的功能混合进来并能够代表所有这些类)

Mixin的好处

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.

C++中的实现

C++支持多重继承,所以可以很容易实现Mixin。不过如果采用模板可以更容易更好实现。
a mixin is a class that inherits from its type parameter.

//: generics/Mixins.cpp
#include 
#include 
#include 
using namespace std;

template class TimeStamped : public T {
  long timeStamp;
public:
  TimeStamped() { timeStamp = time(0); }
  long getStamp() { return timeStamp; }
};

template class SerialNumbered : public T {
  long serialNumber;
  static long counter;
public:
  SerialNumbered() { serialNumber = counter++; }
  long getSerialNumber() { return serialNumber; }
};

// Define and initialize the static storage:
template long SerialNumbered::counter = 1;

class Basic {
  string value;
public:
  void set(string val) { value = val; }
  string get() { return value; }
};  

int main() {
  TimeStamped > 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
*///:~

从上面的例子中可以看到C++实现Mixin是多么简单。

TimeStamped > mixin1, mixin2;

Java中如何实现Mixin

Erasure forgets the base-class type, so a generic class cannot inherit directly from a generic parameter.

第一种方式:通过接口

// : generics/Mixins.java
import java.util.Date;

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

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.

第二种方式:装饰器模式

装饰器模式跟Mixin的概念很类似。

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.

// : generics/decorator/Decoration.java
package generics.decorator;

import java.util.Date;

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
    }
} /// :~

仔细研读上面的代码,你会发现它跟一般教程讲解的装饰器模式不一样,一般教程讲解的装饰器模式都是对某个方法功能的加强,比如以下代码:


interface Basket {
    public void show();

}


class Original implements Basket {
    public void show() {
        System.out.println("The original Basket contains");
    }
}


class AppleDecorator implements Basket {
    private Basket basket;

    public AppleDecorator(Basket basket) {
        super();
        this.basket = basket;
    }

    public void show() {
        basket.show();
        System.out.println("An Apple");
    }

}


class BananaDecorator implements Basket {
    private Basket basket;

    public BananaDecorator(Basket basket) {
        super();
        this.basket = basket;
    }

    public void show() {
        basket.show();
        System.out.println("A Banana");
    }

}


class OrangeDecorator implements Basket {
    private Basket basket;

    public OrangeDecorator(Basket basket) {
        super();
        this.basket = basket;
    }

    public void show() {
        basket.show();
        System.out.println("An Oranage");
    }

}


public class DecoratorPattern {

    public static void main(String[] args) {
        Basket basket = new Original();
        Basket myBasket = new AppleDecorator(new BananaDecorator(new OrangeDecorator(basket)));
        myBasket.show();
    }
}

而《Java编程思想》中的这个例子却不是这样的,它需要的是把各个装饰器叠加在一起,营造出Mxin的效果。

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.

第三种方式:使用动态代理

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 static net.mindview.util.Tuple.tuple;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import net.mindview.util.TwoTuple;

class MixinProxy implements InvocationHandler {
    Map delegatesByMethod;

    public MixinProxy(TwoTuple>... pairs) {
        delegatesByMethod = new HashMap();
        for (TwoTuple> 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());
    }
}

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.

总结

这三种方式没有一种能够达到使用C++模板实现的Mixin的效果,说到底不过是把其他不相干的类混合进来,一种方式是使用继承,一种方式是使用delegate。装饰器模式只是类更容易组合,并没有带来其他好处。动态代理可以最大程度模拟,但是由于所有的类都需要实现接口,这也在一定程度起了限制作用。

你可能感兴趣的:(《Java编程思想 Generics》读书笔记——Mixin)