Java常见设计模式(上):创建型模式

设计模式概述

设计模式是从大量的实践中总结和理论化之后优选的代码结构编程风格 、以及问题解决思路。它们如同经典棋谱,不同的棋局不同棋局对应不同"套路",帮助我们高效应对各种编程挑战。

设计原则是一些通用的设计指导方针,它们提供了如何设计一个优秀的软件系统的基本思想和规则。指导着设计者如何组织代码以实现高内聚、低耦合、易扩展易维护的软件系统。

  • 设计模式则是在特定情况下解决常见问题的经验性解决方案,它们提供了如何实现这些设计原则的具体方法。
  • 设计模式往往是在满足设计原则的基础上被应用的。设计模式可以看作是实现设计原则的一种具体方式。

六大设计原则

  • 单一职责原则:一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一项职责。确保类内聚性,降低耦合性。
  • 开闭原则:软件实体(如类、模块、函数)应对扩展开放,对修改关闭,优先通过扩展现有代码实现新功能,而非修改已有代码。
  • 里氏替换原则:子类应能替换父类而不影响程序正确性。子类只能扩展父类功能,不能改变已有行为。所有引用父类的地方,必须能使用子类的对象。
  • 接口隔离原则:客户端不应该依赖于它不需要的接口。一个类应该只提供它需要的接口,而不应该强迫客户端依赖于它不需要的接口。
  • 依赖倒置原则:高层模块不应依赖低层模块,二者应依赖于抽象。抽象不应依赖具体实现,具体实现应依赖抽象。
  • 迪米特法则 :一个对象应该对其他对象保持最少的了解。换句话说,一个对象只应该与它直接相互作用的对象发生交互,而不应该与其它任何对象发生直接的交互。这样可以降低类之间的耦合性,提高系统的灵活性和可维护性。

设计模式分类

设计模式主要有以下三种:

  • 创建型模式,解决对象创建问题。共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,主要解决对象组合问题。共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,主要解决对象之间的交互问题。共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

5种创建型模式详解

创建型模式关注对象的创建与组装,通过抽象化和解耦对象的创建过程,以提高系统灵活性与可扩展性。

单例(Singleton)设计模式

单例模式确保一个类只有一个实例,并提供全局访问点。

实现思路 :如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private ,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
优点:

降低系统性能开销,特别适用于资源消耗大的对象(如配置、数据库连接等)。

应用场景

全局资源管理(如配置文件、日志记录器、数据库连接池等)。

➢网站的计数器,一般也是单例模式实现,否则难以同步。

➢应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一 直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

➢数据库连接池的设计一般也是采用单例模式,如果需要频繁地与数据库交互,使用单例模式可以确保只有一个数据库连接实例,从而减少数据库连接等数量,提高运用程序的性能。

➢项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。

➢Application也是单例的典型应用

➢Windows的Task Manager (任务管理器)就是很典型的单例模式

➢Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

代码实现
饿汉式

bash 复制代码
- 类初始化时直接创建单例对象,而类初始化过程是没有线程安全问题的
class Bank{
    //1.私有化类的构造器
    private Bank(){}
    //2.内部创建类的对象
    //4.要求此对象也必须声明为静态的
    private static Bank instance = new Bank();
    //3.提供公共的静态的方法,返回类的对象
    public static Bank getInstance(){
        return instance;
    }
}

懒汉式

bash 复制代码
-- 延迟创建对象,第一次调用getinstance方法再创建对象
class Order{
    //1.私有化类的构造器
    private Order(){
    }
    
    //2.声明当前类对象,没有初始化
    //4.此对象也必须声明为static的
    private static Order instance = null;
    //3.声明public、static的返回当前类对象的方法
    public static Order getInstance( ){
        if(instance == null){
            instance = new Order();
        }
        return instance;
     }
}

饿汉式VS懒汉式

饿汉式:

  • 坏处:对象加载时间过长。
  • 好处:饿汉式是线程安全的
    懒汉式:
  • 好处:延迟对象的创建。
  • 目前的写法坏处:线程不安全。--->到多线程内容时,再修改

