天天看点

架构师备战(三)-软件工程(十四) 设计模式之结构型模式(二)

1、组合模式

简要说明

将对象组合成树形结构以表示“整体-部分”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

速记关键字

树形目录结构

类图如下

架构师备战(三)-软件工程(十四) 设计模式之结构型模式(二)

由类图其实可以看出,组合模式就是将具有父子关系的结构,组装形成一棵树,并且根据规范,树干节点和叶子节点均需要实现统一接口或者继承同一个抽象类。只是各自实现树干和叶子节点的特有功能。下面我们以菜单目录和菜单为例,使用组合模式组装菜单。

Java代码实现

/**
 * 抽象菜单类
 */
 @Data
public abstract class AbstractMenu{
    // 菜单名称或目录名称
    protected String name;

    // 菜单路由
    protected String route;

    // 菜单目录图标
    protected String icon;

    public AbstractMenu(String name) {
        this.name = name;
    }

    public AbstractMenu(String name, String route) {
        this.name = name;
        this.route = route;
    }

    public AbstractMenu(String name, String route, String icon) {
        this.name = name;
        this.route = route;
        this.icon = icon;
    }

    // 菜单目录下可以添加菜单
    public void add(AbstractMenu menu){
        // 抛出不支持操作异常
        throw new UnsupportedOperationException();
    }

    // 菜单目录下可以删除菜单
    public void remove(AbstractMenu menu){
        // 抛出不支持操作异常
        throw new UnsupportedOperationException();
    }

    // 获取菜单信息
    public abstract AbstractMenu getMenu();
}

/**
 * 菜单目录
 */
public class MenuDir extends AbstractMenu  implements Serializable {

    private List<AbstractMenu> children;

    public MenuDir(String name) {
        super(name);
        children = new ArrayList<AbstractMenu>();
    }

    public MenuDir(String name, String route, String icon) {
        super(name, route, icon);
        children = new ArrayList<AbstractMenu>();
    }


    @Override
    public void add(AbstractMenu menuDir) {
        // 添加菜单子目录
        children.add(menuDir);
    }

    @Override
    public void remove(AbstractMenu menuDir) {
        // 删除菜单子目录
        children.remove(menuDir);
    }

    // 获取菜单信息
    public AbstractMenu getMenu() {
        // 当前菜单信息
        final MenuDir menuDir = new MenuDir(name, route, icon);
        // 子菜单信息
        final List<AbstractMenu> children = this.getChildren();
        menuDir.setChildren(children);
        return menuDir;
    }

    public List<AbstractMenu> getChildren() {
        return children;
    }

    public void setChildren(List<AbstractMenu> children) {
        this.children = children;
    }
}

/**
 * 菜单信息,也就是叶子节点
 */
public class Menu extends AbstractMenu implements Serializable {
    public Menu(String name) {
        super(name);
    }

    public Menu(String name, String route) {
        super(name, route);
    }

    public Menu(String name, String route, String icon) {
        super(name, route, icon);
    }

    public AbstractMenu getMenu() {
        return new Menu(getName(), getRoute(), getIcon());
    }
}

/**
 * 测试类 使用组合模式构建菜单
 */
public class Client2 {

    public static void main(String[] args) {
        // 构建菜单结构
        // 创建一级菜单目录
        AbstractMenu sysMgr = new MenuDir("系统管理", "/system", null);
        // 创建二级菜单目录
        AbstractMenu usrMgr = new MenuDir("用户管理", "/system/user", null);
        // 用户管理下面分为 系统用户,会员用户分别做管理,它们都是叶子节点
        Menu employer = new Menu("员工管理", "/system/user/employer", "员工管理图标");
        Menu members = new Menu("会员管理", "/system/user/members", "会员管理图标");

        // 构建关系
        usrMgr.add(employer);
        usrMgr.add(members);
        sysMgr.add(usrMgr);

        final AbstractMenu menu = sysMgr.getMenu();
        System.out.println(menu);
    }

}           

如果通过断点,我们可以非常清晰的看清楚整棵树的结果,非常的清晰的表示了父子结构,展示了整体和部分的逻辑。

2、享元模式

简要说明

提供了大量细粒度对象共享的有效方法

速记关键字

汉字编码,对象共享,对象缓存

类图如下

架构师备战(三)-软件工程(十四) 设计模式之结构型模式(二)

如类图所述,享元模式其实在我们工作中非常常用,虽然您可能不知道这是享元模式,但是它就是。

它广泛运用在池技术里面,比如JDK的String或者Integer的常量池.它们缓存了一部分非常常用的数字或者字符,把它们放在池中,一般先从池中获取,不存在就新建一个对象。新建对象后将新建的对象回填到池中。从而避免大量的对象创建并减少内存的消耗。

