架构设计系列之基础:基础理论(一)

在软件开发和软件架构领域,深厚的理论基础是构建高质量、可维护、可扩展系统的关键,本部分内容将围绕这些基础理论展开。(本部分内容介绍第一部分:编程三范式、架构设计原则、软件设计七原则)

一、编程三范式

编程范式是程序设计的基本思想和方法,对于程序员来说,他是告诉我们在实际编码和设计的过程中不能做什么,而非可以做什么。了解和灵活运用编程范式,有助于构建更加清晰、灵活、可维护的代码。

1 、结构化编程(Structured programming)

是基于过程的一种编程范式,将问题分解为一系列的步骤,每个步骤都由一个过程或函数表示。强调程序的执行过程,是用于简单的任务和流程。

结构化编程是对程序控制权的直接转移进行了限制和规范。

优势
  • 直观且易于理解和实现
  • 适用于线性、简单的任务
适用场景
  • 独立脚本编写
  • 简单的操作和流程

2、面向对象编程(Object-oriented programming)

是一种以对象为基础的编程范式,将数据和操作封装在对象中,强调对象之间的交互和关系。

核心思想是通过抽象、继承、封装和多态等概念,提高代码的重用性和可维护性。

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

优势
  • 模块化,提高代码的可维护性
  • 重用性高,减少重复代码
适用场景
  • 复杂系统设计
  • 大规模团队协作

3、函数式编程(Funtional programming)

是一种以函数为基础的编程范式,将计算视为数据函数的求值过程。强调纯函数、不可变性和无副作用,通过组合函数来构建复杂的系统。

函数式编程是对程序中的赋值进行了限制和规范。

优势
  • 副作用少,易于调试和测试
  • 并行计算友好
适用场景
  • 数据处理和转换
  • 并行和分布式系统

了解并灵活运用这三个范式,有助于根据问题的性质选择最合适的编程方式,提高代码的质量和可维护性。在实际项目中,通常会综合运用多种范式,以充分发挥他们的优势。

4、编程范式与架构设计的关系

编程范式和架构设计是有着密切关系的:

  • 结构化编程是在做各个模块设计和实现时的基础要求
  • 面向对象编程中比如多态是跨越架构边界的手段
  • 函数式编程是用来规范和限制数据存放位置与访问权限的手段

基于上面的分析,你会发现,这三点和架构设计中的功能性、组件独立性、数据管理是密切相关的。

二、架构设计原则

1 、 合适原则

架构设计应该全面考虑时间、人力、能力、效率、成本,选择当前最为合适的方案。

合适的方案并非简单化,而是在充分考虑投资回报率(ROI)的基础上,根据项目需求和实际情况进行权衡和决策。

在实现功能的前提下,追求高效率和降低成本是合适方案的关键。

2 、简单原则

简单原则强调在满足功能需求的前提下,追求简洁和可读性。

简单设计有助于提高研发效率、降低成本、减少错误率,并能够提高系统的可维护性和可扩展性。

核心思想是简单优于复杂。在实现功能的前提下,减少代码量和复杂性,避免过度设计带来的复杂度。

简单的设计更容易理解、测试和部署,同时也易于维护和扩展,减少未来的开发难度和成本。

3 、演化原则

演化原则是指导设计出能够适应未来变化的重要原则,要求技术人员具备前瞻性。

架构设计应该具备灵活性和可扩展性,以便在未来的开发过程中能轻松应对新功能的研发、适应新的需求和变化。

核心思想是演化强于一步到位。任何一个软件产品和系统都需要不断变化和演进,因此在设计过程中要避免过于复杂、难以维护和扩展的结构。

持续的演化有助于保持系统的健康状态,适应业务和技术的不断发展。

三、软件设计七原则

做好一个系统,要先从写出整洁和易维护的代码开始。如果代码质量不佳,那架构能起到的作用就不会太大,多好的架构也绷不住堆砌的烂代码。反之,如果架构设计的不太理想,代码写的再好其实也无太大用处。两者之间是相辅相成,互相成就的。

1 、SRP(Single responsibility Principle)单一职责原则

SRP 是一项简单易懂且重要的原则,但是在实际过程中往往最容易忽略的,它强调在应用中,一个类应该只有一个引起变化的原因,只有一个职责。