懒汉式-线程安全版

bash 复制代码
class Order{
    private Order(){
    }
    
    private static Order instance = null;
    
    //同步代码块
    public static  Order getInstance( ){
        //方式一:效率稍差
        synchronized(Order.class){
            if(instance == null){
                instance = new Order();
            }
            return instance;
        }
        
        //方式二:效率稍高  当实例没有创建时需要加锁。双重检查锁
        if (instance == null){
             synchronized(Order.class){
                if(instance == null){
                    instance = new Order();
                }
            return instance;
        }        
     }
     
     
    //方法三:同步方法(锁粒度大,比较粗糙)
    public static synchronized Order getInstance( ){
        if(instance == null){
           instance = new Order();
        }
        return instance;
     }
}

工厂模式

工厂模式通过提供一个接口来创建对象,隐藏具体实现细节。
应用场景

1.对象的创建过程比较复杂,需要进行封装 :如果创建一个对象需要进行复杂的初始化过程,或者需要从多个地方获取数据才能创建对象,那么使用工厂模式可以将这些过程封装起来,让客户端代码更加简洁和易于理解。

2.需要动态扩展或修改对象的创建过程 :如果需要增加或修改某个对象的创建过程,而又不希望对客户端代码产生影响,那么使用工厂模式可以很方便地实现这个需求。

3.需要统一管理对象的创建 :如果需要统一管理对象的创建过程,或者需要对创建的对象进行某些统一的处理,那么使用工厂模式可以很好地实现这个需求。

4.需要根据不同的条件创建不同的对象:如果需要根据不同的条件来创建不同类型的对象,那么使用工厂模式可以很方便地实现这个需求。

代码实现

通过一个工厂类来封装对象的创建过程,客户端只需要告诉工厂类需要创建哪种类型的对象即可。将对象的创建过程与客户端代码分离开来,使代码更加灵活和易于扩展

bash 复制代码
// 定义产品接口
public interface Product {
    void operation();
}

// 具体产品类A
public class ConcreteProductA implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductA operation.");
    }
}

// 具体产品类B
public class ConcreteProductB implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductB operation.");
    }
}

// 工厂类
public class SimpleFactory {
    public static Product createProduct(String type) {
        if ("A".equals(type)) {
            return new ConcreteProductA();
        } else if ("B".equals(type)) {
            return new ConcreteProductB();
        } else {
            throw new IllegalArgumentException("Invalid product type.");
        }
    }
}

客户端可以通过调用

bash 复制代码
SimpleFactory.createProduct方法来创建不同类型的产品对象
Product productA = SimpleFactory.createProduct("A");
productA.operation(); // 输出 "ConcreteProductA operation."

Product productB = SimpleFactory.createProduct("B");
productB.operation(); // 输出 "ConcreteProductB operation."

使用小结

在Java中,工厂模式广泛应用于各种框架和类库中,例如JDBC中的DataSource工厂、Spring框架中的Bean工厂、MyBatis框架中的SqlSessionFactory等等。

抽象工厂模式 Abstract factoryPattern

提供一个接口,用于创建相关或依赖对象的序列,而不需要指定实际实现类。

在Java中,抽象工厂模式通常包括以下几个角色:

  • AbstractFactory:抽象工厂,声明了创建产品对象的方法。
  • ConcreteFactory:具体工厂,实现了创建产品对象的方法。
  • AbstractProduct:抽象产品,声明了产品对象的共性接口。
  • Product:具体产品,实现了抽象产品中的抽象方法,构成产品族。
  • Client :客户端,通过调用工厂类的方法创建产品对象。
      抽象工厂和工厂模式都是创建对象的设计模式,它们的主要区别什么呢?
  • 目的不同:工厂模式用于创建一类产品对象的实例,而抽象工厂模式用于创建一组相关的产品对象实例。
  • 实现方式不同:工厂模式中只有一个工厂类,该类负责创建所有的产品对象;而抽象工厂模式中有多个工厂类,每个工厂类负责创建一组相关的产品对象。
  • 范围不同:工厂模式通常用于创建单个对象,而抽象工厂模式通常用于创建一组相关的对象

