程序猿成长之路之设计模式篇——结构型设计模式

本篇开始介绍结构型设计模式

前言

与创建型设计模式用于创建对象不同,结构型设计模式通过结构化的方式实现功能的扩展和解耦,通过对象的组合、聚合、继承和接口等机制来定义对象之间的关系,从而实现松耦合和灵活性。

常见的结构性设计模式(具体介绍前四种)

  1. 代理模式
    分为动态代理和静态代理,在不需要了解某一接口或类内部实现细节的情况下,不需要对接口或类进行修改的情况下实现功能的扩展,即在调用前后进行功能扩展。动态代理又分为jdk动态代理和cglib动态代理。
    静态代理UML图:

    1.1 静态代理:
    IBaseOperation 接口
java 复制代码
/**
 * 基础功能接口
 * @author zygswo
 *
 */
public interface IBaseOperation {
	void operation();
}

代理对象RealSubject

java 复制代码
/**
 * 代理对象
 * @author zygswo
 *
 */
public class RealSubject implements IBaseOperation {

	@Override
	public void operation() {
		System.out.println("real subject print");
	}
}

代理类Proxy

java 复制代码
/**
 * 代理类
 * @author zygswo
 *
 */
public class Proxy implements IBaseOperation{
	/**
	 * 代理对象
	 */
	RealSubject realSubject;
	
	public Proxy(RealSubject realSubject) {
		this.realSubject = realSubject;
	}

	@Override
	public void operation() {
		System.out.println("pre handler");
		realSubject.operation();
		System.out.println("post handler");
	}
}

入口类

java 复制代码
public class Main {

	public static void main(String[] args) {
		RealSubject realSubject = new RealSubject();
		Proxy proxy = new Proxy(realSubject);
		proxy.operation();
	}

}

效果图

1.2 动态代理

动态代理分为jdk动态代理和cglib动态代理,

jdk动态代理:基于反射机制实现,需要实现接口然后生成代理类,调用速度较慢,jdk自带

cglib动态代理:基于字节码机制实现,可以通过继承实现,调用速度较快,需要引入cglib库。

jdk动态代理例子:

要实现的接口

java 复制代码
public interface MyInterface {
	void getResult();
}

实现类:

java 复制代码
public class MyClass implements MyInterface {

	@Override
	public void getResult() {
		System.out.println("hello world");
	}
}

jdk动态代理类

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJvmProxy implements InvocationHandler{
	/**
	 * 代理对象
	 */
	private Object object;
	
	public MyJvmProxy(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//prehandler
		System.out.println("--------------预处理------------");
		Object obj = method.invoke(object, args);
		//posthandler
		System.out.println("--------------处理完毕------------");
		return obj;
	}
	
	public Object getProxyInstance() {
		return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
	}
}

入口类

java 复制代码
public class MainEntrance {

	public static void main(String[] args) {
		MyJvmProxy proxy = new MyJvmProxy(new MyClass());
		//传入classloader 使用反射来获取代理对象,之后调用MyJvmProxy来实现功能扩展
		MyInterface myClass = (MyInterface)proxy.getProxyInstance();
		myClass.getResult();
	}
}

效果图:

cglib 动态代理:

实现类:

java 复制代码
public class MyClass{

	@Override
	public void getResult() {
		System.out.println("hello world");
	}
}

cglib动态代理类

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglibProxy implements MethodInterceptor {
	private Object object;
	public MyCglibProxy(Object object){
		this.object = object;
	}
	 @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       	System.out.println("--------------预处理------------");
        Object returnValue = proxy.invokeSuper(obj, args);
        System.out.println("--------------处理完毕------------");
        return returnValue;
    }
	
	public Object getProxyInstance() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperClass(object.getClass());
		enhancer.setCallback(this);
		return enhancer.create();
	}
} 

入口类

java 复制代码
public class MainEntrance {

	public static void main(String[] args) {
		MyCglibProxy proxy = new MyCglibProxy(new MyClass());
		//传入classloader 使用反射来获取代理对象,之后调用MyJvmProxy来实现功能扩展
		MyClass myClass = (MyClass)proxy.getProxyInstance();
		myClass.getResult();
	}
}

代理模式优点:

  1. 代理对象可以扩展目标对象的功能;

  2. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

