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