天天看點

架構師備戰(三)-軟體工程(十四) 設計模式之結構型模式(二)

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中結構性模式就學習完畢了,了解之後其實也比較簡單。後買你還有行為型模式,繼續學習,繼續加油!

繼續閱讀