代理模式缺点:

  1. 增加系统复杂度、降低了代码的可读性

应用:防火墙、远程(Remote)代理、AOP等。

  1. 适配器模式
    分为类适配器和对象适配器模式,分别通过继承和组合的方式实现功能的扩展。
    类适配器模式UML图:

    类适配器:
    父类:动物,子类:人类,小狗,行为接口类:IBaseBehavior
    父类:
java 复制代码
/**
 * 动物类
 * @author zygswo
 *
 */
public class Animal{
	/**
	 * 名字
	 */
	private String name;
	
	/**
	 * 名字
	 */
	private String type;
	
	/**
	 * 年龄
	 */
	private String age;


	public Animal(String name, String type, String age) {
		this.name = name;
		this.type = type;
		this.age = age;
	}

	protected void intro() {
		System.out.println("姓名: " + name + "\t年龄: " + age + "\t种类: " + type);
	}
}

行为接口类:

java 复制代码
public interface IBaseBehavior {
	void move();
	void bark();
}

子类:

java 复制代码
public class Dog extends Animal implements IBaseBehavior{
	

	public Dog(String name, String age) {
		super(name,"小狗", age);
	}


	@Override
	public void move() {
		super.intro();
		System.out.println("爬了一步");
	}


	@Override
	public void bark() {
		super.intro();
		System.out.println("吠了一声");
	}
}

public class Human extends Animal implements IBaseBehavior{	

	public Human(String name, String age) {
		super(name,"人类", age);
	}


	@Override
	public void move() {
		super.intro();
		System.out.println("走了一步");
	}


	@Override
	public void bark() {
		super.intro();
		System.out.println("喊了一声");
	}
}

入口类:

java 复制代码
public class Main {
	public static void main(String[] args) {
		Human human = new Human("章瑜亮", "28");
		Dog dog = new Dog("xxx", "5");
		human.move();
		human.bark();
		dog.move();
		dog.bark();
	}
}

效果图

对象适配器模式UML图

分为总体类:小狗类、人类

部分结构类:脚类、心脏类(小狗和人都是有脚、有心脏)

部分结构类:

脚类:

java 复制代码
public class Foot{

	public Foot() {
	}

	public void run() {
		System.out.println("肢体运动起来了");
	}
}

public class Heart{

	public Heart() {
	}

	public void run() {
		System.out.println("心脏跳动起来了");
	}
}

总体类:

人类

java 复制代码
public class Human{
	
	private Heart heart;
	
	private Foot foot;

	public Human(Heart heart, Foot foot) {
		this.heart = heart;
		this.foot = foot;
	}

	public void run() {
		this.heart.run();
		this.foot.run();
		System.out.println("小人活跃了");
	}
}

小狗类

java 复制代码
public class Dog{
	
	private Heart heart;
	
	private Foot foot;

	public Dog(Heart heart, Foot foot) {
		this.heart = heart;
		this.foot = foot;
	}

	public void run() {
		this.heart.run();
		this.foot.run();
		System.out.println("小狗活跃了");
	}
}

入口类:

java 复制代码
public class Main {
	public static void main(String[] args) {
		Foot foot = new Foot();
		Heart heart = new Heart();
		Human human = new Human(heart,foot);
		Dog dog = new Dog(heart,foot);
		human.run();
		dog.run();
	}
}

效果图:

适配器优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无序修改原有结构。

  2. 增加了类的透明性和复用性,将具体业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。

  3. 灵活性和扩展性都非常好,通过使用配置文件可以很方便的更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,符合开闭原则。

缺点:

适配器缺点:

类适配器的缺点

  1. 对于 Java 等不支持多重继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。

  2. 适配者类不能为最终类。

对象适配器的缺点

与类适配器模式相比较,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。

应用场景:用于接口和类不兼容、适配不同数据格式等情况

  1. 装饰器模式

    通过层层嵌套实现功能扩展,如BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"gbk")); 其中FileInputStream是父装饰器,其父类为inputStream,调用InputStreamReader(InputStream is) 构造方法,之后生成InputStreamReader类,其父类为Reader类,再调用new BufferedReader(Reader reader)构造方法作为参数传入,分为‌抽象构件角色(装饰目标)、具体构件角色(具体的装饰目标)、装饰角色(装饰类)、具体的装饰角色(具体装饰类)

    抽象构件角色(Component):具体构件类和抽象装饰者类的共同父类。

    具体构件角色(ConcreteComponent):抽象构件的子类,装饰者类可以给它增加额外的职责。

    装饰角色(Decorator):抽象构件的子类,具体装饰类的父类,用于给具体构件增加职责,但在子类中实现。(可以无)

    具体装饰角色(ConcreteDecorator):具体装饰类,定义了一些新的行为,向构件类添加新的特性。