使用场景

  • 当需要创建一组相关的产品对象,这些产品对象之间有共同的约束条件,需要一起使用时,可以使用抽象工厂模式。
  • 当系统需要独立于产品的创建,组装和表示时,可以使用抽象工厂模式
  • 当系统需要支持多个不同的产品族,且不希望依赖于具体产品类时,可以使用抽象工厂模式。
  • 当系统需要在运行时切换不同的产品族时,可以使用抽象工厂模式。
  • 当需要遵循"开闭原则",即系统需要扩展新的产品族时,不需要修改已有代码,可以使用抽象工厂模式。

代码实现

假设有一个在线商店需要提供两种支付方式:信用卡支付和网银支付,每种支付方式又包含两种具体的实现:Visa 信用卡支付和 MasterCard 信用卡支付,以及支付宝网银支付和微信网银支付。

首先,定义支付方式的接口:

bash 复制代码
// 信用卡支付接口
public interface CreditCardPayment {
    void pay(double amount);
}

// 网银支付接口
public interface OnlineBankingPayment {
    void pay(double amount);
}

接着,定义具体的实现:

bash 复制代码
// Visa 信用卡支付
public class VisaCreditCardPayment implements CreditCardPayment {
    @Override
    public void pay(double amount) {
        System.out.println("Visa credit card payment: $" + amount);
    }
}

// MasterCard 信用卡支付
public class MasterCardCreditCardPayment implements CreditCardPayment {
    @Override
    public void pay(double amount) {
        System.out.println("MasterCard credit card payment: $" + amount);
    }
}

// 支付宝网银支付
public class AlipayOnlineBankingPayment implements OnlineBankingPayment {
    @Override
    public void pay(double amount) {
        System.out.println("Alipay online banking payment: ¥" + amount);
    }
}

// 微信网银支付
public class WeChatOnlineBankingPayment implements OnlineBankingPayment {
    @Override
    public void pay(double amount) {
        System.out.println("WeChat online banking payment: ¥" + amount);
    }
}

然后,定义抽象工厂类

bash 复制代码
// 抽象支付工厂
public abstract class PaymentFactory {
    public abstract CreditCardPayment createCreditCardPayment();
    public abstract OnlineBankingPayment createOnlineBankingPayment();
}

定义具体的支付工厂类:

bash 复制代码
// Visa 信用卡支付工厂
public class VisaPaymentFactory extends PaymentFactory {
    @Override
    public CreditCardPayment createCreditCardPayment() {
        return new VisaCreditCardPayment();
    }

    @Override
    public OnlineBankingPayment createOnlineBankingPayment() {
        return new AlipayOnlineBankingPayment();
    }
}

// MasterCard 信用卡支付工厂
public class MasterCardPaymentFactory extends PaymentFactory {
    @Override
    public CreditCardPayment createCreditCardPayment() {
        return new MasterCardCreditCardPayment();
    }

    @Override
    public OnlineBankingPayment createOnlineBankingPayment() {
        return new WeChatOnlineBankingPayment();
    }
}

最后,使用抽象工厂来创建支付对象

