结构性设计模式是软件工程中常用的一类设计模式。
作用:主要用于处理类或对象之间的组合以实现更灵活、可扩展和可维护的代码结构。
这些模式通常涉及到类和对象之间的静态组合关系,以及如何将它们组织在一起以满足特定的设计目标。
结构型模式有:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
适配器模式
文章地址:
适配器模式已经在SpringMVC中的源码实现-CSDN博客
总结:我调用适配器类,适配器类进行内部转换,返回给我一个想要的类
桥接模式
桥接模式以及在JDBC源码剖析-CSDN博客
总结:将抽象部分与实现部分分离,使它们可以独立变化。
装饰者模式
前提总结:动态地给一个对象添加一些额外的职责。
介绍
1、定义 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则
//1、接口
public interface Coffee {
double getCost();
String getDescription();
}
//2、具体咖啡类
public class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1.0;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
//3、装饰者类
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
public double getCost() {
return decoratedCoffee.getCost();
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() {
return super.getCost() + 0.5;
}
public String getDescription() {
return super.getDescription() + ", with milk";
}
}
//4、测试demo
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost: " + simpleCoffee.getCost() + ", Description: " + simpleCoffee.getDescription());
Coffee coffeeWithMilk = new MilkDecorator(simpleCoffee);
System.out.println("Cost: " + coffeeWithMilk.getCost() + ", Description: " + coffeeWithMilk.getDescription());
装饰者模式在JDK中使用
在JavaIO结构中,FilterInputStream就是一个装饰者
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("example.txt"));这里就是装饰者模式的应用,有点类似套娃。
总结
装饰者模式是一种结构型设计模式,它允许你动态地将责任附加到对象上。这种模式可以通过创建一个包装类来包裹原始类,在不改变原始类接口的情况下,给其添加新的功能。
优点
灵活性:装饰者模式通过嵌套组合的方式,可以在运行时动态地添加、移除或修改对象的行为,提供了很高的灵活性。
避免继承的缺点:相比使用继承来扩展对象功能,装饰者模式避免了类爆炸问题,使得代码更加灵活、可维护和可扩展。
单一职责原则:每个装饰者类都专注于一个特定的功能,符合单一职责原则,从而使得系统更易于理解和维护。
缺点
复杂性:过度使用装饰者模式可能会导致大量的小类,增加代码复杂性和理解难度。
顺序敏感:装饰者模式中的装饰顺序很重要,如果顺序错误可能导致意外结果,需要特别注意。
适用场景
当需要在不影响其他对象的情况下,动态地、递归地给对象添加功能时,可以使用装饰者模式。
当需要动态地撤销功能时,该模式同样适用。
总的来说,装饰者模式在需要灵活地扩展对象功能、避免类爆炸以及保持单一职责原则时非常有用。然而,在实际使用时,应权衡利弊,避免过度复杂化设计。
组合模式
简单总结:组合模式是一种设计模式,它允许你将对象组合成树形结构,从而形成“部分-整体”的层次结构。每个组件都可以执行一些操作,并且这些操作可以通过递归的方式应用于组合的整个结构。
介绍
1、组合模式又称部分整体模式,它创建了对象组的树型结构,将对象组合成树状结构,以表示“整体 - 部分” 的层次关系。 2、组合模式依据树型结构来组合对象,用来表示部分以及整体层次 3、属于结构者模式 4、组合模式使得用户对单个对象和组合对象的访问具有一致性。即(组合能让客户以一致的方式处理个别对象以及组合对象)
示例
import java.util.ArrayList;
import java.util.List;
// 组件接口
interface Component {
void operation();
}
// 叶子节点类
class Leaf implements Component {
@Override
public void operation() {
System.out.println("Leaf operation");
}
}
// 组合节点类
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
System.out.println("Composite operation");
for (Component component : children) {
component.operation();
}
}
}
public class Main {
public static void main(String[] args) {
// 创建叶子节点
Leaf leaf1 = new Leaf();
Leaf leaf2 = new Leaf();
// 创建组合节点
Composite composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);
// 调用操作
composite.operation();
}
}
说明:
1、Component:这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component可以是抽象类或接口 2、Leaf:在组合中表示叶子节点,叶子节点没有子节点 3、Composite:非叶子节点,用于存储子部件,在Component 接口中实现 子部件的相关操作。比如增加、删除....
组合模式在JDK中使用
HashMap中的使用。
这里是Map接口(类似上述的Component接口)
这里是HashMap的内部类,类似上述的Leaf节点
这里是HashMap类,类似上述Composite
总结
个人理解:
组合模式确实就是将复合组件(Composite)和叶子组件(Leaf)组合起来,以实现一些功能。
在组合模式中,复合组件可以包含叶子组件,也可以包含其他复合组件,从而形成一个树形结构。
这种树形结构使得客户端可以统一地对待单个组件和组合组件,而不必关心它们的具体类型。客户端可以通过统一的接口来访问组合中的所有组件,从而简化了客户端的代码。
注意事项和细节
1、简化客户端操作。 客户只需要面对一致的对象,而不需要考虑整体部分或者叶子节点的问题。
2、具有较强的扩展性。 当需要修改组合对象时,只需要调整内部的层次关系,客户端不需要做出任何改动。
3、方便创建出复杂的层次结构。 客户不需要了解内部的组成细节,容易添加节点或叶子从而创建出复杂的树型结构
4、需要遍历组织机构,或处理对象具有树型结构时,非常适合组合模式
5、需要较高的抽象性。 如果节点和叶子有很多差异性的话,不适合组合模式。
优点
简化客户端代码:客户端可以一致地处理单个对象和组合对象,不需要区分它们的具体类型,从而简化客户端代码。
灵活性:可以通过组合不同的对象来构建复杂的层次结构,同时能够方便地增加新的组件或改变组件的结构。
统一接口:叶子节点和组合节点都实现了相同的接口,使得客户端可以统一调用操作,无需关心具体实现。
容易扩展:可以很容易地添加新的组件类,无需修改现有的代码。
缺点
限制组件类型:由于所有组件都需要实现相同的接口,可能会限制组件类型的多样性。
增加复杂性:引入组合模式会增加系统的复杂性,特别是在处理递归结构时需要小心设计,避免出现死循环或性能问题。
不适合所有情况:并不是所有的场景都适合使用组合模式,特别是当组件间的层次关系比较简单或不固定时,可能会显得过度复杂。
小结
组合模式在处理整体 - 部分结构的问题上具有明显的优点,但在某些情况下也需要权衡其复杂性和适用性。
外观模式
介绍
1、外观模式又称过程模式。 外观模式为子系统中的一组接口提供了一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 2、外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关注子系统中的内部细节。
外观模式中的角色:
1、外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当的子系统对象。 2、调用者(Client):外观接口的调用者 3、子系统的集合:指模块或子系统,处理Facade 对象指派的任务,他是功能的提供者。
实例
下面是一个简单的家庭影院系统作为案例。在这个案例中,外观模式可以隐藏复杂的子系统,并提供一个简单的接口给客户端。
家庭影院外观 HomeTheaterFacade
封装了投影仪、音响和屏幕等子系统的操作,为客户端提供了简单的接口。客户端只需与外观类交互,而不需要了解内部子系统的复杂操作。
// 子系统类:投影仪
class Projector {
public void on() {
System.out.println("投影仪打开");
}
public void off() {
System.out.println("投影仪关闭");
}
}
// 子系统类:音响
class AudioSystem {
public void on() {
System.out.println("音响打开");
}
public void off() {
System.out.println("音响关闭");
}
}
// 子系统类:屏幕
class Screen {
public void up() {
System.out.println("屏幕上升");
}
public void down() {
System.out.println("屏幕下降");
}
}
// 外观类:家庭影院外观
class HomeTheaterFacade {
private Projector projector;
private AudioSystem audioSystem;
private Screen screen;
public HomeTheaterFacade(Projector projector, AudioSystem audioSystem, Screen screen) {
this.projector = projector;
this.audioSystem = audioSystem;
this.screen = screen;
}
public void watchMovie() {
System.out.println("准备观影...");
projector.on();
audioSystem.on();
screen.down();
}
public void endMovie() {
System.out.println("结束观影...");
projector.off();
audioSystem.off();
screen.up();
}
}
// 客户端类
public class Client {
public static void main(String[] args) {
Projector projector = new Projector();
AudioSystem audioSystem = new AudioSystem();
Screen screen = new Screen();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, audioSystem, screen);
homeTheater.watchMovie();
System.out.println("一段时间后...");
homeTheater.endMovie();
}
}
外观模式在Mybatis框架的使用
上述就是在Mybatis框架中的使用。
1、子系统类: DefaultObjectFactory类、DefaultObjectWrapperFactory类、DefaultReflectorFactory类 2、外观类: Configuration类
外观模式像一个集合子系统类,为什么叫外观模式呢?
外观模式的主要目的是提供一个简化接口,用于访问系统中一组复杂的子系统。外观模式通过创建一个高层接口(外观类),封装了子系统中的一组接口,从而隐藏了子系统的复杂性,并提供了一个更简单的接口给客户端使用。
虽然外观模式中的外观类看起来像是集合了多个子系统的功能,但它们的核心不在于集合,而在于简化接口。外观模式的外观类不负责实现功能,而是将请求转发给子系统中相应的对象,让子系统来完成实际的工作。
因此,外观模式的核心特点可以总结为:
-
简化接口:外观类提供了一个简化的接口,隐藏了子系统的复杂性,使得客户端不需要了解子系统的具体实现细节。
-
封装子系统:外观类封装了子系统中的一组接口,通过外观类访问子系统,客户端不需要直接与子系统的组件进行交互。
-
解耦:外观模式将客户端与子系统之间的耦合度降低到最低程度,客户端只需要与外观类进行交互,而不需要了解子系统的内部结构。
因此,尽管外观模式中的外观类看起来像是集合了多个子系统的功能,但其主要作用是提供一个简化接口,隐藏复杂性,降低耦合度,而不是简单地将多个类集合在一起形成一个新的集合类。
总结
个人理解:
外观模式理解为一个主类(外观类)集成了多个子类(子系统),并通过外观类来调用这些子类完成特定功能的过程
外观模式是一种结构型设计模式,旨在为复杂系统提供一个简单统一的接口,从而隐藏系统的复杂性,使客户端与系统之间的交互变得更加简单和直观。
优点:
-
简化接口:外观模式提供了一个简单的接口,隐藏了子系统的复杂性,使客户端使用起来更加方便。
-
解耦:通过外观模式,客户端与子系统之间的耦合度降低,客户端不需要直接与多个子系统进行交互,只需与外观类交互即可。
-
更好的封装性:外观模式将子系统的细节封装在内部,对客户端隐藏了具体实现细节,提高了系统的安全性和稳定性。
缺点:
-
不符合开闭原则:当系统中的子系统发生变化时,可能需要修改外观类,违反了开闭原则。
-
可能造成性能问题:在某些情况下,外观类可能会变得过于庞大,导致性能问题。
-
可能引入不必要的复杂性:如果系统本身并不复杂,引入外观模式可能会增加代码复杂性。
总的来说,外观模式适合用于简化复杂系统的接口,提高系统的易用性和灵活性。在设计时需要权衡好利弊,确保外观模式的引入能够带来实际的好处。
享元模式
“享元”是中国古代的一种度量衡单位,用于衡量铜器时代的重量和容量
介绍
1、享元模式又称蝇量模式。 运用共享技术有效地支持大量细粒度对象(系统中存在许多具有相似性质和功能的小型对象)。
2、常用于系统底层开发,解决系统的性能问题,像数据库连接池,里面都是创建好的链接对象,里面都是已创建好的连接对象,在这些连接对象中。
3、能够解决重复对象内存浪费问题。 当系统中有大量相似对象,需要缓冲池时,不需要新建对象,可以从缓冲池中拿。 -》降低系统内存,提高效率
4、享元模式经典使用场景:池技术。 String常量池、数据库连接池、缓冲池等等
实例
import java.util.HashMap;
import java.util.Map;
// 享元工厂
class FlyweightFactory {
private static final Map<String, Flyweight> flyweights = new HashMap<>();
public static Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight());
}
return flyweights.get(key);
}
}
// 享元接口
interface Flyweight {
void operation();
}
// 具体享元
class ConcreteFlyweight implements Flyweight {
@Override
public void operation() {
System.out.println("具体享元的操作");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Flyweight flyweight1 = FlyweightFactory.getFlyweight("key1");
flyweight1.operation();
Flyweight flyweight2 = FlyweightFactory.getFlyweight("key2");
flyweight2.operation();
}
}
代码分析
前提: 1、享元模式提出了两个要求:细粒度和共享对象。这里涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态。 1)"大量细粒度对象"指的是系统中存在许多具有相似性质和功能的小型对象,这些对象通常具有较小的内存占用和较简单的结构。 2)共享对象指的是被多个客户端或实例共同引用或共享的对象。这种共享可以带来一些好处,例如减少内存占用、提高性能和降低系统复杂度 2、内部状态:对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变 3、外部状态:对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态 代码分析 1、FlyweightFactory :享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法 2、Flyweight:抽象的享元对象,他是产品的抽象类,同时定义出对象的外部状态和内部状态 3、ConcreteFlyweight:具体享元对象,是具体的产品类,实现抽象角色定义的业务
享元模式在JDK-Integer的应用源码分析
Integer x = Integer.valueOf(127);
Integer y = new Integer(127);
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y));//true
System.out.println(x == y);//false
System.out.println(x == z);//true
System.out.println(y == w);//false
小结:
1、在valueOf方法中,先判断是否在IntegerCache中,如果不在,就创建新的Integer(new),否则,就直接从缓存池中返回
2、这里使用到了享元模式
总结
个人理解: 一种生成重复对象的工厂,但是它给的如果是相同内容的对象,就是引用。 享元模式确实是一种用于避免重复创建相同对象的设计模式。
当使用享元模式时,如果请求创建的对象已经存在,工厂会返回对现有对象的引用,而不是创建一个新的对象,从而节省内存和提高效率。这种方式确保了相同内容的对象在系统中只有一个实例,并且多个客户端使用同一对象的引用。
优点:
减少内存占用:通过共享对象的方式,减少了大量相似对象的内存占用。
提高性能:由于共享对象,可以减少创建和销毁对象的开销,提高系统性能。
缺点:
需要对内部状态和外部状态进行区分和管理,增加了系统复杂度。
如果共享对象过多,可能会导致系统的维护困难。
代理模式(重点)
代理模式以及静态代理、JDK代理、Cglib代理的实现-CSDN博客
一般在面试中会考察SpringAOP使用的Cglib动态代理和JDK代理的区别?
是一种通过代理某一个类,并增强其功能的设计模式。