这篇文章我们来讲解下结构型设计模式,结构型设计模式,主要处理类或对象的组合关系,为如何设计类以形成更大的结构提供指南。
结构型设计模式包括:适配器模式(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();
}
public class Mp4Video implements VideoInterFace {
@Override
public String getVideoPath() {
return "Mp4视频的路径";
}
}
public class VlcVideo implements VideoInterFace{
@Override
public String getVideoPath() {
return "Vlc视频的路径";
}
}
public class Player {
private Mp4Video video;
public Player(Mp4Video video) {
this.video = video;
}
public void play() {
System.out.println(StringFormatter.concat("播放视频视频地址:", video.getVideoPath()).getValue());
}
}
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());
}
}
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("支付宝支付中...");
}
}
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();
}
}
上面使用一个代理类,代理了多个对象,相对于静态代理,是代码更简介,灵活性也更高。