design pattern我爱编程Java设计模式

实际项目运用之Adapter模式(适配器模式)

2018-05-28  本文已影响71人  南乡清水

1. 模式简介

适配器模式解决的问题:让原本因为接口不兼容而不能一起工作的类可以一起工作

适配器模式中有三种角色:

适配器模式的优点:

通过适配器模式,用户在做相似的操作时可以调用同一个接口,其内部过程对于用户是透明的,这样做更简单、更直接、更解耦;
复用了现存的类,解决了现存类和复用环境要求不一致的问题;
将目标接口和现有接口解耦,通过引入一个适配器类,而无需修改原有的代码。

适配器模式的缺点:

使用适配器模式后,如果想要改变适配对象,就需要更换适配器,而更换适配器是一个非常复杂的过程。

适配器模式的适用场景:

当系统需要使用现有的类,而现有的类不符合系统的接口
当期望的功能和系统中现有的某个类的功能相似,但是具有不同的接口
当系统已经实现某功能,但用户想通过另种接口方式访问,而不想修改原有接口
当使用的第三方组件的接口和系统中定义好的接口不同,不希望修改系统接口

2.案例代码

适配器分类适配器模式对象适配器模式

2.1 类适配器UML

类适配器类图

2.2 对象适配器模式UML

对象适配器类图

2.3 案例代码:

被适配对象

public class Adaptee {
    public void adapteeMethod() {
        System.out.println("这是我们已经实现的功能!");
    }
}

对象适配器

public class Adapter implements Target {

    private Adaptee adaptee = new Adaptee();

    @Override
    public void targetMethod() {
        adaptee.adapteeMethod();
    }
}

目标接口

public interface Target {
    void targetMethod();
}

客户端调用

public class Client {

    public static void main(String[] args) {
        Target target = new Adapter();
        target.targetMethod();
        
    }
}

3.JAVA源码中的运用

有时候我们需要把集合变成线程安全的集合

List<Integer> list = Collections.synchronizedList(new ArrayList<>(10));      
Map<String, String> map = Collections.synchronizedMap(new HashMap<>(8));

就拿Collections.synchronizedMap()来分析源码,其中

目标接口

Map<K,V> m

不多做说明,大家应该都知道这是字典的接口

被适配对象

new HashMap<>(8)

同步适配器 SynchronizedMap

    private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }
 public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        
//////////////此处省略方法
}

很容易理解,在调用 synchronizedMap方法

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

创建了一个同步适配器对象,其实就是注册了一把mutex锁

SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

然后在调用具体方法的时候会竟如同步块,比如put方法

 public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        

通过这种方式实现了map的线程安全同步,Collections.synchronizedList方法同理

4. 实际项目中运用

背景:有时候对于类的toString方法需要做修改,如果属性为空则赋值一些默认值,或者字符串为空的时候返回的是空字符串等,对此可以进行一些优化

抽象父类AbstractDO

@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = {"id"})
public abstract class AbstractDO implements Serializable {

    private static final long serialVersionUID = -1679770357930200297L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Date createTime;

    private Date updateTime;

}

具体实体类SysResources

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysResources extends AbstractDO {

    private static final long serialVersionUID = 1936021577348624761L;

    /**
     * 资源名
     */
    private String name;

    /**
     * 资源类型
     */
    private String type;

    /**
     * 资源地址
     */
    private String url;

    /**
     * 资源权限
     */
    private String permission;

    /**
     * 父级资源
     */
    private Long parentId;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 是否外部链接
     */
    private Boolean external;

    private Boolean available;

    /**
     * 资源图标
     */
    private String icon;

    @Transient
    private String checked;

    @Transient
    private SysResources parent;

    @Transient
    @Singular
    private List<SysResources> nodes;

    @Override
    public String toString() {
        //正常字符串拼接 省略
    }
}

首先我的想法是不希望每次新建一个属性的时候去重新生成toString方法,当然你可以使用lombok的ToString注解去解决问题,下面说下我的思路:

