本篇开始介绍结构型设计模式
前言
与创建型设计模式用于创建对象不同,结构型设计模式通过结构化的方式实现功能的扩展和解耦,通过对象的组合、聚合、继承和接口等机制来定义对象之间的关系,从而实现松耦合和灵活性。
常见的结构性设计模式(具体介绍前四种)
- 代理模式
分为动态代理和静态代理,在不需要了解某一接口或类内部实现细节的情况下,不需要对接口或类进行修改的情况下实现功能的扩展,即在调用前后进行功能扩展。动态代理又分为jdk动态代理和cglib动态代理。
静态代理UML图:
1.1 静态代理:
IBaseOperation 接口
/**
* 基础功能接口
* @author zygswo
*
*/
public interface IBaseOperation {
void operation();
}
代理对象RealSubject
/**
* 代理对象
* @author zygswo
*
*/
public class RealSubject implements IBaseOperation {
@Override
public void operation() {
System.out.println("real subject print");
}
}
代理类Proxy
/**
* 代理类
* @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");
}
}
入口类
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动态代理例子:
要实现的接口
public interface MyInterface {
void getResult();
}
实现类:
public class MyClass implements MyInterface {
@Override
public void getResult() {
System.out.println("hello world");
}
}
jdk动态代理类
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);
}
}
入口类
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 动态代理:
实现类:
public class MyClass{
@Override
public void getResult() {
System.out.println("hello world");
}
}
cglib动态代理类
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();
}
}
入口类
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等。
- 适配器模式
分为类适配器和对象适配器模式,分别通过继承和组合的方式实现功能的扩展。
类适配器模式UML图:
类适配器:
父类:动物,子类:人类,小狗,行为接口类:IBaseBehavior
父类:
/**
* 动物类
* @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);
}
}
行为接口类:
public interface IBaseBehavior {
void move();
void bark();
}
子类:
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("喊了一声");
}
}
入口类:
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图
分为总体类:小狗类、人类
部分结构类:脚类、心脏类(小狗和人都是有脚、有心脏)
部分结构类:
脚类:
public class Foot{
public Foot() {
}
public void run() {
System.out.println("肢体运动起来了");
}
}
public class Heart{
public Heart() {
}
public void run() {
System.out.println("心脏跳动起来了");
}
}
总体类:
人类
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("小人活跃了");
}
}
小狗类
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("小狗活跃了");
}
}
入口类:
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. 适配者类不能为最终类。
对象适配器的缺点
与类适配器模式相比较,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。
应用场景:用于接口和类不兼容、适配不同数据格式等情况
-
装饰器模式
通过层层嵌套实现功能扩展,如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图
抽象构件角色
/**
* 基础接口
* @author zygswo
*
*/
public interface BasePrinter {
/**
* 打印信息
* @param msg
*/
void print();
}
具体构件角色
public class Printer implements BasePrinter{
private String message;
public Printer(String message) {
this.message = message;
}
@Override
public void print() {
System.out.println(message);
}
}
具体装饰角色:
/**
* 具体装饰角色
* @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");
}
}
入口类:
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. 装饰器模式提供了一种比继承更加灵活、机动的解决方案,但同时也意味着比继承更加易于出错,排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,较为烦琐。
应用 :字符字节流等
-
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
抽象角色引用实现角色。
例:我们拿支付举例,支付模式和支付渠道是支付的两个维度
支付方式可以抽象出一个支付方式类,将各种方式支付作为其子类,如下图
支付模式与支付方式存在组合关系,可以提供支付模式接口,将具体的模式作为实现类。
桥接模式优点:- 完成了实现和抽象的分离,实现了解耦
- 增加了系统的可扩展性
桥接模式缺点: - 由于关联关系建立在抽象层,要求开发者一开始就要对抽象层进行设计和编程
- 桥接模式要求正确识别出系统中的两个独立变化的维度,需要设计者有一定的经验
应用场景:不同数据库的 JDBC 驱动程序、需要在某种统一协议下增加更多组件时(如不同支付渠道下的支付业务)
-
组合模式
将对象组合成树形结构以表示整个部分的层次结构。可以理解为文件目录结构。一个文件夹里有文件夹和文件,其中每一个文件就是叶子节点,不能有其他操作;而文件夹则是一个树枝节点,和根节点一样具有增删改节点的功能,叶子节点则是根目录,可以理解为linux的”/"目录
UML图:
主要角色
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。在该角色中可以包含所有子类共有行为的声明和实现,在抽象根节点中定义了访问及管理它的子构件的方法,如增加子节点、删除子节点、获取子节点等。
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
树枝节点可以包含树枝节点,也可以包含叶子节点,它其中有一个集合可以用于存储子节点,实现了在抽象根节点中定义的行为。包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
在组合结构中叶子节点没有子节点,它实现了在抽象根节点中定义的行为。
组合模式优点:
1. 结构清晰,为树的结构化表示提供帮助
2. 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
组合模式缺点:- 需要存在目录式的结构才可以使用
应用场景:
- 需要存在目录式的结构才可以使用
-
享元模式
摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象
UML图:
主要角色
抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。享元(Flyweight)模式中存在以下两种状态: 内部状态,即不会随着环境的改变而改变的可共享部分。 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
可共享的具体享元(Concrete Flyweight)角色:它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
非共享的具体享元(Unshared Flyweight)角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
例如:建立五子棋工厂,生产白/黑子。
GoBangFactory(享元池):通过静态内部类实现 GoBangFactory 单例,生产五子棋棋子的工厂。
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(享元类):抽象五子棋类,定义主要功能或特征。
public abstract class GoBang {
public abstract String getColor();
public void display(){// 显示棋子颜色
System.out.println("棋子的颜色:" + getColor());
}
}
WhiteGoBang(共享享元类):白色棋子。
public class WhiteGoBang extends GoBang{
@Override
public String getColor() {
return "白色";
}
}
BlackGoBang(共享享元类):黑色棋子。
public class BlackGoBang extends GoBang{
@Override
public String getColor() {
return "黑色";
}
}
入口类:
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();
}
}
效果图
享元模式的优点:
- 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能。
- 获取的相同对象为同一对象,保证数据的一致性。
享元模式的缺点:
- 如果要自定义对象,不太适合用享元模式。
- 为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。
应用场景:线程池、数据库连接池、常量池等。
————————————有问题欢迎小伙伴评论区里留言————————————