架构整洁之道上篇(编程范式设计原则)

news2024/11/26 12:01:17

目录

1.概述

2.编程范式

2.1.结构化编程

2.2.面向对象编程

2.3.函数式编程

3.设计原则

3.1.单一职责原则

3.2.开闭原则

3.3.里氏替换原则

3.4.接口隔离原则

3.5.依赖反转原则

4.小结


1.概述

软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求。

工程师们忽视了一个自然规律:无论是从短期还是长期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢。研发团队最好的选择是清晰地认识并避开工程师们过度自信的特点,开始认真地对待自己的代码架构,对其质量负责。要想跑得快,先要跑得稳。

软件software。“ware”的意思是“产品”,而“soft”的意思,不言而喻,是指软件的灵活性。

艾森豪威尔矩阵中,软件的系统架构——那些重要的事情——占据了该列表的前两位,而系统行为——那些紧急的事情——只占据了第一和第三位。重视软件系统架构。

2.编程范式

编程范式指的是程序的编写模式,与具体的编程语言关系相对较小。三个编程范式分别限制了goto语句、函数指针和赋值语句的使用。

2.1.结构化编程

Dijkstra发现了:goto语句的某些用法会导致某个模块无法被递归拆分成更小的、可证明的单元;Bohm和Jocopini证明人们可以用顺序结构、分支结构、循环结构这三种结构构造出任何程序。结构化编程主张用我们现在熟知的if/then/else语句和do/while/until语句来代替跳转语句的。

本质是:结构化编程对程序控制权的直接转移进行了限制和规范。在架构设计领域,功能性降解拆分仍然是最佳实践之一

2.2.面向对象编程

通常认为,面向对象的三大特点是封装、继承、多态。

封装:把一组相关联的数据和函数圈起来,使圈外面的代码只能看见部分函数,数据则完全不可见。虽然C语言是非面向对象的编程语言,但却是完美封装。

继承:让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆盖。虽然C语言支持继承,但需要显式转换,不够完美。

多态:ML1和接口I在源代码上的依赖关系(或者叫继承关系),该关系的方向和控制流正好是相反的,我们称之为依赖反转。(实线为源代码依赖,虚线为控制流)。换句话说,无论我们面对怎样的源代码级别的依赖关系,都可以将其反转。因此,我们让用户界面和数据库都成为业务逻辑的插件。也就是说,业务逻辑模块的源代码不需要引入用户界面和数据库这两个模块,可以做到独立部署和开发。

因此,作者认为:面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。

本质是:面向对象编程对程序控制权的间接转移进行了限制和规范。

2.3.函数式编程

大部分函数式编程语言只允许在非常严格的限制条件下,才可以更改某个变量的值。函数式编程核心思想是将计算视作一系列函数的组合。它强调函数的纯粹性、不可变性和无副作用。以Java程序为例:本质是:函数式编程对程序中的赋值进行了限制和规范。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
//问题:假设我们有一个整数列表,我们要找到这个列表中的偶数,并将它们乘以2,然后将结果打印出来。
//解释:我们使用了Java 8的流API和Lambda表达式。
//首先,我们通过调用stream()方法将列表转换为流。然后,我们使用filter()方法过滤出偶数。在filter()方法中,我们传递了一个Lambda表达式,该表达式接受一个整数参数n,并检查n是否是偶数。接下来,我们使用map()方法将所有偶数乘以2。最后,我们使用collect()方法将结果收集到一个新的列表中,并使用forEach()方法打印每个元素。
public class FunctionalProgrammingExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> evenNumbersDoubled = numbers.stream()
            .filter(n -> n % 2 == 0) // 过滤偶数
            .map(n -> n * 2) // 将偶数乘以2
            .collect(Collectors.toList()); // 收集结果到一个新的列表

        evenNumbersDoubled.forEach(System.out::println); // 打印结果
    }
}

3.设计原则

软件构建中层结构的主要目标为:可容忍被改动、更容易被理解、可在多个软件系统中复用的组件。

设计原则简述为:SOLID。分别为:SRP(单一职责原则)、OCP(开闭原则)、LSP(里氏替换原则)、ISP(接口隔离原则)和DIP(依赖反转原则)。

3.1.单一职责原则

单一职责原则是指一个类只负责一项职责。另外,我们可以将不同职责的代码分开,并将每个职责放在独立的方法中。