目标对象 AdapteeTarget

public interface AdapteeTarget {

    @Override
    String toString();

    /**
     * StringBuilder拼接字符串
     * @param capacity 初始化容量
     */
    default String builderToString(int capacity) {
        final StringBuilder builder = new StringBuilder(capacity);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass(), Object.class);
            PropertyDescriptor[] list = beanInfo.getPropertyDescriptors();
            builder.append(beanInfo.getBeanDescriptor().getName()).append("{");
            for (int i = 0; i < list.length; i++) {
                PropertyDescriptor descriptor = list[i];
                if (i > 0) {
                    builder.append(", ");
                }
                builder.append(descriptor.getName()).append("=").append(descriptor.getReadMethod().invoke(this));
            }
            builder.append("}");
        } catch (IntrospectionException | ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

}

上面接口使用了java8接口的新特性和java内省知识对于javabean对象的获取

首先基于类适配器思想,让 SysResources去实现AdapteeTarget这个接口,并重写toString方法。代码如下:

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysResources extends AbstractDO implements AdapteeTarget{

    private static final long serialVersionUID = 1936021577348624761L;

    //关键代码。。。。。。。。。。。。
    @Override
    public String toString() {
        return this.builderToString(1 << 10);
    }
}

这样如果项目中有很多实体类的时候,只需实现AdapteeTarget接口,覆写toString方法使用默认的this.builderToString方法就可以很方便解决问题。当然你可以说虽然你为了使用stringbuilder去优化字符串拼接,但是同时使用了反射,性能上并不能多大提升。确实,这样的写法只是减少对象的创建,但是性能不是最好的。

但是,如果需要对toString方法有些定制化需求,也是一种选择。比如我们需要String类型的字段默认值为空字符串,那么我们可以创建如下适配器:

字符串空适配器 StringAdapter

public class StringAdapter<T> implements AdapteeTarget {

    private T t;

    private int capacity;

    public StringAdapter(T t, int capacity) {
        this.t = t;
        this.capacity = capacity;
    }

    @Override
    public String toString() {
       return ToStringUtil.getObjectString(t, capacity);
    }
}

ToStringUtil工具类

public final class ToStringUtil {

    /**
     * 字符串属性默认为""
     */
    public static String getObjectString(Object t, int capacity) {
        final StringBuilder builder = new StringBuilder(capacity);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(), Object.class);
            PropertyDescriptor[] list = beanInfo.getPropertyDescriptors();
            builder.append(beanInfo.getBeanDescriptor().getName()).append("{");
            for (int i = 0; i < list.length; i++) {
                PropertyDescriptor descriptor = list[i];
                if (i > 0) {
                    builder.append(", ");
                }
                builder.append(descriptor.getName()).append("=");
                Object o = descriptor.getReadMethod().invoke(t);
                if (descriptor.getPropertyType() == String.class && Objects.isNull(o)) {
                    builder.append("''");
                }else {
                    builder.append(o);
                }

            }
            builder.append("}");
        } catch (IntrospectionException | ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

    /**
     *设置属性默认之短
     */
    public static String getObjectStringNotEmpty(Object t, int capacity, Map<Class, Object> map) {

        if (CollectionUtils.isEmpty(map)) {
            return getObjectString(t, capacity);
        }
        final StringBuilder builder = new StringBuilder(capacity);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(), Object.class);
            PropertyDescriptor[] list = beanInfo.getPropertyDescriptors();
            builder.append(beanInfo.getBeanDescriptor().getName()).append("{");
            for (int i = 0; i < list.length; i++) {
                PropertyDescriptor descriptor = list[i];
                if (i > 0) {
                    builder.append(", ");
                }
                builder.append(descriptor.getName()).append("=");
                Object o = descriptor.getReadMethod().invoke(t);
                Class type = descriptor.getPropertyType();
                if (Objects.isNull(o) && map.containsKey(type)) {
                    builder.append(map.get(type));
                } else {
                    builder.append(o);
                }

            }
            builder.append("}");
        } catch (IntrospectionException | ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

}