装饰器模式UML图

‌抽象构件角色

java 复制代码
/**
 * 基础接口
 * @author zygswo
 *
 */
public interface BasePrinter {
	/**
	 * 打印信息
	 * @param msg
	 */
	void print();
}

具体构件角色

java 复制代码
public class Printer implements BasePrinter{
	private String message;
	
	public Printer(String message) {
		this.message = message;
	}
	
	@Override
	public void print() {
		System.out.println(message);
	}
}

具体装饰角色:

java 复制代码
/**
 * 具体装饰角色
 * @author zygswo
 *
 */
public class ParentPrinter implements BasePrinter{
	
	/**
	 * 打印日志
	 */
	protected volatile BasePrinter printer;
	
	/**
	 * 构造方法
	 * @param printer
	 */
	public ParentPrinter(BasePrinter printer) {
		this.printer = printer;
	}

	@Override
	public void print() {
		System.out.println("ParentPrinter print start");
		printer.print();
		System.out.println("ParentPrinter print end");
	}
}

/**
 * 具体装饰角色
 * @author zygswo
 *
 */
public class ChildPrinter implements BasePrinter{
	
	/**
	 * 打印日志
	 */
	protected volatile BasePrinter printer;
	
	/**
	 * 构造方法
	 * @param printer
	 */
	public ChildPrinter(BasePrinter printer) {
		this.printer = printer;
	}

	@Override
	public void print() {
		System.out.println("ChildPrinter print start");
		printer.print();
		System.out.println("ChildPrinter print end");
	}
}

入口类:

java 复制代码
public class Main {
	public static void main(String[] args) {
		BasePrinter myprinter = new Printer("hello world");
		//装饰器层层嵌套
		BasePrinter childPrinter = new ParentPrinter(new ChildPrinter(myprinter));
		childPrinter.print();
	}
}

效果图:

装饰器优点:

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。

  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。

  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合

  4. 可以创造出很多不同行为的组合,得到更加强大的对象。

  5. 具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,原有类库代码无序改变,符合开闭原则。

装饰器缺点:

  1. 在使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值不同,大量的小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能。

  2. 装饰器模式提供了一种比继承更加灵活、机动的解决方案,但同时也意味着比继承更加易于出错,排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,较为烦琐。

应用 :字符字节流等

  1. 桥接模式

    将抽象部分与它的实现部分分离,使它们都可以独立地变化。

    抽象角色引用实现角色。

    例:我们拿支付举例,支付模式和支付渠道是支付的两个维度

    支付方式可以抽象出一个支付方式类,将各种方式支付作为其子类,如下图

    支付模式与支付方式存在组合关系,可以提供支付模式接口,将具体的模式作为实现类。

    桥接模式优点:

    1. 完成了实现和抽象的分离,实现了解耦
    2. 增加了系统的可扩展性
      桥接模式缺点:
    3. 由于关联关系建立在抽象层,要求开发者一开始就要对抽象层进行设计和编程
    4. 桥接模式要求正确识别出系统中的两个独立变化的维度,需要设计者有一定的经验
      应用场景:不同数据库的 JDBC 驱动程序、需要在某种统一协议下增加更多组件时(如不同支付渠道下的支付业务)
  2. 组合模式

    将对象组合成树形结构以表示整个部分的层次结构。可以理解为文件目录结构。一个文件夹里有文件夹和文件,其中每一个文件就是叶子节点,不能有其他操作;而文件夹则是一个树枝节点,和根节点一样具有增删改节点的功能,叶子节点则是根目录,可以理解为linux的"/"目录

    UML图:

    主要角色

    抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。

    在该角色中可以包含所有子类共有行为的声明和实现,在抽象根节点中定义了访问及管理它的子构件的方法,如增加子节点、删除子节点、获取子节点等。

    树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。

    树枝节点可以包含树枝节点,也可以包含叶子节点,它其中有一个集合可以用于存储子节点,实现了在抽象根节点中定义的行为。包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

    叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

    在组合结构中叶子节点没有子节点,它实现了在抽象根节点中定义的行为。

    组合模式优点:

    1. 结构清晰,为树的结构化表示提供帮助

    2. 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合"开闭原则"。

    组合模式缺点:

    1. 需要存在目录式的结构才可以使用
      应用场景:
  3. 享元模式

    摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象

    UML图:
    主要角色

    抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

     享元(Flyweight)模式中存在以下两种状态:
     
     内部状态,即不会随着环境的改变而改变的可共享部分。
     外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
    

    可共享的具体享元(Concrete Flyweight)角色:它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

    非共享的具体享元(Unshared Flyweight)角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

    享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