bash 复制代码
public class Client {
    public static void main(String[] args) {
        // 选择 Visa 信用卡支付工厂
        PaymentFactory paymentFactory = new VisaPaymentFactory();

        // 创建 Visa 信用卡支付对象
        CreditCardPayment creditCardPayment = paymentFactory.createCreditCardPayment();
        creditCardPayment.pay(100);

        // 创建支付宝网银支付对象
        OnlineBankingPayment onlineBankingPayment = paymentFactory.createOnlineBankingPayment();
        onlineBankingPayment.pay(200);

        // 选择 MasterCard 信用卡支付工厂
        PaymentFactory paymentFactory2 = new MasterCardPaymentFactory();

        // 创建  MasterCard 信用卡支付对象
        CreditCardPayment creditCardPayment2 = paymentFactory2.createCreditCardPayment();
        creditCardPayment2.pay(100);

        // 创建微信网银支付对象
        OnlineBankingPayment onlineBankingPayment2 = paymentFactory2.createOnlineBankingPayment();
        onlineBankingPayment2.pay(200);        
}

使用小结

Java 抽象工厂模式在很多框架和应用程序中都有广泛的应用。以下是一些具体的应用:

1.Java 数据库连接框架 JDBC 中,使用抽象工厂模式来创建连接对象,例如 Connection、Statement 等。

2.Java 中的 XML 处理器 DOM 和 SAX,也使用抽象工厂模式来创建解析器和生成器对象。

3.Java 中的 Java Cryptography Architecture (JCA) 也使用抽象工厂模式,用于创建加密算法和密钥生成器对象。

总之,Java 抽象工厂模式可以在许多应用程序和框架中找到,它可以帮助您更好地组织和管理代码,提高代码的可扩展性和灵活性。

建造者模式

将复杂对象的创建与表示分离,使得同样的构建过程可以创建不同的表示。

原型模式 Prototype Pattern

通过克隆来创建对象,避免了通过 new 关键字显示调用构造函数的开销。

使用场景

  • 当对象创建的过程比较耗时或者比较复杂,例如需要进行复杂的计算或者涉及到网络请求等操作,可以使用原型模式来避免重复的初始化过程。
  • 当需要创建的对象需要和其他对象进行协同工作时,例如需要创建一个包含多个对象的组合对象,可以使用原型模式来复制一个已有的组合对象,然后进行修改来创建新的组合对象。
  • 当需要动态地增加或者删除一些对象时,可以使用原型模式来复制一个已有的对象,然后进行修改来创建新的对象。
  • 当需要保护对象的复杂状态时,例如当一个对象的创建需要大量的数据初始化时,可以使用原型模式来保护这些数据,避免因为对象的复制而产生意外的副作用。

代码实现

bash 复制代码
// 定义一个原型接口
interface Prototype {
    public Prototype clone();
}

// 具体的原型类
class ConcretePrototype implements Prototype {
    public Prototype clone() {
        return new ConcretePrototype();
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        Prototype prototype = new ConcretePrototype();
        Prototype clone = prototype.clone();
    }
}

使用小结

1.Java中的Object类实现了Cloneable接口,这就意味着Java中的任何对象都可以实现原型模式。通过实现Cloneable接口,并重写Object类中的clone()方法,可以实现原型模式。例如 ArrayList、HashMap 等集合类都实现了Cloneable 接口,可以通过复制现有对象来创建新的对象。

2.Java中的线程池也是使用了原型模式,线程池中的每个线程都是从原型线程中复制而来,而不是每次创建新的线程。

相关推荐
明月看潮生3 分钟前
青少年编程与数学 02-010 C++程序设计基础 12课题、输入输出
开发语言·c++·青少年编程·编程与数学
chen2017sheng18 分钟前
Spring Framework测试工具MockMvc介绍
java·后端·spring
灿灿的金26 分钟前
pip 与当前python环境版本不匹配,python安装库成功,还是提示没有该库
开发语言·python·pip
中东大鹅34 分钟前
Tomcat 目录结构和应用实现
java·tomcat·firefox
鹿说-35 分钟前
Spring基础01
java·spring
Aphelios38039 分钟前
Linux 权限系统和软件安装(二):深入理解 Linux 权限系统
java·linux·运维·服务器·tomcat
小黄人软件40 分钟前
C++ openssl AES/CBC/PKCS7Padding 256位加密 解密示例 MD5示例
开发语言·c++
姜来可期41 分钟前
Go Test 单元测试简明教程
开发语言·后端·学习·golang·单元测试
walkskyer1 小时前
Golang `testing`包使用指南:单元测试、性能测试与并发测试
开发语言·golang·单元测试
JIU_WW1 小时前
Netty内置的空闲检测机制
java·服务器·前端·websocket·netty