这个跟我们的缓存简直是一模一样,我们缓存是热数据,而享元模式缓存的就是热对象。一样的道理。

Java代码实现

/**
 * 享元模式接口
 */
public interface IFlyWeight {
    void test();
}

/**
 * 享元模式接口
 */
@Data
public class FlyWeightImpl implements IFlyWeight{

    private String from;
    private String to;

    public FlyWeightImpl(String from, String to) {
        this.from = from;
        this.to = to;
    }

    public void test() {
        System.out.println("测试从" + from + "到" + to);
    }
}

/**
 * 享元工厂,也就是在这里 我们如果每次都创建对象,那么创建的对象就会很多。如果存在大量的公共信息,那么可有将公共信息抽象出来,然后共享起来
 */
public class FlyWeightFactory {

    private static final Map<String ,IFlyWeight> poll = new ConcurrentHashMap<String, IFlyWeight>();

    public static IFlyWeight getInfo(String from, String to){
        String key = from + "->" + to;
        if (poll.containsKey(key)){
            System.out.println("使用缓存:" + key);
            return poll.get(key);
        }
        System.out.println("首次查询,创建对象:" + key);
        IFlyWeight ticket = new FlyWeightImpl(from, to);
        poll.put(key,ticket);
        return ticket;
    }
}

/**
 * 测试类
 */
public class Client {
    public static void main(String[] args) {
        // 其实享元模式就是将一些共享度比较高(不经常变化,但是创建次数很多的对象)放在资源池(一般是Map<对象唯一标识,对象>或者List<对象>格式)里面。
        // 如果我们资源池子里面没有对象,我们才去创建,创建之后把把对象放入资源池。下次进行时直接从资源池获取,不再重复创建对象。从而减少资源的开销。
        // 一般在JDK常量池,数据库的连接池,我们自己写的代码做旁路缓存模式(先查redis, redis存在,直接返回,不存在,写入redis)是一样道理
        final IFlyWeight info1 = FlyWeightFactory.getInfo("重庆", "北京");
        final IFlyWeight info2 = FlyWeightFactory.getInfo("成都", "北京");
        final IFlyWeight info3 = FlyWeightFactory.getInfo("上海", "北京");
        final IFlyWeight info4 = FlyWeightFactory.getInfo("北京", "深圳");
        final IFlyWeight info5 = FlyWeightFactory.getInfo("重庆", "北京");

        info1.test();
        info2.test();
        info3.test();
        info4.test();
        info5.test();
    }
}              

说白了,享元模式就是给热对象做了一层缓存而已。因为热对象会频繁被创建,但是这个对象本身又不怎么改变,因此缓存起来将会减少内存的开销和效率的提升。

3、外观模式

简要说明

定义一个高层接口,为子系统中的一组接口提供一个一致的外观,从而简化了该子系统的使用。

速记关键字

对外统一接口

类图如下

架构师备战(三)-软件工程(十四) 设计模式之结构型模式(二)

由类图可以看出,其实我们就是将几个类的方法调用放在同一个类中,提供一个统一的调用入口。就跟以前的hao123导航一样,就是一个简单的整合,怕大家不好找,放在一个统一的地方,方便调用而已。可以说是一个接口的门户。

Java代码实现

/**
 * 画形状接口
 */
public interface Shape {
    void draw();
}

public class Rectangle implements Shape{
    public void draw() {
        System.out.println("画长方形");
    }
}

public class Square implements Shape {
    public void draw() {
        System.out.println("画正方形");
    }
}

public class Circle implements Shape{
    public void draw() {
        System.out.println("画圆");
    }
}

// 定义一个统一的类,来画圆形,正方形,长方形
public class ShapeMaker {
   private final Shape circle;
   private final Shape rectangle;
   private final Shape square;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }

  /**
   * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
   */

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        // 创建统一外观
        final ShapeMaker shapeMaker = new ShapeMaker();
        // 使用统一外观进行调用方法
        shapeMaker.drawCircle();
        shapeMaker.drawRectangle();
        shapeMaker.drawSquare();
    }
}           

外观模式非常简单,就不在赘述

4、代理模式

简要说明

为其他对象提供一种代理以控制这个对象的访问

速记关键字

快捷方式,代理公司

类图如下

架构师备战(三)-软件工程(十四) 设计模式之结构型模式(二)

代理模式类图也很简单,就是代理类需要实现源对象的接口,并且组合源对象,然后调用源对象方法。但是在调用源对象前后能做一些不可描述的事情,因此代理类的权限非常之大,它甚至可以不去调用源对象的方法。

代理模式分为静态代理和动态代理。静态代理就是需要一个实实在在的代理类对象存在,这会导致会产生大量的代理对象。于是,动态代理应运而生。动态代理就是不需要你生成代理类了,由代码帮你生成一个。