例如:建立五子棋工厂,生产白/黑子。

GoBangFactory(享元池):通过静态内部类实现 GoBangFactory 单例,生产五子棋棋子的工厂。
java 复制代码
public class GoBangFactory {

    private Map<String, GoBang> pool = null;

    public GoBangFactory() {
        pool = new HashMap<>();
        WhiteGoBang whiteGoBang = new WhiteGoBang();// 白子
        BlackGoBang blackGoBang = new BlackGoBang();// 黑子
        
        pool.put("w", whiteGoBang);
        pool.put("b", blackGoBang);
    }
    
    private static class SingletonHandler{
        private static final GoBangFactory INSTANCE = new GoBangFactory();
    }
    
    public static GoBangFactory getInstance(){
        return SingletonHandler.INSTANCE;
    }

    public GoBang getGoBang(String key){
        return pool.get(key);
    }
}
GoBang(享元类):抽象五子棋类,定义主要功能或特征。
java 复制代码
public abstract class GoBang {
    
    public abstract String getColor();
    
    public void display(){// 显示棋子颜色
        System.out.println("棋子的颜色:" + getColor());
    }
}
WhiteGoBang(共享享元类):白色棋子。
java 复制代码
public class WhiteGoBang extends GoBang{ 
    @Override
    public String getColor() {
        return "白色";
    }
}
BlackGoBang(共享享元类):黑色棋子。
java 复制代码
public class BlackGoBang extends GoBang{
    @Override
    public String getColor() {
        return "黑色";
    }
}

入口类:

java 复制代码
public class TestFlyweight {

    @Test
    public void testExample02() {
        GoBangFactory instance = GoBangFactory.getInstance();
        GoBang w1 = instance.getGoBang("w");
        GoBang w2 = instance.getGoBang("w");
        GoBang w3 = instance.getGoBang("w");
        System.out.println("判断黑子是否是同一对象:" + (w1 == w2));

        GoBang b1 = instance.getGoBang("b");
        GoBang b2 = instance.getGoBang("b");
        System.out.println("判断白子是否是同一对象:" + (b1 == b2));
        b1.display();
        b2.display();
        w1.display();
        w2.display();
        w3.display();
    }
}

效果图

享元模式的优点:

  1. 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能。
  2. 获取的相同对象为同一对象,保证数据的一致性。

享元模式的缺点:

  1. 如果要自定义对象,不太适合用享元模式。
  2. 为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。

应用场景:线程池、数据库连接池、常量池等。

------------------------------------有问题欢迎小伙伴评论区里留言------------------------------------

相关推荐
咖啡の猫6 小时前
原型模式详解与实践
设计模式·原型模式
青岚岁叶9 小时前
设计模式——泛型单例类
单例模式·设计模式·unity3d
weixin_3077791313 小时前
解耦Java应用程序的方法和技巧
java·设计模式
zhulangfly16 小时前
【Java设计模式-1】单例模式,Java世界的“独苗”
java·单例模式·设计模式
Leaf吧16 小时前
java设计模式 单例模式
java·单例模式·设计模式
JINGWHALE116 小时前
设计模式 结构型 桥接模式(Bridge Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·桥接模式
苹果17 小时前
C++二十三种设计模式之抽象工厂模式
c++·设计模式·抽象工厂模式
憶巷17 小时前
代理模式和适配器模式有什么区别
代理模式·适配器模式
silver68717 小时前
代理模式详解
设计模式
臣妾写不来啊17 小时前
结构型模式7.代理模式
代理模式