//在这个示例中,我们定义了一个UserService类和一个UserRepository类。UserService类负责处理用户相关的业务逻辑,如获取用户、添加用户、更新用户和删除用户等。UserRepository类负责与数据库交互,包括获取用户、添加用户、更新用户和删除用户等。这样,我们将业务逻辑和数据访问逻辑分开,并将它们分配到不同的类中,从而实现了单一职责原则。
//使用了以下单一职责原则的实现方法:
//1.一个类只负责一项职责:UserService类只负责处理用户相关的业务逻辑,UserRepository类只负责与数据库交互。这样可以使得每个类只负责一项职责,避免了类的职责模糊和不必要的复杂性。
//2.将业务逻辑和数据访问逻辑分开:UserService类和UserRepository类分别处理业务逻辑和数据访问逻辑。这样可以使得每个类的职责更加明确和专一,提高了代码的可读性、可维护性和可扩展性。
//在实现中,我们可以通过将业务逻辑和数据访问逻辑分开来实现单一职责原则;同时,我们还需要考虑类的职责和类的依赖关系,以便更好地组织和管理我们的代码。再者,我们可以将不同职责的代码分开,并将每个职责放在独立的方法中,这样可以使得代码更加简单、易读和易维护。
public class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(int id) {
        return userRepository.getUserById(id);
    }

    public void addUser(User user) {
        userRepository.addUser(user);
    }

    public void updateUser(User user) {
        userRepository.updateUser(user);
    }

    public void deleteUser(int id) {
        userRepository.deleteUser(id);
    }
}

public class UserRepository {
    private Connection connection;

    public UserRepository(Connection connection) {
        this.connection = connection;
    }

    public User getUserById(int id) {
        // 调用数据库API获取用户信息
        return null;
    }

    public void addUser(User user) {
        // 调用数据库API添加用户信息
    }

    public void updateUser(User user) {
        // 调用数据库API更新用户信息
    }

    public void deleteUser(int id) {
        // 调用数据库API删除用户信息
    }
}

3.2.开闭原则

开闭原则是指软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

如何实现呢?可以先将满足不同需求的代码分组(即SRP),然后再来调整这些分组之间的依赖关系(即DIP)(单一职责+依赖反转+单向依赖)。另外,如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖于A组件。

什么是高阶/低阶组件?高阶组件具有更高的复用性,因为它们可以用于多个组件,并且可以在不同的应用中使用。低阶组件则是实现具体功能的组件,它们通常只能在一个特定的应用中使用。

//我们定义了一个Shape接口,它有一个area()方法来计算形状的面积。然后,我们定义了一个Rectangle类和一个Circle类,它们都实现了Shape接口,并分别计算矩形和圆形的面积。最后,我们定义了一个AreaCalculator类,它接受一个Shape数组作为参数,并计算数组中所有形状的总面积。
//使用了以下开闭原则的实现方法:
//1.对扩展开放:我们通过定义Shape接口和Rectangle类和Circle类来实现对扩展开放。如果我们需要添加其他形状,我们只需要实现Shape接口,并创建一个新的类来计算该形状的面积即可。
//2.对修改关闭:我们通过AreaCalculator类来实现对修改关闭。我们不需要修改AreaCalculator类就可以添加新的形状,因为我们已经定义了Shape接口来表示所有形状,并且每个形状都实现了area()方法来计算面积。
//在实现中,我们可以通过定义接口、抽象类和使用基类等方式来实现对扩展开放;同时,我们可以使用策略模式、工厂模式和依赖注入等方式来实现对修改关闭。
public interface Shape {
    double area();
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double area() {
        return Math.PI * radius * radius;
    }
}

public class AreaCalculator {
    public double calculateTotalArea(Shape[] shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.area();
        }
        return totalArea;
    }
}

3.3.里氏替换原则

里氏替换原则是指子类能够替换其父类并且不会影响程序的正确性。

在实现中,我们可以通过继承、实现接口和使用抽象类等方式来实现里式替换原则;同时,我们还需要确保子类满足父类的所有行为和属性,并且不会增加或修改父类的行为。

//我们定义了一个Rectangle类,它有一个width和一个height属性,以及一个area()方法来计算矩形的面积。然后,我们定义了一个Square类,它继承自Rectangle类,并覆盖了setWidth()和setHeight()方法来确保正方形的宽度和高度相等。这样,我们可以使用Square类来代替Rectangle类,并且不会影响程序的正确性。
//使用了以下里式替换原则的实现方法:
//1.子类可以替换父类:Square类继承自Rectangle类,它可以替换Rectangle类的实例,因为它继承了Rectangle类的所有属性和方法,并且具有与Rectangle类相同的行为。
//2.不会影响程序的正确性:Square类覆盖了setWidth()和setHeight()方法来确保正方形的宽度和高度相等,并且保持了与Rectangle类相同的行为,因此在使用Square类替换Rectangle类时,不会影响程序的正确性。
//在实现中,我们可以通过继承、实现接口和使用抽象类等方式来实现里式替换原则;同时,我们还需要确保子类满足父类的所有行为和属性,并且不会增加或修改父类的行为。
public class Rectangle {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}