我们按对象适配器的方式改造SysResources

public class SysResources extends AbstractDO {

    @Override
    public String toString() {
        AdapteeTarget target = new StringAdapter<>(this, 1 << 7);
        return target.toString();
    }

    public static void main(String[] args) {
        System.out.println(new SysResources());
    }
}

创建一个空对象,打印接口如下:

SysResources{available=null, checked='', createTime=null, external=null, icon='', id=null, name='', nodes=null, parent=null, parentId=null, permission='', sort=null, type='', updateTime=null, url=''}

String字段的默认值都变成了空字符串


当然我们也有其他需求,比如修改其他类型的默认值,那我们可以创建如下适配器:

默认值自定义适配器DefaultValueAdapter

@AllArgsConstructor
public class DefaultValueAdapter<T> implements AdapteeTarget {

    private T t;

    private int capacity;

    private Map<Class, Object> map;

    @Override
    public String toString() {
        return ToStringUtil.getObjectStringNotEmpty(t, capacity, map);
    }
}

然后在重写 SysResources的toString方法

@Override
    public String toString() {
        Map<Class, Object> map = new HashMap<>(5);
        map.put(Boolean.class, false);
        map.put(Long.class, -1L);
        map.put(List.class, new ArrayList<>(1));
        AdapteeTarget target = new DefaultValueAdapter<>(this, 1 << 10, map);
        return target.toString();
    }

new一个对象得到的结果如下:

SysResources{available=false, checked=null, createTime=null, external=false, icon=null, id=-1, name=null, nodes=[], parent=null, parentId=-1, permission=null, sort=null, type=null, updateTime=null, url=null}

Long类型的默认值为-1,Boolean对象默认值是 false

另外当我们还有其他需求,比如toString方法只打印部分字段那么又必须写新的类,当适配器数量到达一定程定,我们可以参考Collections工具类考虑使用简单工厂方法:

适配器AdapterFactory

public final class AdapterFactory {

    //默认值设定
    public static  String builderDefaultValueAdapter(Map<Class,Object> map, int capacity, Class cls) {
        return new DefaultValueAdapter(cls, capacity, map).toString() ;
    }
   //空字符串设定
    public static String builderStringValueAdapter(Class cls, int capacity) {
        return new StringAdapter(cls, capacity).toString();
    }

    @AllArgsConstructor
    private static class DefaultValueAdapter implements AdapteeTarget {

        private Class t;

        private int capacity;

        private Map<Class, Object> map;


        @Override
        public String toString() {
            return ToStringUtil.getObjectDefaultValue(t, capacity, map);
        }
    }

    private static class StringAdapter implements AdapteeTarget {

        private Class t;

        private int capacity;

        public StringAdapter(Class t, int capacity) {
            this.t = t;
            this.capacity = capacity;
        }

        @Override
        public String toString() {
            return ToStringUtil.getObjectStringValue(t, capacity);
        }
    }

}

那么需要改进toString方法,如下:

    @Override
    public String toString() {
//        Map<Class, Object> map = new HashMap<>(5);
//        map.put(Boolean.class, false);
//        map.put(Long.class, -1L);
//        map.put(List.class, new ArrayList<>(1));
//        return AdapterFactory.builderDefaultValueAdapter(map, 1 << 10, SysResources.class);
        return AdapterFactory.builderStringValueAdapter(SysResources.class, 1 << 10);
    }

这样就可以通过简单工厂调用具体的适配器,这样写的好处就是可以减少创建多个类文件,防止类爆炸。当然如果对于重写equalsandhashcode方法也需要不同的算法的场景,则可以使用策略模式,然后再使用工厂方法去实现单工厂多个模式生产线

上一篇下一篇

猜你喜欢

热点阅读