文章目录
核心作用:
是从程序的结构,上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
结构型模式分类:
- 适配器模式
- 代理模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
适配器(adapter)模式 (常用)
什么是适配器模式?
- 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
模式中的角色
- 目标接口( Target) :客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
- 需要适配的类( Adaptee) : 需要适配的类或适配者类。
- 适配器( Adapter) :通过包装一个需要适配的对象,把原接口转换成目标接口。
使用场景:
- 经常用来做旧系统改造和升级
- java.io.InputStreamReader(InputStream)
- java.io.OutputStreamWriter(OutputStream)
代码实现:
被适配的类:
/**
* 被适配的类
*/
public class Adaptee {
public void request(){
System.out.println("可以完成客户请求需要的功能");
}
}
接口:
public interface Target {
void handleReq();
}
适配器:
/**
* 适配器(用继承的方式实现,也可以用组合的方式实现)
*/
public class Adapter extends Adaptee implements Target{
@Override
public void handleReq() {
super.request();
}
}
客户端:
/**
* 客户端
*/
public class Client {
public void test1(Target t){
t.handleReq();
}
public static void main(String[] args) {
Client c = new Client();
Adaptee a = new Adaptee();
Target t = new Adapter();
//通过适配器来使用其他类的方法
c.test1(t);
}
}
代理模式 (常用)
作用:
- 通过代理,控制对对象的访问;可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。( 即: AOP的微观实现! )
- AOP(Aspect Oriented Programming面向切面编程)的核心实现机制 !
核心角色:
- 抽象角色:
- 定义代理角色和真实角色的公共对外方法
- 真实角色:
- 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。关注真正的业务逻辑!
- 代理角色:
- 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 将统一的流程控制放到代理角色中处理!
应用场景:
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。(比如你要开发一个大文档查看软件,大文档中有大的图片,有可能个图片有100MB ,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式。当需要查看图片时。用proxy来进行大图片的打开。)
开发框架中应用场景:
- struts2中拦截器的实现
- 数据库连接池关闭处理
- Hibernate中延时加载的实现
- mybatis中实现拦截器插件
- AspectJ的实现
- spring中AOP的实现
- 日志拦截
- 声明式事务处理
- web service
- RMI远程方法调用
分类:
静态代理(静态定义代理类)
代码实现:
抽象类:
public interface Star {
void confer(); //面谈
void signContract(); //签合同
void bookTicket(); //订票
void sing(); //唱歌
void collectMoney(); //收钱
}
真实角色:
/**
* 真实角色
*/
public class RealStar implements Star{
@Override
public void confer() {
System.out.println("RealStar.confer()");
}
@Override
public void signContract() {
System.out.println("RealStar.signContract()");
}
@Override
public void bookTicket() {
System.out.println("RealStar.bookTicket()");
}
@Override
public void sing() {
System.out.println("真实角色实现唱歌功能RealStar.sing()");
}
@Override
public void collectMoney() {
System.out.println("RealStar.collectMoney()");
}
}
代理角色:
/**
* 代理角色
*/
public class ProxyStar implements Star{
private Star star;
public ProxyStar(Star real) {
super();
this.star = real;
}
public ProxyStar(){
}
@Override
public void confer() {
System.out.println("ProxyStar.confer()");
}
@Override
public void signContract() {
System.out.println("ProxyStar.signContract()");
}
@Override
public void bookTicket() {
System.out.println("ProxyStar.bookTicket()");
}
//代理角色不能实现核心功能:唱歌,必须通过star对象调用
public void sing() {
star.sing();
}
@Override
public void collectMoney() {
System.out.println("ProxyStar.bookTicket()");
}
}
测试类:
public class Client {
public static void main(String[] args) {
Star real = new RealStar();
Star proxy = new ProxyStar(real); //对真实角色进行代理
proxy.confer();
proxy.signContract();
proxy.bookTicket();
proxy.signContract();
proxy.sing();
proxy.collectMoney();
}
}
UML图:
动态代理(动态生成代理类):
1.JDK自带的动态代理
- java.lang.reflest.Proxy.
作用:动态生成代理类和对象 - java,lang.reflect.InvocationHandler(处理器接口)
- 可以通过invoke方法实现对真文角色的代理访问。
- 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象
动态代理:在内存中形成代理类
-
实现步骤:
- 代理对象和真实对象实现相同的接口
- 代理对象 = Proxy.newProxyInstance();
- 使用代理对象调用方法。
- 增强方法
-
增强方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
下面用代理卖电脑的方式进行讲解:
接口:
public interface SaleComputer {
public String sale(double money);
public void show();
}
真实类:
package cn.itcast.proxy;
/**
* 真实类
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了"+money+"元买了一台联想电脑...");
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑....");
}
}
实现动态代理:
package cn.itcast.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.动态代理增强lenovo对象
/*
三个参数:
1. 类加载器:真实对象.getClass().getClassLoader()
2. 接口数组:真实对象.getClass().getInterfaces()
3. 处理器:new InvocationHandler()
*/
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
/*
代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
参数:
1. proxy:代理对象
2. method:代理对象调用的方法,被封装为的对象
3. args:代理对象调用的方法时,传递的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*System.out.println("该方法执行了....");
System.out.println(method.getName());
System.out.println(args[0]);
*/
//判断是否是sale方法
if(method.getName().equals("sale")){
//1.增强参数
double money = (double) args[0];
money = money * 0.85;
System.out.println("专车接你....");
//使用真实对象调用该方法
String obj = (String) method.invoke(lenovo, money);
System.out.println("免费送货...");
//2.增强返回值
return obj+"_鼠标垫";
}else{
Object obj = method.invoke(lenovo, args);
return obj;
}
}
});
//3.调用方法
/* String computer = proxy_lenovo.sale(8000);
System.out.println(computer);*/
proxy_lenovo.show();
}
}
2.javaassist字节码操作库实现
3.CGLIB
4.ASM(底层使用指令,可维护性较差)
桥接模式(bridge)
为了方便理解桥接模式解决的问题,下面引入一个场景:
商城系统中常见的商品分类.以电脑为类,如何良好的处理商品分类销售的问题?
我们可以用多层继承结构实现下图的关系。
多层继承结构带来的问题:
- 扩展性问题(类个数膨胀问题) :
- 如果要增加一个新的电脑类型:智能手机,则要增加各个品牌下面的类。如果要增加一个新的品牌,也要增加各种电脑类型的类。
- 违反单一职责原则:
- 一个类:联想笔记本,有两个引起这个类变化的原因(品牌和机型)
场景分析:
- 商城系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题?
- 这个场景中有两个变化的维度:电脑类型、电脑品牌。
- 我们可以按照这两个维度作为划分,每一个维度建立一系列的类;然后将两个维度结构桥接起来(用组合代替继承)。当需要添加品牌和机型时,在对应的维度添加类即可。
桥接模式核心要点:
- 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。
桥接模式实际开发中应用场景:
-
JDBC驱动程序。
-
AWT中的Peer架构
-
银行日志管理:
- 格式分类:操作日志、交易日志、异常日志
- 距离分类:本地记录日志、异地记录日志
-
人力资源系统中的奖金计算模块:
- 奖金分类:个人奖金、团体奖金、激励奖金。
- 部门分类:人事部门、销售部门、研发部门。
-
OA系统中的消息处理:
- 业务类型:普通消息、加急消息、特急消息
- 发送消息方式:系统内消息、手机短信、邮件
代码实现:
品牌维度:
package 桥接模式;
/**
* 品牌类型维度
* @author Administrator
*
*/
public interface Brand {
void sale();
}
class Lenovo implements Brand{
@Override
public void sale() {
System.out.println("销售联想电脑");
}
}
class Dell implements Brand{
@Override
public void sale() {
System.out.println("销售Dell电脑");
}
}
class Shenzhou implements Brand{
@Override
public void sale() {
System.out.println("销售Shenzhou电脑");
}
}
机型维度:
package 桥接模式;
/*
* 电脑类型维度
*/
public class Computer {
protected Brand brand;
public Computer(Brand b){
this.brand = b;
}
public void sale(){
brand.sale();
}
}
class Desktop extends Computer{
public Desktop(Brand b) {
super(b);
}
public void sale(){
super.sale();
System.out.println("销售台式电脑");
}
}
class Laptop extends Computer{
public Laptop(Brand b) {
super(b);
}
public void sale(){
super.sale();
System.out.println("销售笔记本电脑");
}
}
测试类:
public class Client {
public static void main(String[] args) {
//销售联想的笔记本电脑
Computer c = new Laptop(new Lenovo());
c.sale();
//销售神舟的台式机
Computer c2 = new Desktop(new Shenzhou());
c2.sale();
}
}
桥接模式总结:
- 桥接模式可以取代多层继承的方案。多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本。
- 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。
组合模式(composite)
使用组合模式的场景:
- 把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。(比如吧文件夹看做一个特殊的文件)
组合模式核心:
- 抽象构件(Component)角色:定义了叶子和容器构件的共同点
- 叶子(Leaf)构件角色:无子节点
- 容器(Composite)构件角色:有容器特征,可以包含子节点
开发中的应用场景:
- 操作系统的资源管理器
- GUI中的容器层次图
- XML文件解析
- 0A系统中,组织结构的处理
- Junit单元测试框架
底层设计就是典型的组合模式, TestCase(叶子)、TestUnite(容器)、Test接口(抽象)
常见代码形式:
package 组合模式;
/**
* 抽象组件
*/
public interface Component {
void operation();
}
//叶子组件
interface Leaf extends Component{
}
//容器组件
interface Composite extends Component{
void add(Component c);
void remove(Component c);
Component getChild(int index);
}
下面模拟一个杀毒软件,文件作为叶子,文件夹作为容器。代码如下:
package 组合模式;
import java.util.ArrayList;
import java.util.List;
//抽象构建
public interface AbstractFile {
void killVirus(); //杀毒
}
//图片文件
class ImageFile implements AbstractFile{
private String name;
public ImageFile(String name) {
super();
this.name = name;
}
@Override
public void killVirus() {
System.out.println("-----图像文件"+name+"进行查杀");
}
}
//文本文件
class TextFile implements AbstractFile{
private String name;
public TextFile(String name) {
super();
this.name = name;
}
@Override
public void killVirus() {
System.out.println("-----文本文件"+name+"进行查杀");
}
}
//视频文件
class VideoFile implements AbstractFile{
private String name;
public VideoFile(String name) {
super();
this.name = name;
}
@Override
public void killVirus() {
System.out.println("-----视频文件"+name+"进行查杀");
}
}
//文件夹,
class Folder implements AbstractFile{
private String name;
//定义容器,用来存放本容器下的子节点
private List<AbstractFile> list = new ArrayList<AbstractFile>();
public Folder(String name) {
super();
this.name = name;
}
public void add(AbstractFile file){
list.add(file);
}
public void remove(AbstractFile file){
list.remove(file);
}
public AbstractFile getChild(int index){
return list.get(index);
}
@Override
public void killVirus() {
System.out.println("---文件夹:"+name+"进行查杀");
for(AbstractFile file:list){
file.killVirus();
}
}
}
测试类:
package 组合模式;
public class Client {
public static void main(String[] args) {
AbstractFile f2, f3, f4, f5;
Folder f1 = new Folder("我的收藏");
f2 = new ImageFile("大头照.jpg");
f3 = new TextFile("Hello.txt");
f1.add(f2);
f1.add(f3);
Folder f11 = new Folder("电影");
f4 = new VideoFile("笑傲江湖.avi");
f5 = new VideoFile("神雕侠侣.avi");
f11.add(f4);
f11.add(f5);
f1.add(f11);
f1.killVirus();
}
}
测试结果:
---文件夹:我的收藏进行查杀
-----图像文件大头照.jpg进行查杀
-----文本文件Hello.txt进行查杀
---文件夹:电影进行查杀
-----视频文件笑傲江湖.avi进行查杀
-----视频文件神雕侠侣.avi进行查杀
装饰器模式(decorator) (常用)
作用:
- 动态的为一个对象增加新的功能。
- 装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
开发中使用的场景:
- IO中输入流和输出流的设计
- Swing包中图形界面构件功能
- Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper ,HttpServletRequestWrapper类,增强了request对象的功能。
- Struts2中 ,request,response,session对象的处理
实现细节:
- Component抽象构件角色:
真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。 - ConcreteComponent 具体构件角色(真实对象) :
io流中的FileInputStream、 FileOutputStream - Decorator装饰角色 :
持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。 - ConcreteDecorator具体装饰角色:
负责给构件对象增加新的责任。
优点:
- 扩展对象功能,比继承灵活,不会导致类个数急剧增加
- 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更
加强大的对象 - 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类。
缺点:
- 产生很多小对象。大量小对象占据内存, 一定程度上影响性能。
- 装饰模式易于出错,调试排查比较麻烦。
装饰模式和桥接模式的区别:
- 两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。
下面通过给汽车添加新功能,体现装饰模式:
代码实现:
package 装饰模式;
/*
* 抽象构建
*/
public interface ICar {
void move();
}
// ConcreteComponent 具体构件角色(真实对象)
class Car implements ICar {
@Override
public void move() {
System.out.println("陆地上跑!");
}
}
//Decorator装饰角色
class SuperCar implements ICar{
private ICar car;
public SuperCar(ICar car) {
super();
this.car = car;
}
@Override
public void move() {
car.move();
}
}
//ConcreteDecorator具体装饰角色
class FlyCar implements ICar{
private ICar car;
public FlyCar(ICar car) {
super();
this.car = car;
}
public void fly(){
System.out.println("天上飞");
}
@Override
public void move() {
car.move();
fly();
}
}
//ConcreteDecorator具体装饰角色
class WaterCar implements ICar{
private ICar car;
public WaterCar(ICar car) {
super();
this.car = car;
}
public void wsim(){
System.out.println("水上游! ");
}
@Override
public void move() {
car.move();
wsim();
}
}
//ConcreteDecorator具体装饰角色
class AICar implements ICar{
private ICar car;
public AICar(ICar car) {
super();
this.car = car;
}
public void autoMove(){
System.out.println("自动驾驶");
}
@Override
public void move() {
car.move();
autoMove();
}
}
测试类:
public class Client {
public static void main(String[] args) {
Car car = new Car();
car.move();
System.out.println("增加新的功能,飞行----------" );
FlyCar flycar = new FlyCar(car);
flycar.move();
System.out.println("增加新的功能,水里游--------");
WaterCar waterCar = new WaterCar(car);
waterCar.move();
System.out.println("增加两个新的功能,飞行,水里游----------");
WaterCar waterCar2 = new WaterCar(new FlyCar(car));
waterCar2.move();
}
}
UML图:
外观模式
迪米特法则(最少知识原则) :
- 一个软件实体应当尽可能少的与其他实体发生相互作用。
外观模式核心:
- 为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。
开发中常见的场景:
频率很高。哪里都会遇到。各种技术和框架中,都
有外观模式的使用。如:
- JDBC封装后的, commons提供的DBUtils类,
- Hibernate提供的工具类、Spring JDBC工具类等
下面以注册公司流程进行举例:
不使用外观模式的情况:
使用外观模式后:
享元模式(FlyWeight)
使用场景:
- 内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存(时间换空间)。
核心:
- 享元模式以共享的方式高效地支持大量细粒度对象的重用。
- 享元对象能做到共享的关键是区分了内部状态和外部状态。
- 内部状态:可以共享,不会随环境变化而改变
- 外部状态:不可以共享,会随环境变化而改变
- 比如围棋中的每一个棋子都是一个对象,它们的具有相同的属性:颜色、形状、大小,这些事可以共享的,称之为内部状态;但是每一个棋子的位置是不一样的,这是不可以共享的,称之为外部状态。
享元模式实现:
- FlyweightFactory享元工厂 类
创建并管理享元对象,享元池一般设计成键值对 - FlyWeight抽象享元类
通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。 - ConcreteFlyWeight具体享元类
, 为为部状态提供成员变量进行存储 - UnsharedConcreteFlyWeight非共享享元类
不能被共享的子类可以设计为非共享享元类
享元模式开发中应用的场景:
- 元模式由于其共享的特性,可以在任何“池”中操作
比如:线程池、数据库连接池。 - String类的设计也是享元模式
优点:
- 极大减少内存中对象的数量
- 相同或相似对象内存中只存一-份 ,极大的节约资源,提高系统性能
- 外部状态相对独立,不影响内部状态
缺点:
- 模式较复杂,使程序逻辑复杂化
- 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。
下面按照棋子关系展示代码的实现:
package 享元模式;
/**
* 享元类
*/
public interface ChessFlyWeight {
void setColor();
String getColor();
void display(Coordinate c);
}
//具体享元类, 为为部状态提供成员变量进行存储
class ConcreateChess implements ChessFlyWeight{
private String color;
public ConcreateChess(String color) {
super();
this.color = color;
}
@Override
public void setColor() {
}
@Override
public String getColor() {
return color;
}
@Override
public void display(Coordinate c) {
}
}
坐标类:
package 享元模式;
/**
* 坐标类
*/
public class Coordinate {
private int x, y;
public Coordinate(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
享元工厂:
package 享元模式;
import java.util.HashMap;
import java.util.Map;
/**
* 享元工厂类
*/
public class ChessFlyWeightFactory {
//享元池
private static Map<String, ChessFlyWeight> map = new HashMap<String, ChessFlyWeight>();
//获得棋子
public static ChessFlyWeight getChess(String color){
if(map.get(color)!=null){
return map.get(color);
}else{
ChessFlyWeight cfw = new ConcreateChess(color);
map.put(color, cfw);
return cfw;
}
}
}
测试类:
public class Client {
public static void main(String[] args) {
ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色");
ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色");
System.out.println(chess1);
System.out.println(chess2);
System.out.println("增加外部状态的处理===============");
chess1.display(new Coordinate(10, 10));
chess2.display(new Coordinate(10, 10));
}
}
UML图:
结构型模式总结:
- 结构型模式关注对象和类的组织。