JDK动态代理就是针对该类是实现了接口的,就可以进行代理,Java自带的功能。

Cglib代理则不需要实现接口,但是需要引入cglib的jar包,但是我们一般的业务实现都是有接口的,两种方式都可以使用,根据情况而定。

Java代码实现

// 静态代理实现
/**
 * 源接口
 */
public interface IUserDao {
    Object getUserInfo();
}

/**
 * 原接口实现类
 */
public class UserDaoImpl implements IUserDao{
    public Object getUserInfo() {
        Map<String, Object> user =  new HashMap<String, Object>();
        user.put("userId", 1);
        user.put("userName", "zhangsan");
        System.out.println("userId:" + user.get("userId"));
        System.out.println("userName:" + user.get("userName"));
        return user;
    }
}

/**
 * 静态代理类
 */
public class UserStaticProxy implements IUserDao{

    private final IUserDao userDao;

    public UserStaticProxy(IUserDao userDao) {
        this.userDao = userDao;
    }

    public Object getUserInfo() {
        System.out.println("我是代理类,我在执行目标方法之前干了点事情");
        final Object userInfo = userDao.getUserInfo();
        System.out.println("我是代理类,我在执行目标方法之后又干了点事情");
        return userInfo;
    }
}

// 静态代理测试
public class Client {
    public static void main(String[] args) {
        final UserStaticProxy userStaticProxy = new UserStaticProxy(new UserDaoImpl());
        final Object userInfo = userStaticProxy.getUserInfo();
    }
}


/**
 * JDK动态代理类 需要实现InvocationHandler接口
 */
public class JdkProxy implements InvocationHandler{
    private Object target;

    // 使用实现InvocationHandler方式
    public Object getInstance(Object object){
        this.target = object;
        // 创建一个代理类,传入类信息,类的接口信息,已经本类的信息
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理开始");
        // 使用反射执行对应的方法
        final Object invoke = method.invoke(target, args);
        System.out.println("JDK动态代理结束");
        return invoke;
    }
}

/**
 * JDK动态代理类 使用静态方法
 */
public class JdkProxyUtil {
    private static final JdkProxy proxy = new JdkProxy();

    public static Object getProxy(Object object){
        return proxy.getInstance(object);
    }
}

/**
 * 这是一个没有接口实现的类
 */
public class UserService {

    public Object getUserInfo(){
        Map<String, Object> user =  new HashMap<String, Object>();
        user.put("userId", 2);
        user.put("userName", "没有实现接口的用户");
        System.out.println("userId:" + user.get("userId"));
        System.out.println("userName:" + user.get("userName"));
        return user;
    }
}

/**
 * 测试类
 */
public class Client {
    public static void main(String[] args) {
        // 有接口的实现
        final IUserDao userDao = new UserDaoImpl();
        final IUserDao userInfo = (IUserDao)JdkProxyUtil.getProxy(userDao);
        userInfo.getUserInfo();


        // jdk获取没有接口的实现类的代理对象, 此时我们将会惨遭报错
        final UserService userService = new UserService();
        final UserService userServiceProxy = (UserService) JdkProxyUtil.getProxy(userService);
        userServiceProxy.getUserInfo();
    }
}


/**
 * cglib动态代理类 需要实现MethodInterceptor接口
 */
public class CglibProxy implements MethodInterceptor {

    // 同理 维护一个对象
    private Object target;

    // 返回一个代理对象:  是 target 对象的代理对象
    public Object getProxyInstance(Object target) {
        this.target = target;
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();

    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("Cglib代理模式开始");
        Object returnVal = method.invoke(target, args);
        System.out.println("Cglib代理模式结束");
        return returnVal;
    }
}

/**
 * Cglib代理工具类
 */
public class CglibProxyUtil {
    private static final CglibProxy proxy = new CglibProxy();

    public static Object getProxy(Object object){
        return proxy.getProxyInstance(object);
    }
}

/**
 * 测试类
 */
public class Client {
    public static void main(String[] args) {
        // Cglib获取有接口的实现类的代理对象
        final IUserDao userDao = new UserDaoImpl();
        final IUserDao userInfo = (IUserDao) CglibProxyUtil.getProxy(userDao);
        userInfo.getUserInfo();

        // Cglib获取没有接口的实现类的代理对象
        final UserService userService = new UserService();
        final UserService userServiceProxy = (UserService) CglibProxyUtil.getProxy(userService);
        userServiceProxy.getUserInfo();
    }
}           

代理模式也很简单,就是我们需要实现对应的接口,写一下代理前后的逻辑即可。最后附上cglib的gav坐标

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.6</version>
</dependency>           

5、小结

至此,设计模式的7中结构性模式就学习完毕了,理解之后其实也比较简单。后买你还有行为型模式,继续学习,继续加油!

继续阅读