古怪的泛型循环 CRG
他能产生使用子类作为参数 和 返回类型 的父类
class BasicHolder<T>{private T item;public void setItem(T item) {this.item = item;}public T getItem() {return item;}void f(){System.out.println(item.getClass().getSimpleName());}}class SubType extends BasicHolder<SubType>{}public class Holder {public static void main(String[] args) {SubType s1 = new SubType(), s2 = new SubType();s1.setItem(s2);SubType s3 = s1.getItem();s1.f();}}SubType
基类用子类替代其参数,因此在SubType中,传递给setItem()和getItem()的返回类型都是确切的SubType。
混型
混型最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类
混型与接口相结合
例如 现在需要把获取当前时间戳、获取计数器和存取某个值的三个类合成一个混型。
interface TimeStamped {Date getStamp();}class TimeStampedImp implements TimeStamped {private final Date timeStamp;public TimeStampedImp() {timeStamp = new Date();}@Overridepublic Date getStamp() {return timeStamp;}}interface SerialNumbered {long getSerialNumber();}class SerialNumberedImp implements SerialNumbered {private static long counter = 1;private final long serialNumber = counter++;@Overridepublic long getSerialNumber() {return serialNumber;}}interface Basic {public void set(String val);public String get();}class BasicImp implements Basic {private String value;@Overridepublic void set(String val) {value = val;}@Overridepublic String get() {return value;}}//自己创建的混型interface Game{String lol();}class GameImp implements Game{private String str;public GameImp() {str = "来把LOL";}@Overridepublic String lol(){return str;}}//实现获取时间 和 序列号class Mixin extends BasicImp implements TimeStamped, SerialNumbered ,Game{private TimeStamped timeStamp = new TimeStampedImp();private SerialNumbered serialNumber = new SerialNumberedImp();private Game str = new GameImp();@Override //每加入一个混型就要重写方法,代码量增大public Date getStamp() {return timeStamp.getStamp();}@Overridepublic long getSerialNumber() {return serialNumber.getSerialNumber();}@Overridepublic String lol() {return str.lol();}}public class Mixins {public static void main(String[] args) {Mixin mixin1 = new Mixin(), mixin2 = new Mixin();mixin1.set("测试1");System.out.println(mixin1.get() + " " +mixin1.getStamp() + " " + mixin1.getSerialNumber()+" "+mixin1.lol());mixin2.set("测试2");System.out.println(mixin2.get() + " " +mixin2.getStamp() + " " + mixin2.getSerialNumber());}}测试1 Mon May 16 08:41:31 CST 2022 1 来把LOL测试2 Mon May 16 08:41:31 CST 2022 2
Mixin 类基本上在使用代理,因此每个混入类型都要求在Mixin 中有一个相应的域,还必须在Mixin 中编写所有必须的方法。 这样下来当 使用更复杂的混型时,代码数量会急速增加。
通过动态代理来实现混型
由于动态代理的限制,每个被混入的类都要求为接口。
public class TwoTuple<A, B> {public TwoTuple(A first, B second) {this.first = first;this.second = second;}private A first;private B second;public A getFirst() {return first;}public B getSecond() {return second;}}
class MixinProxy implements InvocationHandler {// 在 MixinProxy 代理类的构造方法中,将需要代理的方法作为 key ,// 实现代理方法的实现类作为 value 存入 delegatesByMethod 中。private Map<String, Object> delegatesByMethod;//TwoTuple 存储两个类型参数,第一个为被代理接口的实现类,第二个为被代理接口。public MixinProxy(TwoTuple<Object, Class<?>> ... pairs) {delegatesByMethod = new HashMap<String, Object>();for (TwoTuple<Object, Class<?>> pair : pairs) {//getMethods(): 返回一个包含Method对象的数组,// 该数组反映由这个class对象表示的类或接口的所有公共方法for (Method method : pair.second.getMethods()) {//获取要代理接口中所有的Method对象//method.getName(): 返回由这个method对象表示的方法的名称,作为一个String。String methodName = method.getName();//System.out.print(methodName+" ");//判断是否包含将要代理的接口中的方法 不包含的话 将Method与实现类对应//即动态绑定 Map中保存的是每一个具体实现类与将要代理的方法一一对应//将需要代理的方法作为 key ,实现代理方法的实现类作为 value 存入 delegatesByMethod 中。if (!delegatesByMethod.containsKey(methodName))delegatesByMethod.put(methodName, pair.first);}}}//当指定的接口类的方法被调用时 真正的调用发生在此处 根据调用的方法 去Map中查找对于的具体实现类 然后通过反射调用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")//在此处处理多个接口与多个实现类的关系 产生真正的代理对象//通过 newInstance() 方法获取实例时,传入第一个接口实现类的类加载器、需要被代理的接口、实现 InvocationHandler 接口的类// (任意一个接口实现类的加载器都行,传入第一个是防止因下标越界引起异常)//返回代理对象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) {//因代理对象同时代理了多组接口,// 故可以转换为 Basic 、TimeStamped 、SerialNumbered 任意类型进行方法调用。Object mixin = MixinProxy.newInstance(tuple(new BasicImp(), Basic.class),tuple(new TimeStampedImp(), TimeStamped.class),tuple(new SerialNumberedImp(), SerialNumbered.class),tuple(new GameImp(), Game.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());System.out.println(g.lol()); //调用游戏类方法}}输出:HelloMon May 16 09:04:06 CST 20221来把LOL
倘若我们需要添加新的类型至混型之中,只需要拓展对应的接口。相比直接通过接口实现混型,此方式的代码量更少,也更为灵活
装饰器模式
装饰器是通过使用 组合 和 形式化结构 (可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。
因此可以将基于参数化类型的混型当作是一种泛型装饰器机制 。 这种机制不需要装饰器设计模式的继承结构
—————————————————————————————————————————————————————————-
装饰器模式也叫做包装器模式,属于结构性设计模式一种,装饰器设计模式在实际的生活中也有很多体现,举例来说,某手机厂商发布了XXX标准版手机,为了吸引用户眼球,提升销量,还特别有纪念碑,青春版,至尊版等
我们可以这么设想,不管是哪个版本的手机,其基本的功能是不变的,电话、短信、拍照等这些基础的功能在各个版本都能找到
但是不同的版本,基于某个基础的版本,根据标价的不同添加了更多定制化或个性化的功能,相当于是说丰富了标准版的功能
- 装饰器模式作为现有类的一个包装,允许向现有的对象添加注入新的功能,同时还不改变原有的结构
- 给基础对象添加功能,一般有2种方式,继承或关联组合,将一个类的对象嵌入到另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,相比继承更加灵活
定义一个手机接口
public interface Phone {String desc();int getPrice();}
具体的被装饰类的实现,以手机为例,这里提供标准版手机,普通版
public class StandardPhone implements Phone{private String desc = "标准版手机";@Overridepublic String desc() {return desc;}@Overridepublic int getPrice() {return 1000;}}
public class MiddlePhone implements Phone{private String desc = "普通版手机";@Overridepublic String desc() {return desc;}@Overridepublic int getPrice() {return 2000;}}
手机装饰器(这个例子中没怎么使用)
public class PhoneDecorator implements Phone{//装饰器在这里起到一个中间层作用,方便后面的扩展,具体的装饰实现延迟到子类中private String desc = "这是装饰器";@Overridepublic String desc() {return desc;}@Overridepublic int getPrice() {return 0;}}
添加了视频通话的装饰器类
public class VideoDecorator extends PhoneDecorator{private String desc = "增加一个视频通话功能";private int VideoPrice = 500;private Phone phone;public VideoDecorator(Phone phone){this.phone = phone;}@Overridepublic String desc() {return desc;}@Overridepublic int getPrice() {return phone.getPrice()+VideoPrice;}}
增加了wifi充电功能的装饰器类
public class WifiChargeDecorator extends PhoneDecorator{private String desc = "增加一个wifi充电功能";private int WifiPrice = 200; //加价 200private Phone phone;public WifiChargeDecorator(Phone phone){this.phone = phone;}@Overridepublic String desc() {return desc;}@Overridepublic int getPrice() {return phone.getPrice()+WifiPrice;}}
新增的手机的附加功能可以通过多个装饰类实现,方便客户端调用时自由选择,如果有更多的装饰类,可以按照上面的方式继续添加
下面来简单测试下,
1、获取一个普通版手机
public class PhoneTest {public static void main(String[] args) {//测试一下 获取一个标准版手机Phone middlePhone = new MiddlePhone();System.out.println(middlePhone.desc()+", "+middlePhone.getPrice()); //普通版手机, 2000//增加视频通话功能Phone videophone = new VideoDecorator(middlePhone);System.out.println(videophone.desc()+", "+videophone.getPrice());//增加一个视频通话功能, 2500//增加视频通话 和 Wifi充电功能Phone wifi = new WifiChargeDecorator(videophone);System.out.println(wifi.desc()+", "+wifi.getPrice());//增加视频通话 和 wifi充电功能, 2700//只增加wifi充电功能Phone onlywifi = new WifiChargeDecorator(middlePhone);System.out.println(onlywifi.desc()+", "+onlywifi.getPrice()); //增加一个wifi充电功能, 2200}}
装饰器设计模式,优点:
装饰模式与继承关系的目的都是要扩展原有对象的功能,但是装饰器模式比继承增加了更多的灵活性
使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出更多的不同行为的组合,原有的代码无需改变,符合“开闭”原则
