当前位置: 首页 > news >正文

【后端高频面试题--设计模式上篇】

🚀 作者 :“码上有前”
🚀 文章简介 :后端高频面试题
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬

往期精彩内容

【后端高频面试题–设计模式上篇】
【后端高频面试题–设计模式下篇】
【后端高频面试题–Linux篇】
【后端高频面试题–Nginx篇】
【后端高频面试题–Mybatis篇】
【后端高频面试题–SpringBoot篇】

什么是设计模式?怎么理解设计模式?

设计模式是在软件设计中,针对常见问题和场景提供的可重用解决方案的一种描述。它们是由经验丰富的软件开发者和设计师总结和归纳出来的,旨在解决软件设计中的通用问题,并提供经过验证的、可靠的解决方案。

设计模式可以看作是解决常见设计问题的模板或蓝图。它们描述了一种特定问题的解决方案,以及如何将不同的组件、类和对象进行组织和交互,以达到设计目标。设计模式不是具体的算法或代码实现,而是一种更高层次的抽象,可以帮助开发者理解和沟通设计思想。

理解设计模式可以从以下几个方面入手:

  1. 问题和场景:设计模式是为了解决特定的问题和应对常见的设计场景而存在的。首先,了解具体问题和场景,例如如何处理对象之间的依赖、如何实现灵活的扩展性等。

  2. 模式的描述:每个设计模式都有明确的描述,包括该模式的名称、意图、问题描述、解决方案以及涉及的角色和关系。仔细阅读并理解每个模式的描述,可以帮助你熟悉其设计思想和解决方案。

  3. 具体实现:理解设计模式后,尝试通过代码实现来加深理解。可以使用适当的编程语言和工具,根据模式的描述创建相应的类、接口和对象,并演示模式的应用。

  4. 实际应用:将设计模式应用于实际项目中,可以帮助识别和解决类似的问题,并提供可重用和可维护的解决方案。通过实际应用,可以更好地理解模式的优势、适用性和局限性。

设计模式的学习和理解是一个渐进的过程。开始时,可以重点关注一些常用的设计模式,如单例模式、工厂模式、观察者模式等。随着经验的积累,逐渐熟悉更多的设计模式,并学会在实际项目中选择和应用合适的模式。阅读设计模式的经典书籍和参与相关的讨论和实践也是提高理解和应用设计模式的有效途径。

设计模式分类

在这里插入图片描述
设计模式可以根据其解决问题的方式和目标进行分类。以下是常见的设计模式分类:

  1. 创建型模式(Creational Patterns):这些模式关注如何创建对象,以及在不暴露对象创建逻辑的情况下实现对象的实例化。创建型模式包括单例模式、工厂模式、抽象工厂模式、建造者模式和原型模式。

  2. 结构型模式(Structural Patterns):这些模式关注如何将类和对象组合成更大的结构,并定义它们之间的关系。结构型模式包括适配器模式、装饰器模式、代理模式、桥接模式、组合模式、外观模式和享元模式。

  3. 行为型模式(Behavioral Patterns):这些模式关注对象之间的通信和交互方式,以及如何在系统中分配职责。行为型模式包括策略模式、观察者模式、责任链模式、命令模式、迭代器模式、模板方法模式、访问者模式、备忘录模式、状态模式和中介者模式。

  4. J2EE模式(J2EE Patterns):这些模式是特定于Java企业版(Java Enterprise Edition,J2EE)的设计模式,用于解决企业级应用程序开发中的常见问题。J2EE模式包括MVC模式、业务代表模式、数据访问对象模式、前端控制器模式、拦截过滤器模式等。

  5. 并发模式(Concurrency Patterns):这些模式关注多线程和并发编程中的问题和解决方案。并发模式包括锁模式、线程池模式、读写锁模式、观察者模式等。

除了以上分类,还有其他的设计模式,如领域驱动设计模式(Domain-Driven Design Patterns)、面向切面编程模式(Aspect-Oriented Programming Patterns)等。

每个设计模式都有其特定的用途和适用场景,了解不同类型的设计模式可以帮助开发者在设计和开发过程中选择合适的模式,并遵循最佳实践。

设计模式的六大原则

在这里插入图片描述

设计模式的六大原则是指在软件设计过程中的准则和指导原则,用于帮助开发者设计出可维护、可扩展和可复用的软件系统。这些原则被广泛应用于各种设计模式中。以下是六大原则的简要介绍:

  1. 单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因。换句话说,一个类应该只承担一个责任。这样可以提高类的内聚性,并使其更加易于理解、修改和测试。

  2. 开放封闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。通过使用抽象、接口和多态等机制,可以在不修改现有代码的情况下增加新功能或行为。

  3. 里氏替换原则(Liskov Substitution Principle,LSP):子类型必须能够替换其基类型。这意味着在使用继承关系时,子类必须能够替代父类并完全符合其约定。这样可以确保在不破坏原有功能的情况下进行扩展。

  4. 依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现细节,而是应该依赖于抽象接口。这样可以降低模块之间的耦合度,提高代码的灵活性和可维护性。

  5. 接口隔离原则(Interface Segregation Principle,ISP):客户端不应该强迫依赖它们不使用的接口。一个类不应该依赖于它不需要的接口。通过定义细粒度的接口和使用接口隔离,可以避免出现臃肿的接口和不必要的依赖关系。

  6. 迪米特法则(Law of Demeter,LoD,也称为最少知识原则):一个对象应该对其他对象有尽可能少的了解。一个类应该只与其直接的朋友进行通信,而不应该了解朋友的内部细节。这样可以减少对象之间的依赖关系,提高代码的松耦合性。

这些原则在软件设计中起着重要的指导作用,帮助开发者设计出具有良好结构和可维护性的软件系统。然而,这些原则并不是绝对的,具体实践时需要根据实际情况进行权衡和应用。

单例模式

什么是单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点以获取该实例。单例模式通常用于需要在整个应用程序中共享资源或控制某个独占资源的情况。

理解单例模式可以从以下几个方面入手:

  1. 唯一实例:单例模式要求类只能有一个实例存在。这意味着在任何时候只能创建一个对象,并且该对象可以被全局访问。

  2. 全局访问点:单例模式提供了一个全局访问点(通常是一个静态方法),通过该方法可以获取单例对象的引用。这样可以确保在应用程序的任何地方都可以方便地访问该对象。

  3. 实例化控制:单例模式通常涉及对实例化过程的控制,以确保只有一个实例被创建。这可以通过限制类的构造函数的访问权限或使用延迟初始化等方式来实现。

下面是一个使用Java实现单例模式的示例:

public class Singleton {private static Singleton instance;// 私有构造函数,防止外部实例化private Singleton() {}// 全局访问点,获取单例对象public static Singleton getInstance() {if (instance == null) {// 延迟初始化instance = new Singleton();}return instance;}
}

在上述示例中,Singleton类的构造函数被声明为私有的,这意味着其他类无法直接实例化该类。通过getInstance()方法获取Singleton类的唯一实例。在第一次调用getInstance()方法时,会检查instance变量是否为空,如果为空,则创建一个新的Singleton对象。在后续调用中,直接返回已经创建的实例。

这种实现方式被称为懒汉式单例,它在需要时才创建实例。需要注意的是,该实现方式在多线程环境下可能会导致并发问题,因为多个线程可能同时判断instance为空,从而创建多个实例。可以通过加锁或使用双重检查锁定等方式来解决并发问题。

除了懒汉式单例,还有饿汉式单例、静态内部类单例等不同的实现方式。每种实现方式都有其适用场景和特点,具体选择取决于实际需求和性能考虑。

那些地方用到了单例模式

单例模式在许多应用程序和框架中都得到了广泛应用。以下是一些常见的使用单例模式的场景:

  1. 配置信息管理:单例模式可以用于管理应用程序的配置信息,确保在整个应用程序中只有一个配置对象,避免重复读取配置文件或数据库的开销。

  2. 日志记录器:单例模式常用于创建全局的日志记录器,以便在应用程序中的任何地方记录日志信息,并提供统一的日志管理和访问接口。

  3. 数据库连接池:单例模式可以用于创建和管理数据库连接池,以确保在应用程序中重复使用已经建立的数据库连接,提高数据库操作的效率。

  4. 缓存管理:单例模式适用于管理全局的缓存对象,以提供高效的数据缓存和访问机制,避免重复创建和销毁缓存对象。

  5. 线程池:单例模式可以用于创建和管理线程池,在应用程序中统一管理线程的创建、调度和销毁,提高多线程应用程序的性能和可伸缩性。

  6. GUI应用程序中的窗口管理器:单例模式可以用于创建和管理窗口管理器对象,确保在应用程序中只有一个窗口管理器,负责窗口的创建、关闭、切换等操作。

  7. 共享资源管理:单例模式可以用于管理共享资源,例如打印机池、数据库连接池等,以避免资源的重复创建和占用,提高资源的利用率。