SRP 是基于康威定律的推导结论:软件系统的最佳结构高度依赖于开发这个系统的组织的内部结构,每个软件模块都有且只有一个需要被改变的理由。

优点
  • 降低类之间的耦合:将不同职责分解为不同的类,降低类之间的依赖关系,提高系统的灵活性和可维护性
  • 提升类的可维护性和可重用性:当一个类只有一个职责时,修改该职责不会影响到其他职责,使得类更加稳定,易于维护和重用
  • 简化设计过程:SRP 原则使得类的设计更加清晰,每个类都专注于解决一个问题,降低了设计的复杂性
示例代码

多职责的类设计

public class UserService {/*** 运营部员工绩效计算 */public BigDecimal calculateKPIResultForOperation() { }/*** 商务员工绩效计算 */public BigDecimal calculateKPIResultForBusiness() { }/*** 产品部员工绩效计算 */public BigDecimal calculateKPIResultForProduct() { }/*** 技术部员工绩效计算 */public BigDecimal calculateKPIResultForTechnology() { }
}

SRP 后的类设计

public interface UserService {public BigDecimal calculateKPIResult();
}// 不同部门实现接口
public class OperationUserService implement UserService {}
public class BusinessUserService implement UserService {}
public class ProductUserService implement UserService {}
public class TechnologyUserService implement UserService {}
注意点
  • 拆分多职责类:如果一个类承担了多个职责,就一定要拆分为多个类,确保每个类只有一个职责
  • 识别变化原因:识别引起类变化的原因,然后职责分配到对应的类,确保每个类的变化原因是明确的
  • 避免过度设计:尽可能简单,避免过度设计和复杂性,SRP 的目标是简化设计,而不是增加不必要的结构

2 、OCP(Open/Close Principle) 开闭原则

OCP 的核心思想是通过扩展已有的代码,增加新的行为和功能,而不是修改已有的代码。

优点
  • 提高代码的可维护性和可重用性:允许系统通过扩展已有的代码来应对新的需求,而不是修改已有的代码,降低引入错误的风险
  • 减少代码的耦合度:模块化的设计使得系统个部分之间的耦合度降低,一个模块的修改不会对其他模块产生影响
  • 提高系统的可扩展性和可升级性:新功能可以通过添加新的类或模块来实现,而不是修改已有的代码,使得系统更容易扩展和升级
示例代码

无 OCP 的代码

public class Calculator {  public int add(int a, int b) {  return a + b;  }  public int subtract(int a, int b) {  return a - b;  }  public int multiply(int a, int b) {  return a * b;  }  public int divide(int a, int b) {  return a / b;  }  
}

有 OCP 的代码

public interface Operation {  int calculate(int a, int b);  
}  public class AddOperation implements Operation {  public int calculate(int a, int b) {  return a + b;  }  
}  public class SubtractOperation implements Operation {  public int calculate(int a, int b) {  return a - b;  }  
}  public class MultiplyOperation implements Operation {  public int calculate(int a, int b) {  return a * b;  }  
}  public class DivideOperation implements Operation {  public int calculate(int a, int b) {  return a / b;  }  
}
注意点
  • 使用 OCP 原则:把实体设计成可扩展,通过新加类、模块或者函数来扩展功能和行为
  • 抽象化已有代码:对已有代码进行抽象,将具体的实现细节封装,对外仅提供抽象的接口
  • 避免过度设计:OCP 原则的目标是简化设计而不是增加不必要的结构

3 、LSP(Liskov Substitution Principle)里氏替换原则

LSP 强调在软件中,子类必须能够替换其父类,即子类应该具有与父类相同的行为和功能,而不仅仅是继承父类的属性和方法。

优点
  • 提高代码的可读性和可维护性:子类与父类具有一致的行为和功能,使得代码更易于理解和维护
  • 减少代码的冗余和复杂性:子类继承父类的方法和属性,可以避免重复编写相似的代码
  • 提高系统的可扩展性和可升级性:新的子类可以无缝地替换父类,不会影响系统的其他部分
示例代码

无 LSP 代码

