文章目录
- 一、结构型设计模式
- 二、适配器模式
- 三、桥接模式
- 四、组合模式
- 五、享元模式
- 六、装饰者模式
- 七、外观模式
- 八、代理设计模式
一、结构型设计模式
这篇文章我们来讲解下结构型设计模式,结构型设计模式,主要处理类或对象的组合关系,为如何设计类以形成更大的结构提供指南。
结构型设计模式包括:适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern)
二、适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。
优点:
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 增加了类的透明度。
- 灵活性好。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,在视频播放器中,假设视频播放器只能播放MP4格式的视频,那现在又有个VLC格式的视频,就不能播放了,那要如何解决这个问题?如果我们做个转换器,将VLC格式的视频转换为MP4格式的视频不就可以播放了吗,那这个转换器我们就可以采用适配器设计模式来设计。
下面使用程序演示下上面的例子:
- 定义视频接口
public interface VideoInterFace {
String getVideoPath();
}
- 定时Mp4格式视频实例
public class Mp4Video implements VideoInterFace {
@Override
public String getVideoPath() {
return "Mp4视频的路径";
}
}
- 定义VLC格式视频实例
public class VlcVideo implements VideoInterFace{
@Override
public String getVideoPath() {
return "Vlc视频的路径";
}
}
- 定义播放器,只接口Mp4格式的视频
public class Player {
private Mp4Video video;
public Player(Mp4Video video) {
this.video = video;
}
public void play() {
System.out.println(StringFormatter.concat("播放视频视频地址:", video.getVideoPath()).getValue());
}
}
- 需要播放VLC格式的视频,定义Mp4的适配器,并接收VLC格式视频,进行转码。
public class Mp4Adapter extends Mp4Video {
private VlcVideo vlcVideo;
public Mp4Adapter(VlcVideo vlcVideo) {
this.vlcVideo = vlcVideo;
}
@Override
public String getVideoPath() {
System.out.println(StringFormatter.concat("开始格式转换,vlc地址:", vlcVideo.getVideoPath()).getValue());
return "转换后的Mp4路径!";
}
}
- 测试
public class demo {
public static void main(String[] args) {
Player player = new Player(new Mp4Video());
player.play();
VlcVideo vlcVideo = new VlcVideo();
Player player1 = new Player(new Mp4Adapter(vlcVideo));
player1.play();
}
}
从上面的例子可以看出,需要播放VLC格式,就需要写一个目标适配器,这里是Mp4适配器,并继承Mp4,使之有Mp4的特性,并在内部做相应的转换即可,提高了系统的可扩展性。
三、桥接模式
桥接模式(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
优点:
- 抽象和实现的分离。
- 优秀的扩展能力。
- 实现细节对客户透明。
举个例子:绘画不同颜色的各种图像,画不同的形状和涂颜色,便是两个不同的功能,但两者又相互联系,在画完形状后需要涂颜色,但颜色和形状有使多种多样的,此时就可以采用桥接设计模式,将两者的抽象化与实现化解耦,形状和颜色可以独立变化。
下面使用程序演示下上面的例子:
- 定义颜色的接口
public interface ColorApi {
public void drawCircle();
}
- 定义不同颜色的实现,这里采用红色和绿色
public class ReqColor implements ColorApi {
@Override
public void drawCircle() {
System.out.println("开始涂红色!");
}
}
public class GreenColor implements ColorApi {
@Override
public void drawCircle() {
System.out.println("开始涂绿色!");
}
}
- 定义形状的接口
public interface ShapeApi {
//画形状
void draw();
//画形状并涂颜色
void drawShapeAndsColor();
}
- 定义形状的抽象模板,将共性的操作定义到抽象中
public abstract class ShapeAbstract implements ShapeApi {
public ColorApi colorApi;
public ShapeAbstract(ColorApi colorApi) {
this.colorApi = colorApi;
}
@Override
public void drawShapeAndsColor() {
draw();
colorApi.drawCircle();
}
}
- 定义圆形的实例
public class Circle extends ShapeAbstract {
public Circle(ColorApi colorApi) {
super(colorApi);
}
@Override
public void draw() {
System.out.println("开始画圆形!");
}
}
- 定义矩形的实例
public class Rectangle extends ShapeAbstract {
public Rectangle(ColorApi colorApi) {
super(colorApi);
}
@Override
public void draw() {
System.out.println("开始画矩形");
}
}
- 演示
public class demo {
public static void main(String[] args) {
ShapeApi shapeReq = new Circle(new ReqColor());
shapeReq.drawShapeAndsColor();
ShapeApi shapeGreen = new Circle(new GreenColor());
shapeGreen.drawShapeAndsColor();
ShapeApi rectangle = new Rectangle(new GreenColor());
rectangle.drawShapeAndsColor();
}
}
上面可以看出,可以灵活的定义形状和颜色的组合,并且他们两个都可以独立变化,添加新的形状只需,建立新的类并实现形状接口,添加颜色也是如此,极大的提高的系统的可扩展性和可维护型。
四、组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。
组合模式依据树形结构来组合对象,用来表示部分以及整体层次。它创建了对象组的树形结构。
优点:
- 高层模块调用简单。
- 节点自由增加。
缺点:
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
举个例子:一个公司,从上到下分为,公司、部门、小组等,他们整个在一起才能称为一个完整的公司,要表示做个公司的结构,就可以采用组合设计模式。
下面使用程序演示下上面的例子:
- 定义属性类,用来表示不同层级的对象
@Data
public class Property {
private String name;
//下一层的子集
private List<Property> next;
public Property(String name) {
this.name = name;
next = new ArrayList<Property>();
}
public void add(Property e) {
next.add(e);
}
public void remove(Property e) {
next.remove(e);
}
public List<Property> getSubordinates(){
return next;
}
}
- 使用演示
public class demo {
public static void main(String[] args) {
Property company = new Property("公司");
Property department = new Property("部门");
Property group = new Property("小组");
company.add(department);
department.add(group);
System.out.println(company);
}
}
上面就演示了一个公司的组合,通过company
对象就可以获得整个公司的各个部分的对象。组合设计模式主要适合于整体部分的场景。
五、享元模式
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。他的优点是大大减少对象的创建,降低系统的内存,使效率提高,但也有可能造成内存的浪费。比如Spring的采用容器的方式存储bean,使用时从容器中获取。
还是拿画不同颜色形状的例子演示下享元设计模式的使用:
- 定义形状的接口
public interface Shape {
void draw();
}
- 定义圆形的实现,并接收一个颜色值:
public class Circle implements Shape {
private String color;
public Circle(String color) {
this.color = color;
}
@Override
public void draw() {
System.out.println(StringFormatter.concat("开始画 ", color, " 色的圆 ").getValue());
}
}
- 定义形状对象获取工厂,根据颜色值将对象存储到HashMap中,后再根据颜色值取对象,达到复用的效果。
public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
if (!circleMap.containsKey(color)) {
System.out.println(StringFormatter.concat(">> 创建", color, "颜色的圆 ").getValue());
circleMap.put(color, new Circle(color));
}
return circleMap.get(color);
}
}
- 使用
public class demo {
private static final String colors[] =
{"Red", "Green", "Blue", "White", "Black"};
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Shape circle = ShapeFactory.getCircle(getRandomColor());
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int) (Math.random() * colors.length)];
}
}
上面可以看出,享元设计模式可以大大减少对象的创建,但也有可以造成内存的浪费,比如某个对象的使用频率非常低,如果一直存在内存中就有点浪费空间了。
六、装饰者模式
装饰者模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装。
装饰类和被装饰类可以独立发展,不会相互耦合,装饰者模式是继承的一个替代模式,装饰者模式可以动态扩展一个实现类的功能。
举个例子:还是绘画不同的形状的例子,加入系统中有画各种形状的功能,但随着功能后期的演化,需要画出带有边框的各种形状,那么此时就可以采用装饰者设计模式来做增强。
下面使用程序演示下上面的例子:
- 定义形状接口
public interface Shape {
void draw();
}
- 定义圆形的实现
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("开始画圆形!");
}
}
- 定义矩形的实现
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("开始画矩形!");
}
}
- 定义装饰器的抽象模板
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
@Override
public void draw(){
decoratedShape.draw();
}
}
- 定义具体的边框装饰器
public class BorderShapeDecorator extends ShapeDecorator {
public BorderShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("画边框!");
}
}
- 演示
public class demo {
public static void main(String[] args) {
Shape circle = new Circle();
circle.draw();
Shape shape = new BorderShapeDecorator(new Circle());
shape.draw();
Shape shape1 = new BorderShapeDecorator(new Rectangle());
shape1.draw();
}
}
上面可以看出再不改变原先类的基础上,做了画边框的效果,对原有做增强,使用装饰者设计模式,可以大大提高系统的可扩展性。
七、外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
它的优点是可以减少系统相互依赖、提高灵活性、提高了安全性。但是它不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
举个例子:画各种图形的例子,比如要画圆形、矩形、三角形,每画一种图像都要拿到对应的抽象,并调用绘制方法,如果要画的形状过多,这么多的抽象就不好管理了,而如果使用外观设计模式,提供一个统一的抽象,在这个抽象中就可以完成上面不同的绘制,这样就方便了我们的管理。
下面使用程序演示下上面的例子:
- 定义形状的接口
public interface Shape {
void draw();
}
- 定义圆形的实例
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("开始画圆!");
}
}
- 定义矩形的实例
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("开始画矩形!");
}
}
- 定义三角形的实例
public class Triangle implements Shape {
@Override
public void draw() {
System.out.println("开始画三角形!");
}
}
- 定义一个外观类,并调用上面的功能
public class ShapeFacade {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeFacade() {
circle = new Circle();
rectangle = new Rectangle();
square = new Triangle();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
- 演示
public class demo {
public static void main(String[] args) {
ShapeFacade shapeFacade = new ShapeFacade();
shapeFacade.drawCircle();
shapeFacade.drawRectangle();
shapeFacade.drawSquare();
}
}
外观设计模式还是比较容易理解的,就是把多个功能统一整个到一个对象中,由这个对象再去调用具体的类和方法。
八、代理设计模式
代理设计模式通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既(AOP微实现) 。
代理有分静态代理和动态代理:
- 静态代理:在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
- 动态代理:是在使用时,动态的生成代理对象,他是在内存中构建代理对象的。
举个例子,在做数据库操作时,一般我们都会在事物中做SQL的操作,那就需要在操作前开启事物,操作后如果成功就需要提交事物,如果代用代理设计模式,就可以将事物开启提交逻辑放在代理类中,被代理的类,只需要关注业务逻辑即可。
下面以支付和事物为例演示下代理模式
采用静态代理,实现上面例子:
- 定义支付接口
public interface PayInterFace {
void pay();
}
- 定义微信支付实现
public class WxPay implements PayInterFace {
@Override
public void pay() {
System.out.println("支付中...");
}
}
- 定义支付的代理类
public class PayProxy implements PayInterFace {
private WxPay pay;
public PayProxy(WxPay pay) {
this.pay = pay;
}
@Override
public void pay() {
System.out.println("事物开始!");
pay.pay();
System.out.println("提交事物!");
}
}
- 演示
public class demo {
public static void main(String[] args) {
PayInterFace pay = new PayProxy(new WxPay());
pay.pay();
}
}
上面的静态代理,可以看出,我们需要对每个被代理对象设计一个代理类,如果代理的功能非常多,那就需要开发人员写特别多的代理类,下面可以看下动态代理的使用。
采用动态代理,实现上面例子:
这里使用JDK自带的动态代理来实现
- 再定义一个支付宝的支付实现
public class ZfbPay implements PayInterFace {
@Override
public void pay() {
System.out.println("支付宝支付中...");
}
}
- 定义代理对象,采用jdk的
InvocationHandler
接口
public class PayProxy implements InvocationHandler {
private Object object;
public PayProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事物开始!");
Object result = method.invoke(object, args);
System.out.println("提交事物!");
return result;
}
}
- 演示
public class demo {
public static void main(String[] args) {
PayInterFace pay = (PayInterFace) Proxy.newProxyInstance(
PayInterFace.class.getClassLoader(),
new Class[]{PayInterFace.class},
new PayProxy(new WxPay()));
pay.pay();
PayInterFace pay1 = (PayInterFace) Proxy.newProxyInstance(
PayInterFace.class.getClassLoader(),
new Class[]{PayInterFace.class},
new PayProxy(new ZfbPay()));
pay1.pay();
}
}
上面使用一个代理类,代理了多个对象,相对于静态代理,是代码更简介,灵活性也更高。