需要注意的是,单例模式应该谨慎使用,因为它引入了全局状态和共享状态,可能导致代码的复杂性和耦合度增加。在使用单例模式时,需要仔细考虑线程安全性、并发访问、延迟初始化等问题,并在设计时权衡好使用单例模式的利弊。

单例模式的优缺点

单例模式作为一种设计模式,具有以下的优点和缺点:

优点:

  1. 独一无二的实例:单例模式保证一个类只有一个实例存在,这样可以确保全局只有一个访问点,方便其他对象对该实例的访问。

  2. 全局访问性:通过单例模式,可以在应用程序的任何地方访问单例对象,方便共享资源和数据。

  3. 节约资源:单例模式避免了重复创建对象的开销,特别是对于那些需要消耗大量资源的对象,如数据库连接池、线程池等。

  4. 数据共享和一致性:在单例模式下,多个对象共享同一个实例,可以保持数据的一致性,避免由于多个实例引起的数据不一致问题。

缺点:

  1. 难以扩展:由于单例模式只允许存在一个实例,因此扩展时可能会遇到限制。如果需要创建更多的实例或者变换实例,就需要修改单例类的代码。

  2. 全局状态:由于单例模式引入了全局状态,可能会增加代码的复杂性和耦合度,使得代码难以测试和维护。

  3. 难以调试:由于单例模式隐藏了对象的创建和生命周期管理,调试过程中可能会遇到困难。

  4. 并发访问问题:在多线程环境下,如果没有合适的同步措施,单例模式可能导致并发访问的问题,如多个线程同时创建实例、竞争条件等。

  5. 对象生命周期延长:由于单例模式的实例在整个应用程序生命周期中存在,如果实例持有大量资源或有长时间的生命周期,可能会导致资源占用和内存泄漏问题。

需要根据具体的应用场景和需求来评估单例模式的适用性,权衡其优点和缺点。在使用单例模式时,应注意线程安全性、并发访问、延迟初始化等问题,并在设计时遵循单一职责原则和依赖倒置原则,以确保代码的可维护性和扩展性。

单例模式使用注意事项

在使用单例模式时,有一些注意事项需要注意:

  1. 线程安全性:如果在多线程环境下使用单例模式,需要确保线程安全性。可以通过使用同步机制(如锁、双重检查锁等)或使用线程安全的初始化方法(如静态内部类)来保证线程安全。

  2. 延迟初始化:有时候,单例对象的创建可能会比较耗时或复杂,为了避免不必要的开销,可以采用延迟初始化的方式来创建单例对象。延迟初始化可以通过在首次访问时创建对象,而不是在应用程序启动时立即创建。

  3. 序列化和反序列化:如果单例类需要支持序列化和反序列化,需要特别注意序列化对单例模式的影响。可以通过实现readResolve()方法来保证反序列化时返回单例对象,防止通过反序列化创建新的实例。

  4. 防止反射攻击:单例模式在某些情况下可能会受到反射攻击的威胁。可以通过设置构造函数为私有、在构造函数中进行判断等方式来防止反射实例化。

  5. 测试难度:由于单例模式引入了全局状态,可能会增加代码的复杂性和测试难度。在编写单元测试时,需要注意对单例对象的模拟和隔离,以确保可靠的测试覆盖。

  6. 可扩展性和耦合度:单例模式可能会限制类的扩展性,因为它只允许一个实例存在。如果将来需要创建更多的实例或变换实例,可能需要修改单例类的代码。此外,单例模式引入了全局状态,可能会增加代码的耦合度,使得代码难以维护和扩展。

  7. 合理使用:单例模式应该谨慎使用,只在确实需要全局唯一实例的场景下使用。滥用单例模式可能会导致代码的复杂性增加、可维护性下降等问题。

在使用单例模式时,需要综合考虑以上注意事项,并根据具体的应用场景和需求来评估单例模式的适用性。确保理解单例模式的优缺点,并在设计时遵循设计原则,以提高代码的可维护性、扩展性和安全性。

单例防止反射漏洞攻击

在某些情况下,单例模式可以用于防止反射漏洞攻击。反射漏洞攻击是指攻击者利用反射机制来修改或绕过程序的原本逻辑,从而执行恶意代码或获取非授权的访问权限。

通过合理设计单例模式,可以增加对反射漏洞攻击的防护。以下是一些常见的防护方法:

  1. 私有构造函数:单例类的构造函数应该设置为私有,这样可以防止外部通过反射机制直接调用构造函数创建新的实例。

  2. 防止反射实例化:在单例类的构造函数中,可以增加逻辑判断,如果已经存在实例,则抛出异常,防止通过反射强制创建新的实例。

  3. 序列化和反序列化控制:如果单例类需要支持序列化和反序列化,可以通过实现readResolve()方法,确保在反序列化时返回单例对象,防止通过反序列化创建新的实例。

需要注意的是,单纯依靠单例模式并不能完全防止反射漏洞攻击。攻击者可能采用其他方法来绕过单例模式的限制。为了更有效地防止反射漏洞攻击,还应结合其他安全措施,如输入验证、安全编码实践、访问控制等。

单例创建方式