class Shape {  void draw() {  System.out.println("Drawing a shape");  }  
}  class Circle extends Shape {  void draw() {  System.out.println("Drawing a circle");  }  
}

有 LSP 代码

interface Drawable {  void draw();  
}  class Shape implements Drawable {  public void draw() {  System.out.println( "Drawing a shape");  }  
}  class Circle extends Shape {  public void draw() {  System.out.println("Drawing a circle");  }  
} 
注意点
  • 子类具有一致的行为和功能:子类必须具有与父类相同的行为和功能,但不能改变父类的行为和功能
  • 抽象类定义抽象方法:抽象类应该定义抽象方法,具体方法在子类中实现
  • 避免继承滥用:避免使用继承来共享行为,继承是用来实现多态行为的,而不是为了代码的重用。如果子类需要不同的功能和行为,那应该通过重写父类方法来实现

4 、ISP(Interface Segregation Principle)接口隔离原则

ISP 强调在应用中使用多个特定的接口,而不是一个单一的总接口,从而避免端侧就不需要被强制依赖他们不需要的接口。

优点
  • 提高代码的可维护性和可重用性,特定接口提供特定服务,代码可以更加模块化和可定制化
  • 减少端侧的复杂性,端侧只需要依赖实际使用的接口,避免对不相关接口的强制依赖
  • 提高系统的可扩展性和可升级性,新的接口可以被添加而不会影响实际使用的接口,使得系统更容易扩展和升级
示例代码

无 ISP 代码

interface ShoppingCart {  void addItem(Product product, int quantity);  void removeItem(Product product);  void updateQuantity(Product product, int quantity);  
}  class ShoppingCartImpl implements ShoppingCart {  private Map<Product, Integer> items = new HashMap<>();  @Override  public void addItem(Product product, int quantity) {  items.put(product, quantity);  }  @Override  public void removeItem(Product product) {  items.remove(product);  }  @Override  public void updateQuantity(Product product, int quantity) {  int currentQuantity = items.get(product);  items.put(product, currentQuantity + quantity);  }  
}

有 ISP 代码

interface AddToCart {  void addItem(Product product, int quantity);  
}  interface RemoveFromCart {  void removeItem(Product product);  
}  interface UpdateQuantity {  void updateQuantity(Product product, int quantity);  
}  class ShoppingCartImpl implements AddToCart, RemoveFromCart, UpdateQuantity {  private Map<Product, Integer> items = new HashMap<>();  @Override  public void addItem(Product product, int quantity) {  items.put(product, quantity);  }  @Override  public void removeItem(Product product) {  items.remove(product);  }  @Override  public void updateQuantity(Product product, int quantity) {  int currentQuantity = items.get(product);  items.put(product, currentQuantity + quantity);  }  
}
注意点
  • 接口定义尽可能小:每个接口提供有限的服务,方法尽可能少,不要妄想一个接口走遍天下
  • 分离不相关功能:如果接口中提供的功能不相关,需要将接口进行分离操作,形成独立接口,代码可更模块化和可定制化
  • 避免使用过大的总接口:总接口应该根据需要提供适当的功能,而不是一刀切提供所有功能

5 、DIP(Dependency Inversion Principle)依赖倒置原则

DIP 强调在应用中,高层模块不应该依赖于底层模块,它们应该依赖于抽象。

优点
  • 提高代码的可读性和可维护性:高层模块依赖于抽象,而不是具体实现,使得代码更灵活和易于理解
  • 降低类之间的耦合度:依赖抽象不依赖具体实现,减少了高层模块和底层模块之间的直接依赖,提高了系统的灵活性
  • 提高系统的可扩展性和可升级性:新的实现可以通过实现抽象来引入,不需要修改高层模块的代码
示例代码

无 DIP 代码

