【设计模式】Java 语言不同的编程范式-第1章

简介: 【设计模式】Java 语言不同的编程范式-第1章

目录

前言

1、Java 简介

2、Java 编程范式

2.1、命令式编程

2.2、面向对象编程

2.3、声明式编程

2.4、函数式编程

3、流以及集合的使用

4、设计模式和原则

4.1、单一职责原则

4.2、开闭原则

4.3、里氏替换原则

4.4、接口隔离原则

4.5、依赖倒置原则

4.5.1、案例分享:

4.5.2、分析

4.5.3、依赖传递的三种方式

前言

1)设计模式(design pattern)是前辈的经验积累,是软件开发人员解决软件开发过程中的一般问题的通用方案,能够帮助开发人员提高代码的可重用性,增强系统的可维护性,快速地解决开发过程中常见的诸多难题。

2)本章节主要介绍面向对象编程的基本概念和设计模式的基本原则。

3)适用于每一位有意愿编写高质量代码的 Java 开发人员。
1、Java 简介

1995 年,一个新的编程语言发布了,它从广为人知的 C++ 语言以及鲜为人知的 Smalltalk 语言继承而来。就这样,凭借着 "Write Once,run Anywhere(一次编写,到处运行)"的经典宣言横空问世,你所需要的仅仅是 JVM(Java Virtual Machine,Java 虚拟机)。

Java 会被新兴语言取代吗?_跟着飞哥学编程的博客-CSDN博客_java会被什么语言替代
2、Java 编程范式

什么是编程范式呢?对于不同的编程语言,我们都有一系列的概念、原则和规定。这些概念、原则和规定就被称为编程范式。从理论上来讲,我们希望编程语言只遵从一个编程范式。但是实际上,一个语言往往拥有多个编程范式。

这里我主要介绍 Java 语言的编程范式,包括命令式、面向对象、声明式和函数式编程,以及用来描叙这些编程范式的主要概念。
2.1、命令式编程

命令式编程是这样一种编程范式:用语句更改程序的状态。

“命令”这个名称,顾名思义,指令的执行就是程序的运行。目前大多数流行的编程语言或多或少都基于命令式编程发展而来。最典型的示例就是我们所熟知的 C 语言。

命令式编程示例

为了更好地理解,我们举个通俗易懂的示例:比如你朋友要从西二旗过来找你(你在回龙观),他不知道怎么走,那么你需要给他指令,让他按照你的指令来走,类似百度地图导航。

1)先从西二旗进站

2)乘坐13号线东直门方向的地铁

3)回龙观地铁站出站

4)出站左拐,直行到第二个红绿灯路口

5)结束,找到你了。
2.2、面向对象编程

对象是面向对象编程(OOP)语言的主要元素,它包括状态和行为。

面向对象基于四个基本原则:

封装、抽象、继承、多态。

此处详细内容我就不做过多介绍了,这个属于我们学习 Java 的基础内容。
2.3、声明式编程

声明式编程跟我们前面提到的命令式编程的区别就是,这次我们不用告诉你的朋友如何找到你住的地儿,只需要告诉他你的住址即可,至于他过程如何实现的,我们不关心。

所以声明式编程是这样一种编程范式:它指定程序应该做什么,而不具体说明怎么做。

纯粹的声明式语言包括数据库查询语言(如 SQL 和 XPath)以及正则表达式。与命令式编程语言相比,声明式编程语言更为抽象。

通常,非命令式的编程范式都被认为是声明式类别。比如函数式编程其实就属于声明式编程范式。
2.4、函数式编程

函数式编程是声明式编程的子范式。函数式编程不会改变程序的内部状态。

在函数式编程术语中,函数类似于数学函数,函数的输出仅依赖于其参数,而不管程序的状态如何,完全不受函数是何时执行的影响。大多数函数式语言都是基于 lambda 演算而来,这是由数学家 Alonzo Church 于 20 世纪 30 年代创建的一种形式化数学逻辑系统。
3、流以及集合的使用

1)命令式编程如下示例:创建数字集合,过滤奇数,打印结果。

/**
         * 命令式编程 
         * 1.实例化10个整数的集合,从1到10
         * 2.过滤掉奇数
         * 3.最后把结果打印出来
         */
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        //创建另一个集合,过滤掉奇数
        List<Integer> odds = new ArrayList<Integer>();
        for (Integer integer : list) {
            if (integer % 2 == 0)
            odds.add(integer);
        }
        //打印结果
        for (Integer integer : odds) {
            System.out.println(integer);
        }

2)从 Java 8 开始,我们可以使用流在一行代码中执行相同的操作:

    /**
             * 使用流在一行代码中执行相同的操作
             */
            IntStream.range(0, 10).filter(i -> i % 2 ==0).forEachOrdered(System.out::print);

这里注意流在 java.util.stream 包中定义,用于管理可以对其执行功能式操作的对象流,流是集合的功能对应物。后续我会详细分享关于流式编程的相关内容。
4、设计模式和原则
1)创建软件应用程序是为了满足不断变化和发展的需求。一个成功的应用程序还应该提供一种简单的方法来扩展它以满足不断变化的期望。如果在设计和开发软件时应用一组面向对象的设计原则和模式,则可以避免或解决这些常见问题。

2)面向对象的设计原则也被称为 SOLID 。在设计和开发软件时可以应用这些原则,以便创建易于维护和开发的程序。

SOLID 原则包括,单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。
4.1、单一职责原则

该原则指出软件模块应该只有一个被更改的理由。多数情况下,编写 Java 代码时都会将单一职责原则应用于类。

通俗点儿就是,专业的人做专业的事。每个类只负责做一件事,修改当前类不会对其他类有所影响。