在实现单例模式时,有多种方式可以创建单例对象。以下是几种常见的单例创建方式:

  1. 饿汉式(Eager Initialization):

    public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {// 私有构造函数}public static Singleton getInstance() {return instance;}
    }
    

    这种方式在类加载时就创建了单例对象,因此是线程安全的。但它可能会导致不必要的资源浪费,因为单例对象的创建是提前进行的。

  2. 懒汉式(Lazy Initialization):

    public class Singleton {private static Singleton instance;private Singleton() {// 私有构造函数}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
    }
    

    这种方式在首次访问时才创建单例对象,延迟了对象的初始化。但它在多线程环境下需要添加同步机制来保证线程安全。

  3. 双重检查锁(Double-Checked Locking):

    public class Singleton {private volatile static Singleton instance;private Singleton() {// 私有构造函数}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
    }
    

    这种方式在首次访问时进行双重检查,避免了每次都进行同步操作,提高了性能。使用volatile关键字修饰instance变量可以确保多线程环境下的可见性。

  4. 静态内部类(Static Inner Class):

    public class Singleton {private Singleton() {// 私有构造函数}private static class SingletonHolder {private static final Singleton instance = new Singleton();}public static Singleton getInstance() {return SingletonHolder.instance;}
    }
    

    这种方式利用了类的初始化锁机制,通过静态内部类的方式延迟加载单例对象。它既保证了线程安全性,又延迟了单例对象的创建。

以上是一些常见的单例创建方式,每种方式都有其优缺点和适用场景。在选择单例创建方式时,需要根据具体需求、线程安全性要求和性能考虑做出合适的选择。

如何选择单例创建方式

选择单例创建方式时,需要考虑以下几个因素:

  1. 线程安全性:如果应用程序在多线程环境下使用单例对象,确保线程安全是非常重要的。某些创建方式(如饿汉式)在类加载时就创建了单例对象,因此是线程安全的。其他方式(如懒汉式、双重检查锁)可能需要添加同步机制来保证线程安全。

  2. 延迟初始化:如果单例对象的创建比较耗时或复杂,可以考虑延迟初始化的方式。懒汉式和静态内部类方式都支持延迟初始化,在首次访问时才创建单例对象。

  3. 性能考虑:某些创建方式可能会引入额外的同步开销,影响性能。例如,懒汉式使用了同步机制来保证线程安全性,可能会影响并发性能。双重检查锁方式通过双重检查来避免每次都进行同步操作,提高了性能。

  4. 序列化支持:如果单例对象需要支持序列化和反序列化操作,需要特别注意选择支持序列化的创建方式。静态内部类方式是一个常见的选择,因为静态内部类不会被序列化,可以确保在反序列化时返回单例对象。

  5. 反射攻击防护:如果希望防止反射攻击,需要选择一种不容易被反射实例化的创建方式。例如,在构造函数中增加逻辑判断,如果已经存在实例,则抛出异常。

  6. 代码清晰度和可读性:选择创建方式时,也需要考虑代码的清晰度和可读性。一些方式可能会引入更多的代码复杂性,使得代码难以理解和维护。选择简洁、清晰的方式有助于提高代码的可读性。

综合考虑以上因素,选择适合应用程序需求的单例创建方式。根据具体情况,可以进行性能测试和评估,以确保选择的方式满足应用程序的需求,并在性能、线程安全和可维护性之间做出合理的权衡。

工厂模式

怎么理解工厂模式

工厂模式是一种创建型设计模式,旨在提供一种统一的方式来创建对象,而无需直接暴露对象的具体创建逻辑。它将对象的实例化过程封装在工厂类中,客户端只需通过工厂类来请求创建对象,而无需直接与具体的对象类交互。

工厂模式主要包含以下几个角色:

  1. 抽象产品(Abstract Product):定义了产品的接口或抽象类,描述了产品的共同特性和行为。所有具体产品类都必须实现或继承抽象产品。

  2. 具体产品(Concrete Product):实现了抽象产品接口或继承了抽象产品类,是工厂模式中真正被创建的对象。

  3. 抽象工厂(Abstract Factory):定义了创建产品的接口,声明了创建产品的抽象方法。它可以是接口或抽象类,可以包含多个用于创建不同产品的方法。

  4. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品的对象。它根据客户端的请求,选择合适的具体产品进行实例化。

工厂模式的核心思想是将对象的创建与使用分离,客户端只需要依赖于抽象工厂和抽象产品,而不需要直接依赖于具体的产品类。这样可以实现代码的解耦,提高代码的灵活性和可维护性。通过工厂模式,可以轻松地扩展或替换具体产品的实现,而无需修改客户端代码。

工厂模式适用于以下情况:

  1. 当需要创建一组相关的对象时,可以使用工厂模式来统一管理对象的创建过程,提供一致的接口。

  2. 当对象的创建逻辑比较复杂,涉及到多个步骤或依赖关系时,可以使用工厂模式来封装这些复杂的创建过程。

  3. 当希望通过扩展或替换具体产品的实现来实现灵活性时,可以使用工厂模式。

总而言之,工厂模式通过将对象的创建封装在工厂类中,实现了对象创建与使用的解耦,提供了一种简洁、灵活的对象创建方式。它是一种常用的设计模式,可以在软件开发中提高代码的可扩展性、可维护性和可测试性。

工厂模式分类

工厂模式可以根据其实现方式和结构特点进行分类。以下是几种常见的工厂模式分类:

  1. 简单工厂模式(Simple Factory Pattern):简单工厂模式又称为静态工厂模式,它由一个工厂类负责创建不同类型的对象。客户端通过向工厂类传递参数来请求创建不同的产品对象。这种模式的特点是工厂类负责所有产品的创建逻辑,客户端只需与工厂类交互,而无需直接与具体产品类交互。但简单工厂模式违背了开闭原则,当需要添加新的产品时,需要修改工厂类的代码。

  2. 工厂方法模式(Factory Method Pattern):工厂方法模式使用抽象工厂接口来定义创建对象的方法,具体的对象创建由实现该接口的具体工厂类来完成。每个具体工厂类负责创建特定类型的产品对象。工厂方法模式通过将对象的创建延迟到具体工厂类,实现了对扩展开放、对修改关闭。客户端通过与抽象工厂接口交互,可以根据需要选择具体的工厂类创建对象。

  3. 抽象工厂模式(Abstract Factory Pattern):抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体的类。它包含一个抽象工厂接口和多个具体工厂类,每个具体工厂类负责创建某一类产品。抽象工厂模式适用于需要创建一组相关对象的场景,它可以保证创建的对象之间具有一致性,而无需关心具体的实现细节。

  4. 生成器模式(Builder Pattern):生成器模式将一个复杂对象的构建过程分解为多个简单的步骤,并提供一个指导者(Director)来按照特定的顺序调用生成器(Builder)的方法来构建对象。生成器模式主要关注对象的构建过程,而不同于工厂模式那样直接返回最终构建好的对象。生成器模式适用于创建复杂对象,且构建过程需要多个步骤或者有不同的实现方式的情况。

这些工厂模式都有各自的特点和适用场景,选择合适的工厂模式取决于具体的需求和设计目标。

工厂模式优缺点

工厂模式具有以下优点:

  1. 封装了对象的创建逻辑:工厂模式通过将对象的创建逻辑封装在工厂类中,使得客户端无需关心具体的创建过程,只需通过工厂类来请求创建对象。这样可以提高代码的灵活性和可维护性,减少了客户端与具体产品之间的依赖关系。

  2. 提供了一致的接口:工厂模式定义了一致的接口来创建对象,使得客户端可以统一使用抽象工厂和抽象产品,而无需直接与具体产品类交互。这样可以降低代码的耦合度,便于扩展和替换具体产品的实现。

  3. 支持扩展性:工厂模式通过抽象工厂和抽象产品的定义,可以轻松地扩展新的具体工厂和产品类。在需要添加新的产品时,只需创建相应的具体产品和具体工厂,并实现工厂接口,而无需修改现有的代码。

  4. 降低了代码重复:如果多个地方需要创建相同类型的对象,使用工厂模式可以避免重复的创建代码。通过工厂类集中管理对象的创建逻辑,可以提高代码的复用性。

工厂模式也有一些缺点:

  1. 增加了类的数量:引入工厂模式会增加额外的类,包括抽象工厂、具体工厂和具体产品类。如果只有少量的产品需要创建,使用工厂模式可能会增加代码的复杂性。

  2. 需要理解和维护额外的代码:工厂模式引入了额外的抽象层和接口,需要理解和维护这些额外的代码结构。这可能会增加开发和维护的工作量。

  3. 不适合简单的对象创建:工厂模式主要适用于对象创建过程比较复杂、涉及多个步骤或依赖关系的情况。对于简单的对象创建,使用工厂模式可能会显得繁琐和不必要。

总体而言,工厂模式是一种常用的设计模式,可以提供一种统一的对象创建方式,封装了对象的创建逻辑,提高了代码的灵活性和可维护性。但在使用工厂模式时,需要权衡其优缺点,根据具体的需求和情况做出合理的选择。

为什么要学习工厂设计模式

学习工厂设计模式有以下几个重要的原因:

  1. 提高代码的可维护性和可扩展性:工厂模式可以将对象的创建逻辑封装在工厂类中,使得客户端与具体产品类解耦,提高了代码的可维护性。当需要添加新的产品时,只需创建相应的具体产品和具体工厂,并实现工厂接口,而无需修改现有的代码。这样可以降低代码的耦合度,便于扩展和修改。

  2. 代码重用和一致性:工厂模式可以集中管理对象的创建逻辑,避免在多个地方重复编写创建代码。通过使用工厂类来创建对象,可以保持代码的一致性,提高代码的复用性。

  3. 简化复杂对象的创建过程:某些对象的创建过程可能比较复杂,涉及多个步骤或依赖关系。工厂模式可以将这些复杂的创建过程封装在工厂类中,使得客户端可以简单地通过工厂类来请求创建对象,而无需关心具体的创建细节。

  4. 符合面向对象设计原则:工厂模式符合面向对象设计的开闭原则(Open-Closed Principle),即对扩展开放,对修改关闭。通过使用工厂模式,可以通过扩展具体工厂和产品类来添加新的功能,而无需修改现有的代码。

  5. 增加设计模式的理解和应用能力:工厂模式是一种常用的设计模式,在实际的软件开发中经常会遇到。学习工厂模式可以增加对设计模式的理解和应用能力,使得我们能够更好地理解和使用其他设计模式。

总而言之,学习工厂设计模式可以提高代码的可维护性和可扩展性,简化复杂对象的创建过程,增加代码的重用性和一致性。它是一种常用且重要的设计模式,在软件开发中具有广泛的应用。

Spring开发中的工厂设计模式

在Spring开发中,工厂设计模式主要体现在以下两个方面:

  1. Spring Bean工厂:Spring框架提供了一个Bean工厂(Bean Factory)作为对象的创建和管理中心。Bean工厂负责创建、配置和管理应用中的对象(Bean),它将对象的创建逻辑封装在内部,客户端只需要通过Bean工厂获取需要的对象,而无需直接与具体的对象类交互。Spring Bean工厂使用了工厂模式的思想,将对象的创建与使用解耦,提供了一种统一的对象创建方式。常见的Spring Bean工厂实现包括XML配置的ClassPathXmlApplicationContext、注解配置的AnnotationConfigApplicationContext等。

  2. Spring工厂方法模式:在Spring中,可以使用工厂方法模式(Factory Method Pattern)来创建和管理对象。工厂方法模式通过定义一个工厂接口,该接口包含用于创建对象的方法,具体的对象创建由实现该接口的具体工厂类来完成。Spring中的BeanFactory接口就是一个典型的工厂方法模式的应用,它定义了获取Bean的方法,具体的Bean创建由不同的实现类(如XmlBeanFactory、DefaultListableBeanFactory等)来完成。通过工厂方法模式,Spring可以根据配置文件或注解等方式动态地创建和管理对象,实现了对象的可配置和可扩展。

工厂设计模式在Spring开发中的应用,可以提供灵活的对象创建和管理方式,降低代码的耦合度,提高代码的可维护性和可扩展性。Spring的IoC(Inversion of Control)容器利用工厂设计模式实现了对象的创建和依赖注入,使得开发者能够专注于业务逻辑的实现,而无需过多关注对象的创建和管理过程。

简单工厂模式

简单工厂模式(Simple Factory Pattern),也被称为静态工厂模式,是一种创建型设计模式,它通过一个工厂类根据客户端的请求创建不同的产品对象。客户端无需直接实例化具体的产品类,而是通过工厂类来创建所需的产品。

以下是一个使用Java代码示例,演示了简单工厂模式的实现:

首先,我们定义抽象产品接口和具体产品类:

// 抽象产品接口
public interface Button {void render();
}// 具体产品类A
public class WindowsButton implements Button {@Overridepublic void render() {System.out.println("Rendering a button in Windows style.");}
}// 具体产品类B
public class MacOSButton implements Button {@Overridepublic void render() {System.out.println("Rendering a button in macOS style.");}
}

然后,我们创建一个简单工厂类,用于根据客户端的请求创建具体产品对象:

// 简单工厂类
public class ButtonFactory {public static Button createButton(String type) {if (type.equalsIgnoreCase("Windows")) {return new WindowsButton();} else if (type.equalsIgnoreCase("MacOS")) {return new MacOSButton();} else {throw new IllegalArgumentException("Invalid button type.");}}
}

最后,我们可以编写客户端代码来使用简单工厂模式:

public class Client {public static void main(String[] args) {// 使用简单工厂创建Windows风格的按钮Button windowsButton = ButtonFactory.createButton("Windows");// 渲染按钮windowsButton.render();// 使用简单工厂创建macOS风格的按钮Button macOSButton = ButtonFactory.createButton("MacOS");// 渲染按钮macOSButton.render();}
}

运行以上代码,输出结果如下:

Rendering a button in Windows style.
Rendering a button in macOS style.

在这个示例中,简单工厂类(ButtonFactory)根据客户端传入的类型参数(“Windows"或"MacOS”)创建相应的具体产品对象(WindowsButtonMacOSButton)。客户端代码通过调用简单工厂类的静态方法(createButton())来获取所需的产品对象,并使用产品对象的方法来实现相应的功能。

简单工厂模式的优点包括:

  • 客户端代码与具体产品类解耦,客户端只需要依赖于工厂类。
  • 可以通过修改工厂类的代码来创建不同的产品对象,而无需修改客户端代码。
  • 简化了对象的创建过程,客户端只需要提供合法的类型参数即可获取相应的产品对象。

然而,简单工厂模式的缺点是当需要新增或修改产品时,需要修改工厂类的代码,违反了开闭原则。

简单工厂模式适用于以下情况:

  • 客户端只需要获取产品对象,无需关心具体的创建细节。
  • 只有少量具体产品类,不需要频繁地添加或修改产品。

通过简单工厂模式,我们可以实现对象的统一创建和管理,提高代码的可维护性和可扩展性。

工厂方法模式

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个创建对象的接口,但将具体对象的创建延迟到子类中进行。这样,客户端代码通过与抽象工厂接口进行交互,而无需关心具体的产品类。

以下是一个使用Java代码示例,演示了工厂方法模式的实现:

首先,我们定义抽象产品接口和具体产品类:

// 抽象产品接口
public interface Button {void render();
}// 具体产品类A
public class WindowsButton implements Button {@Overridepublic void render() {System.out.println("Rendering a button in Windows style.");}
}// 具体产品类B
public class MacOSButton implements Button {@Overridepublic void render() {System.out.println("Rendering a button in macOS style.");}
}

接下来,我们定义抽象工厂接口和具体工厂类:

// 抽象工厂接口
public interface GUIFactory {Button createButton();
}// 具体工厂类A,用于创建Windows风格的产品
public class WindowsFactory implements GUIFactory {@Overridepublic Button createButton() {return new WindowsButton();}
}// 具体工厂类B,用于创建macOS风格的产品
public class MacOSFactory implements GUIFactory {@Overridepublic Button createButton() {return new MacOSButton();}
}

最后,我们可以编写客户端代码来使用工厂方法模式:

public class Client {public static void main(String[] args) {// 创建Windows风格的工厂GUIFactory windowsFactory = new WindowsFactory();// 使用工厂创建按钮Button windowsButton = windowsFactory.createButton();// 渲染按钮windowsButton.render();// 创建macOS风格的工厂GUIFactory macOSFactory = new MacOSFactory();// 使用工厂创建按钮Button macOSButton = macOSFactory.createButton();// 渲染按钮macOSButton.render();}
}

运行以上代码,输出结果如下:

Rendering a button in Windows style.
Rendering a button in macOS style.

在这个示例中,工厂方法模式通过抽象工厂接口(GUIFactory)定义了创建按钮的方法(createButton()),具体工厂类(WindowsFactoryMacOSFactory)分别实现了该接口,并负责创建相应风格的按钮。客户端代码通过与抽象工厂接口进行交互,调用工厂方法来创建按钮对象,并使用按钮对象的方法来实现相应的功能。

工厂方法模式的优点包括:

  • 客户端代码与具体产品类解耦,客户端只需要依赖于抽象工厂接口。
  • 可以通过定义新的具体工厂类来扩展产品族,增加新的产品类型,而无需修改现有的代码。
  • 符合开闭原则,对修改关闭、对扩展开放。

工厂方法模式适用于以下情况:

  • 客户端代码需要与多个相关的产品对象进行交互,但无需关心具体的产品类。
  • 需要在运行时动态决定创建哪个具体产品对象。
  • 需要扩展产品族时,只需添加新的具体工厂和具体产品类,无需修改现有的代码。

通过工厂方法模式,我们可以实现更灵活、可扩展的对象创建方式,提高代码的可维护性和可扩展性。

代理模式

怎么理解代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过代理对象控制对真实对象的访问。代理对象充当了客户端和真实对象之间的中介,客户端通过代理对象来间接访问真实对象,从而可以在访问前后进行一些额外的操作。

代理模式通常涉及三个角色:

  1. 抽象主题(Subject):定义了真实主题和代理主题之间的共同接口,客户端通过该接口来访问真实主题和代理主题。

  2. 真实主题(Real Subject):定义了真正的业务逻辑,是代理模式中所关注的核心对象。

  3. 代理主题(Proxy Subject):实现了抽象主题接口,持有一个真实主题的引用,充当了客户端和真实主题之间的中介。代理主题可以在访问真实主题前后进行一些额外的操作,例如权限验证、缓存、延迟加载等。

代理模式的理解可以从以下几个方面入手:

  1. 控制访问:代理模式允许代理对象控制对真实对象的访问。代理对象可以在访问真实对象之前进行一些准备工作,例如权限验证、资源分配等,也可以在访问真实对象之后进行一些收尾工作,例如日志记录、缓存更新等。通过代理对象的控制,可以对真实对象的访问进行灵活的管理。

  2. 隔离复杂性:代理模式可以将复杂的业务逻辑封装在代理对象中,使得客户端不需要关心真实对象的复杂性。客户端只需要与代理对象进行交互,代理对象负责处理真实对象的创建、销毁、维护等操作,将复杂性隔离在代理对象中,简化了客户端的代码。

  3. 延迟加载:代理模式可以支持延迟加载,也称为懒加载。当客户端需要访问真实对象时,代理对象可以根据需要动态地创建真实对象,并将其初始化。这种延迟加载的方式可以提高系统的性能和资源利用率,特别是在真实对象的创建和初始化过程比较耗时或资源密集的情况下。

  4. 安全性增强:代理模式可以增强系统的安全性。代理对象可以充当一个安全层,对真实对象的访问进行控制和限制,例如进行身份验证、访问权限验证等。通过代理对象的安全性增强,可以保护真实对象免受非法访问和潜在风险。

总之,代理模式通过引入代理对象来控制对真实对象的访问,并在访问前后进行一些额外的操作,从而增强了系统的灵活性、安全性和性能。它可以应用于各种场景,例如远程代理、虚拟代理、缓存代理、保护代理等,以满足不同的需求。

代理模式应用场景

代理模式可以应用于多种场景,以下是一些常见的应用场景:

  1. 远程代理(Remote Proxy):当真实对象存在于远程服务器上时,客户端无法直接访问真实对象,而是通过代理对象与远程服务器进行通信。代理对象负责处理网络通信、数据传输等细节,隐藏了远程调用的复杂性,使得客户端可以像访问本地对象一样访问远程对象。

  2. 虚拟代理(Virtual Proxy):当真实对象的创建和初始化过程比较耗时时,可以使用虚拟代理延迟加载真实对象。代理对象在客户端需要访问真实对象时,先创建一个占位符对象,等到真正需要使用真实对象时再进行加载和初始化。这样可以避免不必要的开销,提高系统性能。

  3. 安全代理(Security Proxy):代理对象可以充当一个安全层,对真实对象的访问进行控制和限制。代理对象可以进行身份验证、访问权限验证等操作,确保只有经过授权的客户端可以访问真实对象,增强系统的安全性。

  4. 缓存代理(Caching Proxy):代理对象可以缓存真实对象的结果,以提供快速访问。当客户端请求相同的操作时,代理对象可以直接返回缓存的结果,避免了每次都重新执行真实对象的操作,提高了系统的响应速度和性能。

  5. 日志记录(Logging):代理对象可以在访问真实对象前后进行日志记录,用于跟踪和调试系统的运行情况。代理对象可以记录请求的参数、执行时间、返回结果等信息,帮助开发人员进行错误排查和性能优化。

  6. 延迟加载(Lazy Initialization):代理对象可以延迟加载真实对象,只有在真正需要使用真实对象时才进行创建和初始化。这种延迟加载的方式可以节省系统资源,并提高系统的启动速度。

需要注意的是,代理模式并不是适用于所有情况的通用解决方案,应根据具体的需求和情况来判断是否使用代理模式。在使用代理模式时,需要权衡代理对象的引入对系统的复杂性、性能等方面的影响,并确保代理模式能够带来实际的好处和价值。

代理模式的分类

代理模式可以根据代理对象的创建方式和使用方式进行分类。以下是代理模式的几种常见分类:

  1. 静态代理(Static Proxy):在静态代理中,代理对象在编译时就已经确定,并且代理对象和真实对象实现相同的接口或继承相同的父类。静态代理需要为每个真实对象创建一个对应的代理对象,并在代理对象中调用真实对象的方法。静态代理的优点是简单易懂,缺点是需要为每个真实对象创建代理对象,导致类的数量增加。

  2. 动态代理(Dynamic Proxy):在动态代理中,代理对象是在运行时动态生成的,不需要为每个真实对象创建一个对应的代理对象。Java中的动态代理机制是通过反射实现的,可以动态地在运行时创建代理对象,从而在代理对象中调用真实对象的方法。动态代理的优点是可以减少代理对象的数量,缺点是相对于静态代理会增加一些复杂性。

  3. 远程代理(Remote Proxy):远程代理用于在不同的地址空间中代表真实对象,通过网络通信的方式进行访问。客户端通过远程代理对象与远程服务器进行通信,代理对象负责处理网络通信、数据传输等细节。远程代理可以隐藏真实对象的具体实现细节,使得客户端可以像访问本地对象一样访问远程对象。

  4. 虚拟代理(Virtual Proxy):虚拟代理用于延迟加载真实对象,也称为懒加载。当客户端需要访问真实对象时,虚拟代理会先创建一个占位符对象,等到真正需要使用真实对象时才进行加载和初始化。虚拟代理可以避免不必要的开销,提高系统性能。

  5. 缓存代理(Caching Proxy):缓存代理用于缓存真实对象的结果,以提供快速访问。代理对象会在第一次访问真实对象后将结果缓存起来,当客户端再次请求相同的操作时,代理对象可以直接返回缓存的结果,避免了每次都重新执行真实对象的操作,提高了系统的响应速度和性能。

  6. 安全代理(Security Proxy):安全代理用于控制对真实对象的访问权限。代理对象可以进行身份验证、访问权限验证等操作,确保只有经过授权的客户端可以访问真实对象,增强系统的安全性。

这些分类并不是互斥的,实际上一个代理模式可能同时具有多种分类的特点。根据具体的需求和情况,可以选择合适的代理模式来实现系统的功能和要求。

三种代理的区别

在代理模式中,常见的三种代理类型是静态代理、动态代理和远程代理。它们之间的区别主要体现在以下几个方面:

  1. 创建方式:

    • 静态代理:在编译时期就已经创建好代理对象,代理对象和真实对象实现相同的接口或继承相同的父类。
    • 动态代理:在运行时动态生成代理对象,不需要为每个真实对象创建一个对应的代理对象。代理对象是根据接口动态生成的,利用反射机制实现。
    • 远程代理:用于在不同的地址空间中代表真实对象,通过网络通信的方式进行访问。
  2. 对象关系:

    • 静态代理:代理对象和真实对象之间的关系是一对一的关系,每个真实对象都需要对应一个代理对象。
    • 动态代理:代理对象和真实对象之间的关系是一对多的关系,可以为多个真实对象共享一个代理对象。
    • 远程代理:代理对象和真实对象之间的关系是一对多的关系,代理对象可以代理多个真实对象,这些真实对象可以位于不同的地址空间。
  3. 实现机制:

    • 静态代理:在编译时期就已经确定代理对象和真实对象的关系,代理对象直接调用真实对象的方法。
    • 动态代理:在运行时动态生成代理对象,代理对象通过反射机制调用真实对象的方法。Java中的动态代理机制主要是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。
    • 远程代理:代理对象通过网络通信的方式与真实对象进行交互,代理对象负责处理网络通信、数据传输等细节。
  4. 应用场景:

    • 静态代理:适用于在编译时期就已经确定代理对象和真实对象的关系,且代理对象的数量有限的情况。
    • 动态代理:适用于在运行时动态生成代理对象的情况,可以减少代理对象的数量,提高代码的灵活性。
    • 远程代理:适用于真实对象存在于远程服务器上,需要通过网络通信进行访问的情况。

总之,静态代理在编译时期确定代理对象,动态代理在运行时动态生成代理对象,远程代理用于访问存在于远程服务器上的真实对象。选择适当的代理类型取决于具体的需求和场景。

使用代码演示上述三种代理

下面我将使用Java代码为您演示静态代理、动态代理和远程代理的示例。

  1. 静态代理示例:
// 定义接口
interface Image {void display();
}// 定义真实对象
class RealImage implements Image {private String filename;public RealImage(String filename) {this.filename = filename;loadFromDisk();}private void loadFromDisk() {System.out.println("Loading image: " + filename);}public void display() {System.out.println("Displaying image: " + filename);}
}// 定义代理对象
class ImageProxy implements Image {private RealImage realImage;private String filename;public ImageProxy(String filename) {this.filename = filename;}public void display() {if (realImage == null) {realImage = new RealImage(filename);}realImage.display();}
}// 使用示例
public class StaticProxyExample {public static void main(String[] args) {Image image = new ImageProxy("image.jpg");// 第一次调用会创建真实对象image.display();// 第二次调用直接使用缓存的真实对象image.display();}
}
  1. 动态代理示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 定义接口
interface Image {void display();
}// 定义真实对象
class RealImage implements Image {private String filename;public RealImage(String filename) {this.filename = filename;loadFromDisk();}private void loadFromDisk() {System.out.println("Loading image: " + filename);}public void display() {System.out.println("Displaying image: " + filename);}
}// 定义动态代理处理器
class ImageProxyHandler implements InvocationHandler {private Object realObject;public ImageProxyHandler(Object realObject) {this.realObject = realObject;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;// 在调用真实对象方法之前可以进行相关操作System.out.println("Before displaying image");result = method.invoke(realObject, args);// 在调用真实对象方法之后可以进行相关操作System.out.println("After displaying image");return result;}
}// 使用示例
public class DynamicProxyExample {public static void main(String[] args) {Image realImage = new RealImage("image.jpg");Image imageProxy = (Image) Proxy.newProxyInstance(realImage.getClass().getClassLoader(),realImage.getClass().getInterfaces(),new ImageProxyHandler(realImage));imageProxy.display();}
}
  1. 远程代理示例:
// 定义接口
interface Image {void display();
}// 定义远程代理对象
class RemoteImageProxy implements Image {private String imageUrl;public RemoteImageProxy(String imageUrl) {this.imageUrl = imageUrl;}public void display() {// 在此处实现远程调用逻辑,例如通过网络获取图片并进行展示System.out.println("Displaying remote image: " + imageUrl);}
}// 使用示例
public class RemoteProxyExample {public static void main(String[] args) {Image image = new RemoteImageProxy("https://example.com/image.jpg");image.display();}
}

请注意,这些示例只是为了演示代理模式的不同类型,并可能存在简化。在实际应用中,可能需要更复杂的实现,例如添加更多的逻辑和错误处理。

建造者模式

怎么理解建造者模式

建造者模式(Builder Pattern)是一种创建型设计模式,它的主要目的是将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

理解建造者模式可以从以下几个方面入手:

  1. 分离构建过程和表示:
    建造者模式将一个复杂对象的构建过程拆分成多个步骤,每个步骤由一个具体的建造者负责完成。这样就可以将构建过程和最终的对象表示分离开来。通过定义统一的构建接口,客户端代码可以灵活地指定构建过程中的步骤顺序,从而创建不同的表示。

  2. 组装复杂对象:
    建造者模式通过一系列的构建步骤来逐步构建复杂对象。每个具体的建造者负责实现一个或多个构建步骤,将最终结果保存在一个产品对象中。通过组合不同的构建步骤,可以创建不同的对象表示,而且构建过程可以灵活地扩展和修改。

  3. 隔离客户端和具体构建过程:
    建造者模式将复杂对象的构建过程封装在具体的建造者类中,客户端只需关注如何指定构建的步骤和顺序,而不需要了解具体的构建细节。这样可以降低客户端代码与具体构建过程的耦合度,使得客户端更加简洁和易于维护。

  4. 创建不同的表示:
    建造者模式通过不同的具体建造者来创建不同的对象表示。每个具体建造者实现自己的构建步骤,可以根据需要创建不同的对象。通过使用不同的建造者,可以以统一的方式构建出不同的对象,而无需改变客户端代码。

总之,建造者模式通过将复杂对象的构建过程和表示分离,使得构建过程灵活可扩展,同时隔离了客户端和具体构建过程的耦合。这种模式适用于需要创建复杂对象,并且对象的构建过程可能存在多个步骤、变化较大或者需要灵活组装的情况。

建造者模式的分类

建造者模式可以根据实现方式的不同进行分类。常见的分类包括以下两种:

  1. 静态建造者模式(Static Builder Pattern):
    静态建造者模式是建造者模式的一种变体,它使用静态方法来创建和配置复杂对象。在静态建造者模式中,通常将建造者方法定义为静态方法,通过链式调用来配置对象的属性和参数,最后调用一个静态的构建方法来获取最终的对象实例。这种方式可以简化客户端代码的编写,并且在链式调用中提供了一种流畅的语法。

  2. 动态建造者模式(Dynamic Builder Pattern):
    动态建造者模式是建造者模式的另一种变体,它使用动态方法链来创建和配置复杂对象。在动态建造者模式中,建造者类通常使用方法链的方式来设置对象的属性和参数,每个方法都返回建造者对象本身,以便可以连续地进行方法调用。最后,调用一个构建方法来获取最终的对象实例。这种方式的优点是可以在运行时动态地配置对象的属性和参数,更加灵活。

无论是静态建造者模式还是动态建造者模式,它们都遵循了建造者模式的基本原则,即将复杂对象的构建过程与其表示分离,并提供一种灵活的方式来创建不同的对象表示。具体选择哪种方式取决于项目的需求和设计偏好。
当谈到建造者模式时,还有一些相关的概念和要点可以进一步了解:

  1. 产品(Product):
    产品是由建造者模式创建的复杂对象。产品类通常包含多个属性和方法,用于描述和操作该对象的特征和行为。

  2. 建造者(Builder):
    建造者是一个接口或抽象类,定义了构建复杂对象的各个步骤和方法。建造者通常包含一系列的方法,用于设置对象的属性和参数。

  3. 具体建造者(Concrete Builder):
    具体建造者是实现了建造者接口的具体类。每个具体建造者负责实现构建过程中的具体步骤和方法。它持有一个产品对象,并将最终的结果保存在产品中。

  4. 指挥者(Director):
    指挥者是负责使用建造者对象构建复杂对象的类。它定义了一个构建方法,该方法接收一个建造者对象作为参数,并按照一定的步骤和顺序调用建造者的方法来构建对象。指挥者可以控制和管理构建过程。

  5. 客户端(Client):
    客户端是使用建造者模式的代码。客户端通常通过实例化一个具体建造者对象,并将其传递给指挥者来构建复杂对象。客户端可以根据需要选择不同的建造者和配置选项,从而创建不同的对象。

  6. 可选步骤(Optional Steps):
    在建造者模式中,某些步骤可能是可选的,即客户端可以选择性地调用或不调用这些步骤。建造者模式允许客户端根据实际需求来定制构建过程,从而创建不同配置的对象。

  7. 建造者模式与工厂模式的区别:
    建造者模式和工厂模式都是创建型设计模式,但它们的关注点不同。工厂模式关注于创建对象,它通过一个工厂类来封装对象的创建过程,并返回一个已经创建好的对象。而建造者模式关注于构建复杂对象,它将构建过程拆分成多个步骤,并通过建造者类来逐步构建对象,最后返回一个构建好的对象。

建造者模式在实际应用中常用于创建复杂的对象,特别是当对象具有多个可选的属性或参数,并且构建过程可能存在多个步骤和变化时。它可以提供一种灵活的方式来创建不同配置的对象,并将构建过程与表示分离,使代码更加清晰、可维护和可扩展。

建造者模式的应用场景

建造者模式适用于以下场景:

  1. 创建复杂对象:
    当需要创建的对象具有复杂的内部结构,包含多个属性或参数,并且构建过程涉及多个步骤和变化时,建造者模式可以提供一种有效的方式来构建该对象。

  2. 隔离构建过程和表示:
    如果希望将对象的构建过程与最终的表示分离开来,使得构建过程可以独立地扩展和变化,建造者模式是一种常用的设计模式。

  3. 创建不同配置的对象:
    当需要创建多个配置不同的对象,而且这些对象的构建过程有一定的相似性时,可以使用建造者模式。通过使用不同的具体建造者来配置对象的不同属性和参数,可以灵活地创建不同的对象。

  4. 避免重叠构造函数:
    如果一个类存在多个构造函数,而且构造函数的参数组合较多,导致构造函数的数量庞大且难以维护,可以考虑使用建造者模式。通过将构造函数的参数转移到建造者类中,并使用链式调用的方式来设置参数,可以简化构造函数的定义和使用。

  5. 创建不可变对象:
    当需要创建不可变对象时,建造者模式可以很好地支持。通过将对象的属性和参数设置为只读,并在建造过程中逐步构建对象,最后返回一个不可变的对象实例。

  6. 创建对象的过程需要细粒度控制:
    当需要对对象的构建过程进行细粒度的控制和管理时,建造者模式可以提供一种灵活的方式。通过在指挥者中定义构建过程的步骤和顺序,可以精确地控制对象的创建过程。

总之,建造者模式适用于需要创建复杂对象,且构建过程与表示分离、可扩展和灵活的情况。它可以提供一种清晰、可维护和可扩展的方式来创建不同配置的对象。

代码演示

当使用建造者模式时,通常需要定义产品类、建造者接口或抽象类、具体建造者类和指挥者类。以下是一个简单的代码示例,演示了如何使用建造者模式来构建一个电脑对象。

首先,定义产品类 Computer

class Computer:def __init__(self):self.cpu = Noneself.memory = Noneself.storage = Nonedef set_cpu(self, cpu):self.cpu = cpudef set_memory(self, memory):self.memory = memorydef set_storage(self, storage):self.storage = storagedef display(self):print("Computer configuration:")print("CPU:", self.cpu)print("Memory:", self.memory)print("Storage:", self.storage)

然后,定义建造者接口 ComputerBuilder

from abc import ABC, abstractmethodclass ComputerBuilder(ABC):@abstractmethoddef build_cpu(self):pass@abstractmethoddef build_memory(self):pass@abstractmethoddef build_storage(self):pass@abstractmethoddef get_computer(self):pass

接下来,实现具体建造者类 DesktopBuilderLaptopBuilder

class DesktopBuilder(ComputerBuilder):def __init__(self):self.computer = Computer()def build_cpu(self):self.computer.set_cpu("Intel Core i7")def build_memory(self):self.computer.set_memory("16GB DDR4")def build_storage(self):self.computer.set_storage("1TB HDD")def get_computer(self):return self.computerclass LaptopBuilder(ComputerBuilder):def __init__(self):self.computer = Computer()def build_cpu(self):self.computer.set_cpu("Intel Core i5")def build_memory(self):self.computer.set_memory("8GB DDR4")def build_storage(self):self.computer.set_storage("512GB SSD")def get_computer(self):return self.computer

最后,定义指挥者类 Director,负责控制建造过程:

class Director:def __init__(self, builder):self.builder = builderdef construct_computer(self):self.builder.build_cpu()self.builder.build_memory()self.builder.build_storage()def get_computer(self):return self.builder.get_computer()

现在,我们可以使用建造者模式来构建具体的电脑对象:

desktop_builder = DesktopBuilder()
director = Director(desktop_builder)
director.construct_computer()
desktop_computer = director.get_computer()
desktop_computer.display()laptop_builder = LaptopBuilder()
director = Director(laptop_builder)
director.construct_computer()
laptop_computer = director.get_computer()
laptop_computer.display()

输出结果:

Computer configuration:
CPU: Intel Core i7
Memory: 16GB DDR4
Storage: 1TB HDDComputer configuration:
CPU: Intel Core i5
Memory: 8GB DDR4
Storage: 512GB SSD

以上示例展示了如何使用建造者模式来构建不同配置的电脑对象。通过定义产品类、建造者接口和具体建造者类,并由指挥者控制建造过程,我们可以灵活地创建具有不同配置的电脑对象。这种方式使得构建过程与表示分离,并提供了可扩展和灵活的创建方式。
祝大家新年快乐,学业有成,万事如意!🚀
嘿嘿都看到这里啦,乖乖 点个赞吧

相关文章:

【后端高频面试题--设计模式上篇】

🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 往期精彩内容 【后端高频面试题–设计模式上篇】 【后端高频面试题–设计模式下篇】 【后端高频…...

P3141 [USACO16FEB] Fenced In P题解

题目 如果此题数据要小一点,那么我们可以用克鲁斯卡尔算法通过,但是这个数据太大了,空间会爆炸,时间也会爆炸。 我们发现,如果用 MST 做,那么很多边的边权都一样,我们可以整行整列地删除。 我…...

Android Compose 一个音视频APP——Magic Music Player

Magic Music APP Magic Music APP Magic Music APP概述效果预览-视频资源功能预览Library歌曲播放效果预览歌曲播放依赖注入设置播放源播放进度上一首&下一首UI响应 歌词歌词解析解析成行逐行解析 视频播放AndroidView引入Exoplayer自定义Exoplayer样式横竖屏切换 歌曲多任…...

Nginx实战:安装搭建

目录 前言 一、yum安装 二、编译安装 1.下载安装包 2.解压 3.生成makefile文件 4.编译 5.安装执行 6.执行命令软连接 7.Nginx命令 前言 nginx的安装有两种方式: 1、yum安装:安装快速,但是无法在安装的时候带上想要的第三方包 2、…...

Qt之条件变量QWaitCondition详解(从使用到原理分析全)

QWaitCondition内部实现结构图: 相关系列文章 C之Pimpl惯用法 目录 1.简介 2.示例 2.1.全局配置 2.2.生产者Producer 2.3.消费者Consumer 2.4.测试例子 3.原理分析 3.1.源码介绍 3.2.辅助函数CreateEvent 3.3.辅助函数WaitForSingleObject 3.4.QWaitCo…...

OpenSource - 一站式自动化运维及自动化部署平台

文章目录 orion-ops 是什么重构特性快速开始技术栈功能预览添砖加瓦License orion-ops 是什么 orion-ops 一站式自动化运维及自动化部署平台, 使用多环境的概念, 提供了机器管理、机器监控报警、Web终端、WebSftp、机器批量执行、机器批量上传、在线查看日志、定时调度任务、应…...

【后端高频面试题--设计模式下篇】

🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 后端高频面试题--设计模式下篇 往期精彩内容设计模式总览模板方法模式怎么理解模板方法模式模板方…...

这才是大学生该做的副业,别再痴迷于游戏了!

感谢大家一直以来的支持和关注,尤其是在我的上一个公众号被关闭后,仍然选择跟随我的老粉丝们,你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴,我决定分享一些实用的干货,这些都是我亲身实践并且取得成功的…...

Ubuntu20.04 安装jekyll

首先使根据官方文档安装:Jekyll on Ubuntu | Jekyll • Simple, blog-aware, static sites 如果没有报错,就不用再继续看下去了。 我这边在执行gem install jekyll bundler时报错,所以安装了rvm,安装rvm可以参考这篇文章Ubuntu …...

AWK语言

一. awk awk:报告生成器,格式化输出。 在 Linux/UNIX 系统中,awk 是一个功能强大的编辑工具,逐行读取输入文本,默认以空格或tab键作为分隔符作为分隔,并按模式或者条件执行编辑命令。而awk比较倾向于将一行…...

精通Nmap:网络扫描与安全的终极武器

一、引言 Nmap,即NetworkMapper,是一款开源的网络探测和安全审计工具。它能帮助您发现网络中的设备,并识别潜在的安全风险。在这个教程中,我们将一步步引导您如何有效地使用Nmap,让您的网络更加安全。 因为Nmap还有图…...

Java 学习和实践笔记(11)

三大神器&#xff1a; 官方网址: http://www.jetbrains.com/idea/ 官方网址: https://code.visualstudio.com/ 官方网址: http://www.eclipse.org 装好了idea社区版&#xff0c;并试运行以下代码&#xff0c;OK&#xff01; //TIP To <b>Run</b> code, press &l…...

开发实体类

开发实体类之间先在pom文件中加入该依赖 <!-- 开发实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency>我们在实体类中声明各个属…...

人工智能学习与实训笔记(十五):Scikit-learn库的基础与使用

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 一、介绍 1. 1 Scikit-learn的发展历程及定义 1.2 理解算法包、算法库及算法框架之间的区别和联系 二、Scikit-learn官网结构 三、安装与设置 3.1 Python环境的安装与配置 3.2 Scikit-lea…...

插值与拟合算法介绍

在数据处理和科学计算领域,插值与拟合是两种极为重要的数据分析方法。它们被广泛应用于信号处理、图像处理、机器学习、金融分析等多个领域,对于理解和预测数据趋势具有至关重要的作用。本文将深入浅出地介绍这两种算法的基本原理,并结合C语言编程环境探讨如何在CSDN开发者社…...

下一代Windows系统曝光:基于GPT-4V,Agent跨应用调度,代号UFO

下一代Windows操作系统提前曝光了&#xff1f;&#xff1f; 微软首个为Windows而设的智能体&#xff08;Agent&#xff09; 亮相&#xff1a; 基于GPT-4V&#xff0c;一句话就可以在多个应用中无缝切换&#xff0c;完成复杂任务。整个过程无需人为干预&#xff0c;其执行成功…...

二.自定义头文件

一.Worker.h 1.1概述 - 类名&#xff1a;Worker - 继承关系&#xff1a;所有其他类&#xff08;Employee、Manager、Boss&#xff09;都继承自该抽象类 - 头文件保护&#xff1a;使用 pragma once 防止头文件重复包含 - 引入标准库&#xff1a;包含 <iostream> 和 <st…...

【AIGC】Stable Diffusion之模型微调工具

推荐一款好用的模型微调工具&#xff0c;cybertron furnace 是一个lora训练整合包&#xff0c;提供训练 lora 模型的工具集或环境。集成环境包括必要的依赖项和配置文件、预训练脚本&#xff0c;支持人物、二次元、画风、自定义lora的训练&#xff0c;以简化用户训练 lora 模型…...

探索未来科技前沿:深度学习的进展与应用

深度学习的进展 摘要&#xff1a;深度学习作为人工智能领域的重要分支&#xff0c;近年来取得了巨大的进展&#xff0c;并在各个领域展现出惊人的应用潜力。本文将介绍深度学习的发展历程、技术原理以及在图像识别、自然语言处理等领域的应用&#xff0c;展望深度学习在未来的…...

PTA | Wifi密码

下面是微博上流传的一张照片&#xff1a;“各位亲爱的同学们&#xff0c;鉴于大家有时需要使用 wifi&#xff0c;又怕耽误亲们的学习&#xff0c;现将 wifi 密码设置为下列数学题答案&#xff1a;A-1&#xff1b;B-2&#xff1b;C-3&#xff1b;D-4&#xff1b;请同学们自己作答…...

Linux中gdb使用说明书

首先我们要使用gdb&#xff0c;必须明白gdb使用范围&#xff1a; 要使用gdb调试&#xff0c;必须在源代码生成二进制程序的时候, 加上 -g 选项&#xff08;gcc/g) 其次&#xff0c;我们就要来学习gdb使用的一些命令了&#xff1a; list&#xff0f;l 行号&#xff1a;显…...

LInux——开发工具的使用

目录 Linux软件包管理器 yum rzsz Linux编辑器——vim vim的使用 vim的基本操作 命令模式的常见命令 底行模式的常见命令 vim是需要配置的 Linux编译器——gcc/g 预处理 编译 汇编 链接 函数库 Linux项目自动化构建工具 make/makefile make原理 项目清理 Linux调试器g…...

沁恒CH32V30X学习笔记03--64位systick

systick CH32F2x 系列产品Cortex-M3 内核自带了一个 24 位自减型计数器(SysTick timer)。支持 HCLK 或 HCLK/8 作为时基,具有较高优先级别(6)。一般可用于操作系统的时基。 CH32V3x 系列产品内核自带了一个 64 位加减计数器(SysTick),支持 HCLK 或者 HCLK/8 作为时基,…...

【JavaEE】IP协议

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…...

计算机网络-数据通信基础

目录 前言 一、数据通信基本概念 二、数据通信相关知识1 总结 前言 正在学习计算机网络体系&#xff0c;把每日所学的知识梳理出来&#xff0c;既能够当作读书笔记&#xff0c;又能分享出来和大家一同学习讨论。 一、数据通信基本概念 基本概念&#xff1a;信源、信道、信宿&…...

【lesson53】线程控制

文章目录 线程控制 线程控制 线程创建 代码&#xff1a; 运行代码&#xff1a; 强调一点&#xff0c;线程和进程不一样&#xff0c;进程有父进程的概念&#xff0c;但在线程组里面&#xff0c;所有的线程都是对等关系。 错误检查: 传统的一些函数是&#xff0c;成功返回0&…...

TypeScript(一):TypeScript基本理解

TypeScript基本理解 为什么使用TS JavaScript发展至今&#xff0c;没有进行数据类型的验证而我们知道&#xff0c;在编程阶段&#xff0c;错误发现的越早越好而TS就解决了JS的这个问题 认识TypeScript TypeScript是拥有类型的JavaScript超级&#xff0c;它可以编译成普通、…...

C语言—指针

碎碎念:做指针题的时候我仿佛回到了原点&#xff0c;总觉得目的是为了把框架搭建起来&#xff0c;我胡说的哈31 1.利用指针变量将一个数组中的数据反向输出。 /*1.利用指针变量将一个数组中的数据反向输出。*/#include <stdio.h> #include <time.h> #include <…...

c++作业

Shell中的函数&#xff08;先调用后使用的原则&#xff09;&#xff08;没有申明&#xff09; &#xff08;Function&#xff09; 函数名&#xff08;有没有参数根据调用格式&#xff09;&#xff08;不能写任何内容&#xff09; { 函数体 Return 返回值 } 函数名 ----》…...

什么是tomcat?tomcat是干什么用的?

前言 Tomcat是一个开源的、轻量级的应用服务器&#xff0c;是Apache软件基金会的一个项目。它实现了Java Servlet、JavaServer Pages&#xff08;JSP&#xff09;和Java Expression Language&#xff08;EL&#xff09;等Java技术&#xff0c;用于支持在Java平台上运行的动态W…...

中科院一区论文复现,改进蜣螂算法,Fuch映射+反向学习+自适应步长+随机差分变异,MATLAB代码...

本期文章复现一篇发表于2024年来自中科院一区TOP顶刊《Energy》的改进蜣螂算法。 论文引用如下&#xff1a; Li Y, Sun K, Yao Q, et al. A dual-optimization wind speed forecasting model based on deep learning and improved dung beetle optimization algorithm[J]. Ener…...

C# 如何实现一个事件总线

EventBus&#xff08;事件总线&#xff09;是一种用于在应用程序内部或跨应用程序组件之间进行事件通信的机制。 它允许不同的组件通过发布和订阅事件来进行解耦和通信。在给定的代码片段中&#xff0c;我们可以看到一个使用C#实现的Event Bus。它定义了一些接口和类来实现事件…...

Python学习路线图

防止忘记&#xff0c;温故知新 进阶路线...

作业2.14

chgrp: 只能修改文件的所属组 chgrp 新的组 文件名 要求&#xff1a;修改的目标组已经存在 chown: chown 新的用户名 文件名 sudo chown root &#xff1a;1 将文件1的所属组用户和所属组用户都改为root sudo chown root&#xff1a;ubuntu 1 将文件1的所属用户…...

基于python+django+mysql的小区物业管理系统

该系统是基于pythondjango开发的小区物业管理系统。适用场景&#xff1a;大学生、课程作业、毕业设计。学习过程中&#xff0c;如遇问题可以在github给作者留言。主要功能有&#xff1a;业主管理、报修管理、停车管理、资产管理、小区管理、用户管理、日志管理、系统信息。 演示…...

控制与状态机算法

控制与状态机算法是计算机科学、电子工程和自动化领域中常用的一种设计工具,它用来描述一个系统的行为,该系统在不同时间点可以处于不同的状态,并且其行为取决于当前状态以及输入的信号或事件。状态机算法的核心概念包括: 状态(State):系统的任何可能配置。每个状态代表…...

sql常用语句小结

创建表&#xff1a; create table 表名&#xff08; 字段1 字段类型 【约束】【comment 字段1注释】&#xff0c; //【】里面的东西可以不用加上去 字段2 字段类型 【约束】【comment 字段2注释】 &#xff09;【comment 表注释】 约束&#xff1a;作用于表中字段上的规则…...

云计算基础-虚拟机迁移原理

什么是虚拟机迁移 虚拟机迁移是指将正在运行的虚拟机实例从一个物理服务器&#xff08;或主机&#xff09;迁移到另一个物理服务器&#xff08;或主机&#xff09;的过程&#xff0c;而不会中断虚拟机的运行。 虚拟机拟机迁移分类虚 热迁移&#xff1a;开机状态下迁移 冷迁…...

云计算基础-云计算概念

云计算定义 云计算是一种基于互联网的计算方式&#xff0c;通过这种计算方式&#xff0c;共享的软硬件资源和信息可以按需提供给计算机和其他设备。云计算依赖资源共享以达成规模经济&#xff0c;类似基础设置(如电力网)。 云计算最基本的概念就是云加端&#xff0c;我们有一个…...

如何将阿里云服务器迁移

&#x1f4d1;前言 本文主要是如何将阿里云服务器迁移实现数据转移的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️** &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日…...

如何将本地的python项目部署到linux服务器中

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 。 前言 本地写好的python项目&#xff0c;如何部署在服务器上运行呢&#xff1f;今天&#xff0c;我们就来抽一点点时间来看看。&#xff08;网上找的资料&#xff0c;大部分都囫囵吞枣的…...

每日五道java面试题之java基础篇(五)

目录&#xff1a; 第一题. final、finally、finalize 的区别&#xff1f;第二题. 和 equals 的区别&#xff1f;第三题.hashCode 与 equals?第四题. Java 是值传递&#xff0c;还是引⽤传递&#xff1f;第五题 深拷贝和浅拷贝&#xff1f; 第一题. final、finally、finalize 的…...

HiveSQL——用户行为路径分析

注&#xff1a;参考文档&#xff1a; SQL之用户行为路径分析--HQL面试题46【拼多多面试题】_路径分析 sql-CSDN博客文章浏览阅读2k次&#xff0c;点赞6次&#xff0c;收藏19次。目录0 问题描述1 数据分析2 小结0 问题描述已知用户行为表 tracking_log&#xff0c; 大概字段有&…...

专利的申请

申请发明或者实用新型专利的&#xff0c;应当提交请求书、说明书及其摘要和权利要求书等文件。 请求书应当写明发明或者实用新型的名称&#xff0c;发明人或者设计人的姓名&#xff0c;申请人姓名或者名称、地址&#xff0c;以及其他事项。 说明书应当对发明或者实用新型作出清…...

嵌入式学习 C++ Day5、6

嵌入式学习 C Day5、6 一、思维导图 二、作业 1.以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴…...

阿里云香港服务器cn2速度测试和租用价格表

阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品&#xff0c;中国电信CN2高速网络高质量、大规格BGP带宽&#xff0c;运营商精品公网直连中国内地&#xff0c;时延更低&#xff0c;优化海外回中国内地流量的公网线路&#xff0c;可以提高国际业务访问质量。阿里云服务…...

《学成在线》微服务实战项目实操笔记系列(P92~P120)【下】

史上最详细《学成在线》项目实操笔记系列【下】&#xff0c;跟视频的每一P对应&#xff0c;全系列18万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。 四、课程发布模块 4.1 (课程发布)模块需求…...

php数据类型以及运算符、判断条件

php数据类型以及运算符 1. php数据类型2. 使用举例3. 运算符4. 判断条件if else elseif 1. php数据类型 包括 String(字符串)、Integer(整型)、Float(浮点型)、Boolean(布尔型)、Array(数组)、Object(对象)、NULL(空值) 2. 使用举例 1.字符串 2.整型 3.浮点型 4.布尔型 5.数组…...

大数据01-导论

零、文章目录 大数据01-导论 1、数据与数据分析 **数据&#xff1a;是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未经加工的原始素材。**数据可以是连续的值&#xff0c;比如声音、图像&#xff0c;称为模拟数据&#xff1b;也可…...

智能网卡(SmartNIC):增强网络性能

在当今的数字时代&#xff0c;网络性能和数据安全是各行各业面临的关键挑战。智能网卡是一项颠覆性的技术创新&#xff0c;对增强网络性能和加强数据安全性具有关键推动作用。本文旨在探讨智能网卡的工作原理及其在不同应用场景中的重要作用。 什么是智能网卡&#xff1f; 智…...