class UserService {  private UserDao userDao;  public UserService(UserDao userDao) {  this.userDao = userDao;  }  public User getUserById(int userId) {  return userDao.getUserById(userId);  }  
}  class UserDao {  public User getUserById(int userId) {  // 具体实现逻辑,如从数据库中获取用户信息  return new User(userId, "John Doe");  }  
}

有 DIP 代码

interface UserDataAccess {  User getUserById(int userId);  
}  class UserDao implements UserDataAccess {  @Override  public User getUserById(int userId) {  // 具体实现逻辑,如从数据库中获取用户信息  return new User(userId, "John Doe");  }  
}  class UserService {  private UserDataAccess userDataAccess;  public UserService(UserDataAccess userDataAccess) {  this.userDataAccess = userDataAccess;  }  public User getUserById(int userId) {  return userDataAccess.getUserById(userId);  }  
}
注意点
  • 通过接口或抽象类定义依赖关系:使用接口或抽象类来定义高层模块和底层模块之间的依赖关系
  • 避免直接依赖具体类:如果直接依赖具体类,一旦有修改,依赖元就要同步改动,影响和成本都较高
  • 使用依赖注入解耦:使用依赖注入来解耦类之间的依赖关系,通过注入抽象的实现来实现高层模块对底层模块的依赖

6 、CARP(Composition/Aggregation Reuse Principle)合成/聚合复用原则

CARP 强调在应用设计过程中优先使用合成/聚合的关系,而不是继承的关系来实现复用。

优点
  • 更好地代码封装:通过使用合成/聚合,可以将对象的不同部分封装在不同的类中,更好地隐藏细节,提高代码的模块化和可维护性
  • 更灵活的代码结构:通过使用合成/聚合,可以更容易地改变对象的行为和结构,只需要修改相关的类,不需要修改整个继承体系
  • 更好的可重用性:通过使用合成/聚合,可以根据需要组合不同的对象来实现代码的可重用性,且合成/聚合本身是一种松耦合,可以更便捷地组装新的对象类型
  • 更好的可扩展性:通过使用合成/聚合,更便捷地添加新的功能和行为到应用中,这种灵活的关系,可以更容易适应新的需求和变化
示例代码

无 CARP 代码

class Car {  private Engine engine;  private Transmission transmission;  private Wheel wheel;  private Door door;  public Car(Engine engine, Transmission transmission, Wheel wheel, Door door) {  this.engine = engine;  this.transmission = transmission;  this.wheel = wheel;  this.door = door;  }  public void start() {  engine.start();  }  public void shift(int gear) {  transmission.shift(gear);  }  public void turn(int degrees) {  wheel.turn(degrees);  }  public void open() {  door.open();  }  
}

有 CARP 代码

interface Engine {  void start();  
}  
interface Transmission {  void shift(int gear);  
}  
interface Wheel {  // 可以添加一些方法,例如 rotate() 和 brake() 等  
}  
interface Door {  void open();  
}  
class Car {  private Engine engine;  private Transmission transmission;  private Wheel wheel;  private Door door;  public Car(Engine engine, Transmission transmission, Wheel wheel, Door door) {  this.engine = engine;  this.transmission = transmission;  this.wheel = wheel;  this.door = door;  }  
}
注意点
  • 封装变化:将变化的部分封装起来,使得变化对其他部分的影响最小
  • 强调组合/聚合:首选使用组合/聚合关系,而不是直接继承关系,以提高灵活性和可维护性
  • 松耦合:合成/聚合是一种松耦合关系,允许系统更容易地适应变化

7 、LoD(Law of Demeter)迪米特法则

LoD 强调在应用中应该尽量减少对象之间的直接依赖关系,降低耦合度,提高可维护性和可重用性。

核心思想是一个对象对其他对象保持最少的了解,并且只和那些和自己最有直接关系的对象进行交互。

一个对象只暴露必要的接口给其他对象,并且应该通过这些接口与其他对象进行交互。

优点
  • 降低耦合度:减少对象之间的直接依赖关系,使得系统更容易扩展和维护
  • 提高可维护性:对象之间的松耦合关系使得修改一个对象的内部实现不会影响其他对象
  • 提高可重用性:松耦合关系允许对象更容易地被独立重用在不同的上下文中
示例代码

无 LoD 代码

class Account {  private User user;  private List<Transaction> transactions;  public Account(User user, List<Transaction> transactions) {  this.user = user;  this.transactions = transactions;  }  public double getBalance() {  double balance = 0.0;  for (Transaction transaction : transactions) {  balance += transaction.getAmount();  }  return balance;  }  public void debit(double amount) {  user.setBalance(user.getBalance() - amount);  }  
}

有 LoD 代码

interface UserService {  double getBalance(User user);  
}  interface TransactionService {  void debit(User user, double amount);  
}  class Account {  private UserService userService;  private TransactionService transactionService;  public Account(UserService userService, TransactionService transactionService) {  this.userService = userService;  this.transactionService = transactionService;  }  public double getBalance() {  return userService.getBalance(userService.getUser());  }  public void debit(double amount) {  transactionService.debit(userService.getUser(), amount);  }  
}
注意点
  • 定义接口:将对象的相关操作定义在接口中,而不是直接依赖于具体的实现
  • 通过接口交互:对象应该通过接口进行交互,而不是直接调用其他对象的方法
  • 减少依赖关系:一个对象应该只与其直接的朋友发生交互,避免依赖过多的对象

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

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

相关文章

112. 路径总和(Java)

目录 解法&#xff1a; 官方解法&#xff1a; 方法一&#xff1a;广度优先搜索 思路及算法 复杂度分析 时间复杂度&#xff1a; 空间复杂度&#xff1a; 方法二&#xff1a;递归 思路及算法 复杂度分析 时间复杂度&#xff1a; 空间复杂度&#xff1a; 给你二叉树的…

(C++)最大连续1的个数--滑动窗口

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://le…

MIT18.06线性代数 笔记2

文章目录 正交向量与子空间子空间投影投影矩阵和最小二乘正交矩阵和Gram-Schmidt正交化行列式及其性质行列式公式和代数余子式克拉默法则、逆矩阵、体积特征值和特征向量对角化和A的幂微分方程和exp(At)马尔科夫矩阵 傅里叶级数复习二 正交向量与子空间 向量正交&#xff1a;x…

【初阶C++】前言

C前言 1. 什么是C2. C发展史3. C的重要性4. 如何学习C 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; …

【Linux系统编程二十一】:(进程通信3)--消息队列/信号量(system v标准的内核数据结构的设计模式)

【Linux系统编程二十】&#xff1a;消息队列/信号量(system v标准的内核数据结构的设计模式&#xff09; 一.消息队列二.system v标准的内核数据结构的设计三.四个概念(互斥/临界)四.信号量1.多线程并发访问2.计数器3.原子的4.总结 一.消息队列 一个叫做a进程啊&#xff0c;一个…

如何将LLMs封装成应用并在本地运行

最近我一直在致力于Ollama的工作&#xff0c;因此我花了很多时间思考如何在本地系统上运行大型语言模型&#xff08;LLMs&#xff09;以及如何将它们打包成应用程序。对于使用LLMs的大多数桌面应用程序而言&#xff0c;通常的体验要么是插入OpenAI API密钥&#xff0c;要么是从…

protobuf基础学习

部分内容出自&#xff1a;https://blog.csdn.net/baidu_32237719/article/details/99723353 proto文件来预先定义的消息格式。数据包是按照proto文件所定义的消息格式完成二进制码流的编码和解码。proto文件&#xff0c;简单地说&#xff0c;就是一个消息的协议文件&#xff0c…

MAC IDEA Maven Springboot

在mac中&#xff0c;使用idea进行maven项目构建 环境配置如何运行maven项目1.直接在IDEA中运行2.使用jar打包后执行 如何搭建spring boot1.添加依赖2.创建入口类3.创建控制器4. 运行5.其他 环境配置 官网安装IDEA使用IDEA的创建新项目选择创建MAEVEN项目测试IDEA的MAVEN路径是…

【二分查找】【双指针】LeetCode:2565最少得分子序列

作者推荐 【动态规划】【广度优先】LeetCode2258:逃离火灾 本文涉及的基础知识点 二分查找算法合集 有序向量的二分查找&#xff0c;初始化完成后&#xff0c;向量不会修改。 双指针&#xff1a; 用于计算子字符串是s的字符串的子系列。 题目 给你两个字符串 s 和 t 。 你…

《地理信息系统原理》笔记/期末复习资料(10. 空间数据挖掘与空间决策支持系统)

目录 10. 空间数据挖掘与空间决策支持系统 10.1. 空间数据挖掘 10.1.1. 空间数据挖掘的概念 10.1.2. 空间数据挖掘的方法与过程 10.1.3. 空间数据挖掘的应用 10.2. 空间决策支持系统 10.2.1. 空间决策支持系统的概念 10.2.2. 空间决策支持系统的结构 10.2.3. 空间决策…

基于chaos混沌的彩色图像加解密系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 混沌理论简介 4.2 基于混沌的图像加密原理 4.3 数学公式与实现过程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .................…

记录将C语言编写的Windows程序转换为python语言编写,演示具体效果在最后,这对初学者理解Windows消息机制及框架有一定参考作用

主要思路 由于C语言的数组定义使用名字加中括号及括号内数字定义&#xff0c;但是在python中【】已经被作为列表 元组等序列类型分片、存取元素&#xff0c;因此我们不能像C语言那样定义数组 例如C语言 int a[10] 是声明定义一个含有10个int类型的数组a,而在执行语句部分…

华为OD机试真题B卷 Java 实现【统计大写字母个数】,附详细解题思路

一、题目描述 找出给定字符串中大写字符(即’A’-‘Z’)的个数。 数据范围&#xff1a;字符串长度&#xff1a;1≤∣s∣≤250 字符串中可能包含空格或其他字符 二、输入描述 对于每组样例&#xff0c;输入一行&#xff0c;代表待统计的字符串。 三、输出描述 输出一个整…

【css】css实现文字两端对齐效果:

文章目录 一、方法1&#xff1a;二、方法2&#xff1a;三、注意&#xff1a; 一、方法1&#xff1a; 给元素设置 text-align: justify;text-align-last: justify;并且加上text-justify: distribute-all-line; 目的是兼容ie浏览器 p{width: 130px;text-align: justify;text-alig…

飞天使-linux操作的一些技巧与知识点3-http的工作原理

文章目录 http工作原理nginx的正向代理和反向代理的区别一个小技巧dig 命令巧用 http工作原理 http1.0 协议 使用的是短连接&#xff0c;建立一次tcp连接&#xff0c;发起一次http的请求&#xff0c;结束&#xff0c;tcp断开 http1.1 协议使用的是长连接&#xff0c;建立一次tc…

Network 灰鸽宝典【目录】

目前已有文章 21 篇 Network 灰鸽宝典专栏主要关注服务器的配置&#xff0c;前后端开发环境的配置&#xff0c;编辑器的配置&#xff0c;网络服务的配置&#xff0c;网络命令的应用与配置&#xff0c;windows常见问题的解决等。 文章目录 服务配置环境部署GitNPM 浏览器编辑器系…

GEE:重分类

作者:CSDN @ _养乐多_ 本文记录了在 Google Earth Engine(GEE)平台上对一副类别图像进行重分类的代码。并以 COPERNICUS/Landcover/100m/Proba-V-C3/Global 数据集中的土地利用数据为例。 结果如下图所示, 文章目录 一、核心函数二、示例代码三、代码链接一、核心函数 核…

文件操作及函数

什么是文件&#xff1f; 在程序设计中&#xff0c;文件有两种&#xff1a;程序文件和数据文件。 程序文件 包括源程序文件&#xff08;.c&#xff09;&#xff0c;目标文件&#xff08;.obj&#xff09;&#xff0c;可执行程序(.exe)。 数据文件 文件的内容不一定是程序&…

linux下部署frp客户端服务端-内网穿透

简介 部署在公司内部局域网虚拟机上的服务需要在外网能够访问到&#xff0c;这不就是内网穿透的需求吗&#xff0c;之前通过路由器实现过&#xff0c;现在公司这块路由器不具备这个功能了&#xff0c;目前市面上一些主流的内网穿透工具有&#xff1a;Ngrok&#xff0c;Natapp&…

Docker镜像构建:深入Dockerfile创建自定义镜像

Docker的强大之处在于其能够通过Dockerfile定义和构建自定义镜像&#xff0c;为应用提供独立、可移植的运行环境。在这篇博客文章中&#xff0c;将深入探讨Docker镜像构建的核心概念&#xff0c;通过更加丰富的示例代码&#xff0c;帮助大家全面理解和掌握构建自定义镜像的技术…