示例:使用数据库持久化保存对象。假设对 Car 类添加方法处理 CRUD 操作。
810107c954f44395a7628f50ae65c4d6.png
这种封装方法将导致 Car 逻辑和数据库持久化操作耦合性太高,将来要修改数据持久操作必须更改 Car 类代码,这可能会在 Car 逻辑中产生错误。

解决方式是创建两个类:一个用于封装 Car 逻辑,另一个用于负债持久化。如下图所示:
0156850c1cb940ca8cbaf943ceb8c1d3.png
4.2、开闭原则

这个原则就是:“模块、类和函数应该对扩展开放,对修改关闭。”

开闭原则在 Java 中最典型的案例就是接口,接口我们都知道,它是对外开放,对内封闭。

开闭原则是最重要的设计原则之一,是大多数设计模式的基础。

所以当我们完成一个模块功能后需要修改或增加新功能时,最好保持原有模块不变,然后在此基础上通过继承和多态扩展来添加新功能。
4.3、里氏替换原则

Barbara Liskov 指出,派生类型必须完全可替代其基类型。里氏替换原则(LSP)与子类型多态密切相关。

里氏替换原则声明,在设计模块和类时,必须确保派生类型从行为的角度来看是可替代的。

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

例如:燕子 和 鸵鸟 都属于鸟类,但是鸵鸟不具备飞的功能。

这里列举一个违反此原则的示例:

我们定义一个汽车 Car 类,并添加两个方法,用钥匙 Key 锁门(lock)和 开锁(unlock)

但是有一种车它是没有车门的,巴吉赛车(BUGGY ,一种没有车门的沙漠赛车,后轮驱动)。

我们创建一个继承自 Car 类的 Buggy 类,这时,我们调用 lock 锁门方法时,发现无法锁定车门,因为没有车门。

所以我们在设计软件要用于车时,无论它们是否是巴吉赛车,都应该考虑到汽车锁门和开锁的一系列问题。我们设计的程序一定要能扩展到其他类型的车上。
4.4、接口隔离原则

客户端不应该依赖于它所不需要的接口。

通俗点儿就是:别人不需要的东西不要强加给人家。

比如:汽车 ICar 接口提供了 修理汽车repair()方法和 销售汽车 sell()方法。修理厂 Mechanic 类实现了 ICar 接口,此时,Mechanic 类需要实现 汽车 ICar 接口中所有方法,但是 修理厂不需要 销售汽车 sell() 方法。

这里我们应该都知道有两种解决方式

1)把修理厂 Mechanic 类定义为 Abstract 抽象类

2)把 ICar 接口拆分为两个接口,一个修理汽车的 IRepairable 接口(此接口只有 repair()方法),一个销售汽车 ISellable 的接口(此接口只有 sell()方法)。这样 Mechanic 类只需实现 IRepairable 接口即可。

这里就是用这个示例说明,修理厂不需要汽车厂接口的销售汽车方法,但是汽车接口提供的方法超出了 Mechanic 类所需要的。

接口隔离原则是为了约束接口,降低类对接口的依赖。
接口隔离原则的优点:
1)灵活性,可维护性增强
2)高内聚,低耦合
3)减少代码冗余(减少了方法的实现)
4)体现对象层次

4.5、依赖倒置原则

1)高级模块不应该依赖于低级模块,两者都应该依赖抽象。

2)抽象不应该依赖于细节,细节应该依赖于抽象。

3)依赖倒置的中心思想是面向接口编程
4)依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

4.5.1、案例分享:

完成 Person 接收消息的功能,接收消息者可以通过微信,短信等等方式接收。引入一个抽象的接口IReceiver, 表示接收者。这样Person类与接口IReceiver发生依赖因为Email, WeiXin 等等属于接收的范围,他们各自实现IReceiver 接口就ok, 这样我们就符号依赖倒转原则。
5598fc1e3d104bb0b7ea672ce0d4dab7.png
示例代码

定义接口 IReceiver:
    public interface IReceiver {
        
        public String getInfo();
        
    }
定义两个不同的实现类 Email 和 WeiXin :
    public class Email implements IReceiver {
        public String getInfo() {
            return "电子邮件信息: hello,world";
        }
    }
    public class WeiXin implements IReceiver {
        public String getInfo() {
            return "微信信息: hello,ok";
        }
    }
定义人 Person 类,这里我们依赖接口 IReceiver,这样我们就可以实现解耦,提高代码的可扩展性,之后我们想用其他方式接收消息,只需要传入具体的实现类即可。
    public class Person {
        /**
         * 入参传入接口
         * @param receiver
         */
        public void receive(IReceiver receiver ) {
            System.out.println(receiver.getInfo());
        }
        
    }
客户端调用我们的接收消息方法。
    public class DependecyInversion {
     
        public static void main(String[] args) {
            //客户端无需改变,只需要定义不同的实现类即可
            Person person = new Person();
            person.receive(new Email());
            person.receive(new WeiXin());
        }
        
    }

4.5.2、分析

高层模块Person没有依赖底层模块Email和WeiXin,而是依赖抽象(IReciver)
细节(Email、Weixin)依赖抽象(IReciver)

4.5.3、依赖传递的三种方式

接口传递
构造器传递
setter 方法传递
相关文章
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
15 2
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
13天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
66 5
|
8天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
90 53
|
17天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
5天前
|
设计模式 算法 搜索推荐
Python编程中的设计模式:优雅解决复杂问题的钥匙####
本文将探讨Python编程中几种核心设计模式的应用实例与优势,不涉及具体代码示例,而是聚焦于每种模式背后的设计理念、适用场景及其如何促进代码的可维护性和扩展性。通过理解这些设计模式,开发者可以更加高效地构建软件系统,实现代码复用,提升项目质量。 ####
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
18 2
|
8天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
21 4