public class Square extends Rectangle {
    public Square(double sideLength) {
        super(sideLength, sideLength);
    }

    public void setWidth(double width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    public void setHeight(double height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

3.4.接口隔离原则

接口隔离原则要求接口设计应该精简单一,不应该包含多余的方法。 

//我们定义了两个接口Worker和Eater,分别表示工作者和食客。然后,我们定义了一个Programmer类和一个Waiter类,它们都实现了Worker接口,并分别表示程序员和服务员。Waiter类还实现了Eater接口,表示服务员还是一个食客。最后,我们定义了一个Robot类,它也实现了Worker接口,表示机器人也是一个工作者。
//使用了以下接口隔离原则的实现方法:
//1.接口设计精简单一:Worker接口只包含work()方法,Eater接口只包含eat()方法。这样可以使接口设计更加精简,不会包含多余的方法,避免了接口冗余和不必要的复杂性。
//2.类只实现必要的接口:Programmer类只实现Worker接口,Waiter类实现了Worker和Eater接口,Robot类也实现了Worker接口。这样可以使得每个类只实现必要的接口,避免了不必要的依赖和复杂性。
//在实现中,我们可以通过定义精简的接口和让类只实现必要的接口来实现接口隔离原则;同时,我们还需要考虑接口的依赖关系和接口的易用性,以便更好地组织和管理我们的代码。
public interface Worker {
    void work();
}

public interface Eater {
    void eat();
}

public class Programmer implements Worker {
    public void work() {
        System.out.println("Programmer works.");
    }
}

public class Waiter implements Worker, Eater {
    public void work() {
        System.out.println("Waiter works.");
    }

    public void eat() {
        System.out.println("Waiter eats.");
    }
}

public class Robot implements Worker {
    public void work() {
        System.out.println("Robot works.");
    }
}

3.5.依赖反转原则

依赖反转原则是指依赖关系的建立应该多依赖于抽象而不是具体实现,通过构造函数注入依赖。源代码依赖方向永远是控制流方向的反转,这就是DIP被称为依赖反转原则的原因。

//在这个示例中,我们定义了一个Animal接口和两个实现类Dog和Cat,它们分别表示狗和猫。然后,我们定义了一个AnimalFeeder类,它依赖于Animal接口,而不是具体的实现类。AnimalFeeder类包含一个构造函数,它接受一个Animal类型的参数,并将它保存在类内部。AnimalFeeder类还有一个feed()方法,它调用了Animal接口的eat()方法来喂养动物。
//使用了以下依赖反转原则的实现方法:
//1.依赖关系建立于抽象而不是具体实现:AnimalFeeder类依赖于Animal接口,而不是具体的实现类。这样可以使得AnimalFeeder类与具体的实现类解耦,从而提高了代码的灵活性、可扩展性和可维护性。
//2.通过构造函数注入依赖:AnimalFeeder类通过构造函数接受一个Animal类型的参数,并将它保存在类内部。这样可以使得依赖关系的建立显式化,同时也可以方便进行依赖注入。
//在实现中,我们可以通过依赖注入和面向接口编程来实现依赖反转原则;同时,我们还需要考虑接口的设计和类的依赖关系,以便更好地组织和管理我们的代码。
public interface Animal {
    void eat();
}

public class Dog implements Animal {
    public void eat() {
        System.out.println("Dog eats bones.");
    }
}

public class Cat implements Animal {
    public void eat() {
        System.out.println("Cat eats fish.");
    }
}

public class AnimalFeeder {
    private Animal animal;

    public AnimalFeeder(Animal animal) {
        this.animal = animal;
    }

    public void feed() {
        animal.eat();
    }
}

依赖注入是实现依赖反转原则的一种技术手段,它可以将依赖关系的建立从类内部转移到外部。方式有:1.构造函数注入;2.Setter方法注入;3.接口注入。

//1.构造函数注入: 它通过类的构造函数接受依赖对象,并将其保存在类的成员变量中。
public class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ...
}

//2.Setter方法注入:它通过类的Setter方法接受依赖对象,并将其保存在类的成员变量中。使得依赖注入更加灵活,但也容易导致类的职责模糊和Setter方法的滥用。
public class UserService {
    private UserRepository userRepository;

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ...
}

//3.接口注入:它通过一个接口来定义依赖注入的方法,并由类实现该接口。
public interface UserRepositoryAware {
    void setUserRepository(UserRepository userRepository);
}

public class UserService implements UserRepositoryAware {
    private UserRepository userRepository;

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ...
}

4.小结

软件架构的目标是用最小的人力成本来满足构建和维护该系统的需求。要想跑得快,先要跑得稳。

编程范式有三种:结构化编程、面向对象编程和函数式编程。构化编程主张用我们现在熟知的if/then/else语句和do/while/until语句来代替goto等跳转语句。面向对象编程是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让高层策略性组件与底层实现性组件相分离。函数式编程强调函数的纯粹性、不可变性和无副作用。

设计原则为SOLID原则:单一职责原则是指一个类只负责一项职责;开闭原则是指软件实体(类、模块、函数等)应该对扩展开放,对修改关闭;里氏替换原则是指子类能够替换其父类并且不会影响程序的正确性;接口隔离原则要求接口设计应该精简单一,不应该包含多余的方法;依赖反转原则是指依赖关系的建立应该多依赖于抽象而不是具体实现,通过构造函数注入依赖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023 操作系统 R 复习大纲( 适用于太理软件 21 级)

目录 01.操作系统的定义 02.操作系统的基本类型及特征 1.批处理操作系统&#xff08;单、多道&#xff09; 2.分时操作系统 3.实时操作系统 03.操作系统的功能及特征 04.进程的定义、特征 05.进程基本状态及其转换原因 06.进程互斥、同步 07.进程控制块的内容、作用 …

Java数据类型之整数类型与浮点数

标识符&#xff08;名字&#xff09; 作用域 离其最近的大括号 { } &#xff01;&#xff01;&#xff01; 数据类型的分类 赋值时&#xff0c;不可超过数据类型的范围&#xff08;不可越界&#xff09; 常量的进制转换 tips&#xff1a;给变量赋值时&#xff0c;值可以为不同…

从代码层面理解Transformer

跑通 代码使用的是 https://github.com/jadore801120/attention-is-all-you-need-pytorch, commit-id 为: 132907d 各模块粗览 Transformer 主要包括一堆参数, 以及encoder和decoder forward的时候主要做了如下操作. 先 pad_mask过encoder过decoder输出logit 从train.py …

C语言-【指针一】-【什么是指针/指针类型】

对于初学者来说&#xff0c;是不是一提到指针&#xff0c;大家就头疼啊&#xff0c;哈哈哈&#xff0c;当然&#xff0c;它都这么“吓人”了&#xff0c;那么在C语言中扮演的角色也很重要&#xff0c;当然&#xff0c;它也是C语言中的一个特色&#xff0c;如果我们把它拿下的话…

ESP32CAM,点亮一个LED(Arduino平台)

前言 &#xff08;1&#xff09;在此&#xff0c;吐槽一下乐鑫的函数介绍&#xff0c;真的难找。恶心的一批。气死我了。 &#xff08;2&#xff09;接下来我将会介绍我是如何找到ESP32的Arduino平台的函数库的。你将会知道为啥我这么大的戾气。 &#xff08;3&#xff09;同时…

linux系统中输入与输出重定向

什么是输入输出重定向 我们在日常工作中最常用的是输出重定向&#xff0c;输出重定向就是将原本要打印到屏幕中的信息重定向到一个文件中。而输入重定向呢就是指把文件导入到命令中去&#xff0c;听起来是不是有点抽象啊&#xff0c;后面看博主举例说明就很好理由啦。 输出重定…

通讯录信息管理系统

系列文章 任务50 通讯录信息管理系统 文章目录 系列文章一、实践目的与要求1、目的2、要求 二、课题任务三、总体设计1.存储结构及数据类型定义2.程序结构3.所实现的功能函数4、程序流程图 四、小组成员及分工五、 测试插入按编号查找按姓名查找按城市查找更新排序浏览删除统计…

超好玩C++控制台打飞机小游戏,附源码

我终于决定还是把这个放出来。 视频在这&#xff1a;https://v.youku.com/v_show/id_XNDQxMTQwNDA3Mg.html 具体信息主界面上都有写。 按空格暂停&#xff0c;建议暂停后再升级属性。 记录最高分的文件进行了加密。 有boss&#xff08;上面视频2分47秒&#xff09;。 挺好…

轻松转换CAJ文件为PDF格式:免费工具和技巧

在处理中国知网&#xff08;CNKI&#xff09;数据库中的CAJ文件时&#xff0c;将其转换为更常用的PDF格式可以提供更广泛的共享和便捷的阅读体验。本文将介绍一种免费的工具和一些技巧&#xff0c;帮助您轻松地将CAJ文件转换为PDF格式。我们将使用记灵在线工具进行操作。 记灵…

推荐几本提高程序员职业素养的书

如果你是一名程序员&#xff0c;想要提长自己&#xff0c;那么这几本书推荐给你。 1、好代码 &#xff0c;坏代码 为了写出优良的代码&#xff0c;我们必须对手上的方案有合理的判断&#xff0c;并彻底想清楚特定方法的结果&#xff08;好的和坏的&#xff09;。为此&#xff…

F检验.医学统计实例详解

F检验是一种重要的医学统计方法&#xff0c;常用于检验两个或多个样本的方差是否相等&#xff0c;也被称为方差齐性检验。方差齐性检验是医学研究中的基本方法&#xff0c;因为许多重要的统计分析都要求样本方差相等&#xff0c;如方差分析、t检验等。以下将介绍F检验的基本原理…

简单上手Scrapy框架

创建一个Scrapy框架的爬虫程序 安装Scrapy库&#xff0c;直接通过pycharm搜索Scrapy进行安装即可 在终端执行 scrapy startproject 项目名 scrapy startproject Learn 示例 即可创建名为Learn的Scrapy程序&#xff0c;成功创建项目后&#xff0c;会已项目名称创建一个文件夹&…

FTP和SSH连接远程终端Ubuntu

安装好ubuntu后是默认没有ssh服务和ftp服务的&#xff0c;我们需要自己解决一下这个问题 SSH 更新软件列表和软件 sudo apt-get update sudo apt-get upgrade安装ssh sudo apt-get install ssh启动ssh服务 sudo /etc/init.d/ssh start修改ssh服务配置文件 sudo vim /etc/…

前端工程化:发布一个属于自己的规范 npm 包

初始化项目 首先在github创建一个仓库&#xff0c;协议选择MIT&#xff0c;gitignore选择Node&#xff0c;添加README.md描述文件。使用git clone将项目克隆到本地。cd 进入目录&#xff0c;使用vscode打开&#xff08;终端输入code . 命令即可&#xff09;。 然后创建一个合理…

CentOS-7 安装 MariaDB-10.8

一、安装之前删除已存在的 Mysql/MariaDB 1 查找存在的 MariaDB # 注意大小写 rpm -qa | grep MariaDB # rpm -qa 列出所有被安装的rpm package &#xff08;-qa:query all&#xff09; rpm -qa | grep mariadb # grep &#xff08;缩写来自Globally search a Regular Expre…

C++ 折叠参数包:悄然增强编程效率

前言 欢迎来到&#x1f496;小K&#x1f496;的&#x1f49e;C专栏&#x1f49e;&#xff0c;本节将为大家带来折叠参数包的详细讲解&#xff0c;折叠参数包为C模板编程提供了更加灵活和强大的工具&#xff0c;可以提高代码的简洁性和可读性&#xff0c;看完后希望对你有收获 文…

室内外融合定位UWB信标定位方案

大家好&#xff0c;我是北京华星智控公司小智&#xff0c;今天我给大家介绍室内外融合定位系统方案&#xff0c;该方案室外采用北斗卫星定位技术室内采用UWB定位技术从而实现室内外精确定位无缝切换&#xff0c;实现室内外的融合定位。 室内外融合定位系统&#xff0c; 该方案…

Binder相关问题

Binder相关问题 1、Binder是什么&#xff1f;2、Binder有什么优势&#xff1f;3、Binder如何做到一次拷贝的&#xff1f;4、MMAP的原理是什么&#xff1f;5、Binder机制是如何跨进程的&#xff1f;6、为什么Intent不能传递大数据7、AIDL生成Java类细节8、四大组件底层的通信机制…

波奇学Linux:yum和vim

Linux三种安装方式 源代码安装 用户下载->软件源码->源码编译->可执行程序 rpm安装&#xff1a;相当于去官网下载Linux安装包 可能存在依赖项不匹配问题。 yum安装包 用户yum下载->软件安装包->可执行程序 yum是软件包管理器&#xff0c;解决安装源&…

玩客云刷Armbian带docker详细教程(附所有软件)

文章目录 介绍一.准备工作1.硬件准备2.软件准备 二.开始折腾1.烧录系统固件2.刷入系统1.准备镜像2.刷入镜像3.刷入镜像到系统 三.功能介绍1.网页终端2.设备状态3.AriaNg4.qBittorrent5.微力同步6.filebrowser7.Portainer 四.拓展1.添加Alist1.介绍2.安装3.配置3.1查看密码3.2修…