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中结构性模式就学习完毕了,理解之后其实也比较简单。后买你还有行为型模式,继续学习,继续加油!