敏捷开发笔记(第9章节)--开放-封闭原则(OCP)
目录
1:PDF上传链接
9.1 开放-封闭原则(OCP)
9.2 描述
9.3 关键是抽象
9.3.1 shape应用程序
9.3.2 违反OCP
糟糕的设计
9.3.3 遵循OCP
9.3.4 是的,我说谎了
9.3.5 预测变化和“贴切的”结构
9.3.6 放置吊钩
1.只受一次愚弄
2.刺激变化
9.3.7 使用抽象获得显示封闭
9.3.8 使用“数据驱动”的方法获取封闭性
9.4 结论
1:PDF上传链接
【免费】敏捷软件开发(原则模式与实践)资源-CSDN文库
Ivar Jacobson曾说过,“任何系统在其生命周期中都会发生变化。如果我们期望开发出的系统不会再第一版后就被抛弃你就必须牢牢记住一点”。Bertrand Meyer在1988年提出著名的开发-封闭原则(The Open - closed Principle,简称OCP)为我们提供了指引。
9.1 开放-封闭原则(OCP)
软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。
如果程序中的一处改动就会产生连锁反应,导致一系列相关模块的改动,那么设计就具有僵化性的臭味。OCP建议我们应该对系统进行重构,这样以后对系统在进行那样的改动时,就不会导致更多的修改。如果正确地应用OCP,那么以后再进行同样的改动,就只需要添加新的代码,而不必改动已经正常运行的代码。
也许,这看起来像是重所周知的可望而不可及的美好理想-----然后,事实上却有一些相对简单并且有效的策略可以帮助接近这个理想。
9.2 描述
遵循开放-封闭原则设计出的模块具有两个主要的特征。它们是:
1:对于扩展是开放的(Open for extension)
这意味着模块的行为是可以扩展的,当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为,换句话说,我们可以改变模块的功能。
2:对更改是封闭的(closed for modification)
对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、dll或者Java的jar文件都无需改动。
这个两个特征好像是相互矛盾的,扩展模块行为的通常方式就是修改该模块的源代码。不允许修改的模块常常都被认为具有固定的行为。
怎么可能在不改动模块源代码的情况下,去更改它的行为呢?怎么才能在无需对模块进行改动情况下就改变它的功能呢?
9.3 关键是抽象
在C++、Java或者其他任何的OOPL(面向对象编程语言)中,可以创建出固定去能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象类。而这一组任意个可能得行为则表现为可能得派生类。
模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,所以他对于更改可以是关闭的,同时,通过从这个抽象体派生,也可以扩展此模块的行为。
9.3.1 shape应用程序
下面的例子在许多讲述OOD(面向对象的设计)的书中都提过。他就是声名狼藉的“shape”样列。它常常被用来展示多态的工作原理。不过,这次我们将使用它来阐明OCP。
我们有一个需要再标准的GUI上面绘制圆和正方形的应用程序。圆和正方形必须要按照特定的顺序绘制。我们将创建一个列表,列表由按照适当的顺序排列的圆和正方形组成,程序遍历该列表,依次绘制出每个圆和正方形。
9.3.2 违反OCP
如果使用C语言,并采用不遵循OCP的过程化方法,我们也许会得到程序9.1中所示的解决方法。其中,我们看到了一组的数据结构,它们的第一个成员都相同,但是其余的成员都不同。每个结构中的第一个成员都是一个用来标识该结构是代表圆或者正方形的类型码。DrawAllShapes函数遍历一个数组,该数组的元素是指向这些数据结构的指针,DrawAllShapes函数先检查类型码,然后根据类型码调用对应的函数(DrawCircle或者DrawSquare)。
程序9.1 Square/Circle问题的过程化解决方案
--shape.h --
enum ShapeType {circle,square
};
struct Shape {ShapeType itsType;
}
-circle.h ---
struct Circle {ShapeType itsType;double itsRadius;Point itsCenter;
}
-square.h ----
struct Square {ShapeType itsType;double itsside;Point itsTopLeft;
}:
--drawA11 Shapes.cc----------------
typedef struct Shape *Shapepointer;void DrawAllShapes(ShapePointer list[], int n)
{int i;for (i = 0; i < n; i++){struct Shape *s = list[i];switch (s->itsType) {case square:DrawSquare((struct Square*)s);break;case circle:Drawcircle((struct circle*)s);Break;default:break;}}
}
DrawAllShapes函数不符合OCP,因为它对于新的形状类型的添加不是封闭的。如果希望这个函数能够绘制包含有三角形的列表,就必须得更改这个函数。事实上,每增加一种新的形状类型,都必须要更改这个函数。
当然这只是一个简单的例子。在实际程序中,类似DrawAllShapes函数中的switch语句会在应用程序的各个函数中重复不断地出现,每个函数中switch语句负责完成的工作差别甚微。这些函数中,可能有负责拖曳形状对象的,有负责拉伸形状对象的,有负责移动形状对象的,有负责删除形状对象的,等等。在这样的应用程序中增加一种新的形状类型,就意味着要找出所有包含上述switch语句(或者链式if/else语句)的函数,并在每一处都添加对新增的形状类型的判断。
更糟的是,并不是所有的switch语句和if/else链都像DrawAllShapes中的那样有比较好的结构。更有可能的情形是,if语句中的判断条件由逻辑操作符组合而成,或者是处理方式相同的case语句被成组处理。在一些极端错误的实现中,会有一些函数对于Square的处理竞然和对于circle的处理一样。在这样的函数中,甚至根本就没有switch/case语句或者if/else链。这样,要发现和理解所有的需要增加对新的形状类型进行判断的地方,恐怕就非常的困难了。
同样,在进行上述改动时,我们必须要在ShapeType enum中添加·个新的成员。由于所有不同种类的形状都依赖于这个eum的声明,所以我们必须要重新编译所有的形状模块。并且也必须要重新编译所有依赖于Shape类的模块。
糟糕的设计
再来回顾一下。程序9.1中的解决方法是僵化的,这是因为增加Triangle会导致Shape、Square、Circle以及DrawAllShapes的重新编译和重新部署。该方法是脆弱的,因为有许多其他的即难以查找又难以理解的switch/case或者lse语句。该方法是牢固的,因为想在另一个程序中复用DrawAllShapes时,都必须要附带上Square和Circle,即使那个新程序不需要它们。因此,在程序9.1中展示了许多糟糕设计的臭味。
9.3.3 遵循OCP
程序9.2中展示了一个square/circle问题的符合OCP的解决方案。在这个方案中,我们编写了一个名为Shape的抽象类。这个抽象类仅有一个名为Draw的抽象方法。Circle和Square都从Shape类派生。
程序9.2问题的OoD解决方案class Shape {public:virtual void Draw () const = 0;
};class Square: public Shape {public:virtual void Draw() const = 0;
};class circle: public Shape{public:virtual void Draw () const = 0;
};void DrawAllShapes(vector<Shape*> & list)
{vector<Shape*>::iterator I;for (i = list.begin(); i != list.end(); i++) {(*i)->Draw ();}
}
可以看到,如果我们想要扩展程序9.2中DrawAllShapes函数的行为,使之能够绘制一种新的形状,我们只需要增加一个新的Shape类的派生类.DrawAllShapes函数并不需要改变,这样DrawAllShapes就符合了OCP。无需改动自身代码,就可以扩展它的行为。实际上,增加一个Triangle类对于这里展示的任何模块完全没有影响。很明显,为了能够处理Triangle类,必须要改动系统中的某些部分,但是这里展示的所有代码都无需改动。
在实际的应用程序中,Sape类可能会有更多的方法。但是在应用程序中增加一种新的形状类型依然非常简单,因为所需要做的工作只是创建Sape类的新的派生类,并实现它的所有函数。再也不需要为了找出需要更改的地方而在应用程序的所有地方进行搜寻。这个解决方案不再是脆弱的。
同时,这个方案也不再是僵化的。在增加一个新的形状类型时,现有的所有模块的源码都无需改动,并且现有的所有二进制模块都无需进行重新构建(rebuild)。只有一个例外,那就是实际创建Shape类新的派生类实例的模块必须被改动。通常情况下,创建Shape类新的派生类实例的工作要么是在main中或者被main调用的一些函数中完成,要么是在被main创建的一些对象的方法中完成。
最后,这个方案也不再是牢固的。现在,在任何应用程序中重用DrawAllShapes时,都无需再附带上Square和Circle。因而,这个解决方案就不再具有前面提及的任何糟糕设计的特征。
这个程序是符合OCP的。对它的改动是通过增加新代码进行的,而不是更改现有的代码。因此,它就不会引起像不遵循OCP的程序那样的连锁改动。所需要的改动仅仅是增加新的模块,以及为了能够实例化新类型的对象而进行的围绕main的改动。
9.3.4 是的,我说谎了
上面的例子其实并非是100%封闭的!如果我们要求所有的圆必须在正方形之前绘制,那么程序9.2中的DrawAllShapes函数会怎样呢?DrawAllShapes函数无法对这种变化做到封闭。要实现这个需求,我们必须要修改DrawAllShapes的实现,使它首先扫描列表中所有的圆,然后再扫描所有的正方形。
9.3.5 预测变化和“贴切的”结构
如果我们预测到了这种变化,那么就可以设计一个抽象来隔离它。我们在程序92中所选定的抽象对于这种变化来说反倒成为一种障碍。可能你会觉得奇怪:还有什么比定义一个Shape类,并从它派生出Square类和Cice类更贴切的结构呢?为何这个贴切的模型不是最优的呢?很明显,这个模型对于一个形状的顺序比形状类型具有更重要意义的系统来说,就不再是贴切的了。
这就导致了一个麻烦的结果,一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。
既然不可能完全封闭,那么就必须有策略地对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。
这需要设计人员具备一些从经验中获得的预测能力。有经验的设计人员希望自己对用户和应用领域很了解,能够以此来判断各种变化的可能性。然后,他可以让设计对于最有可能发生的变化遵循OCP原则。
这一点不容易做到。因为它意味着要根据经验猜测那些应用程序在生长历程中有可能遭受的变化。如果开发人员猜测正确,他们就获得成功。如果他们猜测错误,他们会遭受失败。并且在大多数情况下,他们都会猜测错误。
同时,遵循OCP的代价也是昂贵的。创建正确的抽象是要花费开发时间和精力的。同时,那些抽象也增加了软件设计的复杂性。开发人员有能力处理的抽象的数量也是有限的。显然,我们希望把OCP的应用限定在可能会发生的变化上。
我们如何知道哪个变化有可能发生呢?我们进行适当的调查,提出正确的问题,并且使用我们的经验和一般常识。最终,我们会一直等到变化发生时才采取行动。
9.3.6 放置吊钩
我们怎样去隔离变化呢?在上个世纪,我们常常说的一句话是,我们会在我们认为可能发生变化的地方放置吊钩(hook)。我们觉得这样做会使软件灵活一些。
然而,我们放置的吊钩常常是错误的。更糟的是,即使不使用这些吊钩,也必须要去支持和维护它们,从而就具有了不必要的复杂性的臭味。这不是一件好事。我们不希望设计背着许多不必要的抽象。通常,我们更愿意一直等到确实需要那些抽象时再把它放置进去。
1.只受一次愚弄
有句古老的谚语说:“愚弄我一次,应感羞愧的是你。再次愚弄我,应感羞愧的是我。”这也是一种有效的对待软件设计的态度。为了防止软件背着不必要的复杂性,我们会允许自己被愚弄一次。这意味着在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。简而言之,我们愿意被第一颗子弹击中,然后我们会确保自己不再被同一只枪发射的其他任何子弹击中。
2.刺激变化
如果我们决定接受第一颗子弹,那么子弹到来的越早、越快就对我们越有利。我们希望在开发工作展开不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。
因此,我们需要去刺激变化。我们已在第2章中讲述的一些方法来完成这项工作。
(1)我们首先编写测试。测试描绘了系统的一种使用方法。通过首先编写测试,我们迫使系统成为可测试的。在一个具有可测试性的系统中发生变化时,我们可以坦然对之。因为我们已经构建了使系统可测试的抽象。并且通常这些抽象中的许多都会隔离以后发生的其他种类的变化。
(2)我们使用很短的迭代周期进行开发个周期为几天而不是几周。
(3)我们在加入基础结构前就开发特性,并且经常性地把那些特性展示给涉众。
(4)我们首先开发最重要的特性。
(5)尽早地、经常性地发布软件。尽可能快地、尽可能频繁地把软件展示给客户和使用人员。
9.3.7 使用抽象获得显示封闭
第一颗子弹已经击中我们,用户要求我们在绘制正方形之前先绘制所有的圆。现在我们希望可以隔离以后所有的同类变化。
怎样才能使得DrawAllShapes函数对于绘制顺序的变化是封闭的呢?请记住封闭是建立在抽象的基础之上的。因此,为了让DrawAllShapes对于绘制顺序的变化是封闭的,我们需要一种“顺序抽象体”。这个抽象体定义了一个抽象接口,通过这个抽象接口可以表示任何可能的排序策略。
一个排序策略意味着,给定两个对象可以推导出应该先绘制哪一个。我们可以定义一个Shpe类的抽象方法叫作Precedes.。这个方法以另外一个Shape作为参数,并返回-一个bool型结果。如果接收消息的Shape对象应该先于作为参数传入的Shape对象绘制,那么函数返回true。
在C++中,这个函数可以通过重载operator<来表示。程序9.3中展示了添加了排序方法后的Shape类。
既然我们已经有了决定两个Shape对象的绘制顺序的方法,我们就可以对列表中的shape对象进行排序后依序绘制。程序9.4展示了C++的实现代码。
图9.3.7.1
这给我们提供了一种对Shape对象排序的方法,也使得可以按照一定的顺序来绘制它们。但是我们仍然没有一个好的用来排序的抽象体。按照目前的设计,Shape对象应该覆写Precedes方法来指定顺序。这究竟是如何工作的呢?我们应该在Circle:Precedes成员函数中编写一些什么代码,来保证圆一定会被先于正方形绘制呢?请看程序9.5。
图9.3.7.2
显然这个函数以及所有Shape类的派生类中的Precedes函数都不符合OCP。没有办法使得这些函数对于Shape类的新派生类做到封闭。每次创建一个新的Shape类的派生类时,所有的Precedest)函数都需要改动。
当然,如果从来不需创建新的Shape类的派生类,就没有关系了。另一方面,如果需要频繁的创建新的Sape类的派生类,这个设计就会遭到沉重的打击。我们再次被第一颗子弹击中。
9.3.8 使用“数据驱动”的方法获取封闭性
如果我们要使Shape类的各个派生类间互不知晓,可以使用表格驱动的方法。程序9.6展示了一·种可能的实现。
通过这种方法,我们成功地做到了一般情况下DrawAllShapes函数对于顺序问题的封闭,也使得每个Shape派生类对于新的Shape派生类的创建或者基于类型的Shape对象排序规则的改变是封闭的。(比如,改变顺序为正方形必须最先绘制。)
对于不同的Shapes的绘制顺序的变化不封闭的惟-一部分就是表本身。可以把表放置在一个单独的模块中,和所有其他模块隔离,因此对于表的改动不会影响到其他任何模块。事实上,在C++中,我们可以在链接时选择要使用的表。
9.4 结论
在许多方面,OCP都是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处(也就是,灵活性、可重用性以及可维护性)。然而,并不是说只要使用一种面向对象语言就是遵循了这个原则。对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。
相关文章:

敏捷开发笔记(第9章节)--开放-封闭原则(OCP)
目录 1:PDF上传链接 9.1 开放-封闭原则(OCP) 9.2 描述 9.3 关键是抽象 9.3.1 shape应用程序 9.3.2 违反OCP 糟糕的设计 9.3.3 遵循OCP 9.3.4 是的,我说谎了 9.3.5 预测变化和“贴切的”结构 9.3.6 放置吊钩 1.只受一次…...

苹果电脑清理app垃圾高效清理,无需专业知识
在我们的日常使用中,苹果电脑以其优雅的设计和强大的功能赢得了广泛的喜爱。然而,即便是最高效的设备,也无法免俗地积累各种不必要的文件和垃圾,特别是app垃圾。所以,苹果电脑清理app垃圾高效清理,对于大多…...

【算法】(C语言):快速排序(递归)、归并排序(递归)、希尔排序
快速排序(递归) 左指针指向第一个数据,右指针指向最后一个数据。取第一个数据作为中间值。右指针指向的数据 循环与中间值比对,若大于中间值,右指针往左移动一位,若小于中间值,右指针停住。右…...

模型驱动开发(Model-Driven Development,MDD):提高软件开发效率与一致性的利器
目录 前言1. 模型驱动开发的原理1.1 什么是模型驱动开发1.2 MDD的核心思想 2. 模型驱动开发的优势2.1 提高开发效率2.2 确保代码一致性2.3 促进沟通和协作2.4 方便维护和扩展 3. 实现模型驱动开发的方法3.1 选择合适的建模工具3.1.1 UML3.1.2 BPMN3.1.3 SysML 3.2 建模方法3.2.…...

记录discuz修改用户的主题出售价格
大家好,我是网创有方的站长,今天遇到了需要修改discuz的主题出售价格。特此记录下 方法很简单: 进入用于组-》选择论坛-》批量修改...

WGAN(Wassertein GAN)
WGAN E x ∼ P g [ log ( 1 − D ( x ) ) ] E x ∼ P g [ − log D ( x ) ] \begin{aligned} & \mathbb{E}_{x \sim P_g}[\log (1-D(x))] \\ & \mathbb{E}_{x \sim P_g}[-\log D(x)] \end{aligned} Ex∼Pg[log(1−D(x))]Ex∼Pg[−logD(x)] 原始 GAN …...

Maven基本使用
1. Maven前瞻 Maven官网:https://maven.apache.org/ Maven镜像:https://mvnrepository.com 1.1、Maven是什么 Maven是一个功能强大的项目管理和构建工具,可以帮助开发人员简化Java项目的构建过程。 在Maven中,使用一个名为 pom.…...

在Linux系统中配置GitHub的SSH公钥
在Linux系统中配置GitHub的SSH公钥,可以让您无需频繁输入密码即可与GitHub仓库进行交互,提高工作效率。以下是配置步骤: 第一步: 检查SSH密钥是否存在 首先,检查您的用户目录下的.ssh文件夹中是否已有SSH密钥。打开终端࿰…...

小酌消烦暑|人间正清欢
小暑是二十四节气之第十一个节气。暑,是炎热的意思,小暑为小热,还不十分热。小暑虽不是一年中最炎热的时节,但紧接着就是一年中最热的节气大暑,民间有"小暑大暑,上蒸下煮"之说。中国多地自小暑起…...

C语言结构体的相关知识
前言 从0开始记录我的学习历程,我会尽我所能,写出最最大白话的文章,希望能够帮到你,谢谢。 1.结构体类型的概念及定义 1.1、概念: 结构体是一种构造类型的数据结构, 是一种或多种基本类型或构造类型的数…...

RabbitMQ入门教程(精细版二带图)
目录 六 RabbitMQ工作模式 6.1Hello World简单模式 6.1.1 什么是简单模式 6.1.2 RabbitMQ管理界面操作 6.1.3 生产者代码 6.1.4 消费者代码 6.2 Work queues工作队列模式 6.2.1 什么是工作队列模式 6.2.2 RabbitMQ管理界面操作 6.2.3 生产者代码 6.2.4 消费者代码 …...

IO、零拷贝、多路复用、connection、池化
目录 一、IO 模型 二、什么是网络IO 三、什么是零拷贝 四、多路复用 五、java程序、mysql JDBC connection关系 六、connection怎么操作事务 七 、java里面的池化技术 八、线程池7个核心参数 九、线程的状态 一、IO 模型 BIO :同步阻塞io,单线程 内存上下…...

Lua 错误处理
Lua 错误处理 Lua是一种轻量级的编程语言,广泛用于游戏开发、脚本编写和其他应用程序中。在编程过程中,错误处理是一个重要的方面,它可以帮助开发者创建更健壮和可靠的程序。本文将详细介绍Lua中的错误处理机制。 错误类型 在Lua中&#x…...

二刷力扣——单调栈
739. 每日温度 单调栈应该从栈底到栈顶 是递减的。 找下一个更大的 ,用递减单调栈,就可以确定在栈里面的每个比当前元素i小的元素,下一个更大的就是这个i,然后弹出并记录;然后当前元素i入栈,仍然满足递减…...

elementPlus-vue3-ts表格单选和双选实现方式
记录在vue3、ts、element-plus环境下表格单选和多选的实现方式 单选 html部分 <el-table...reftaskTableRefselect"selectClick"... ><el-table-column type"selection" width"50" />... </el-table>ts部分 const taskTabl…...

Linux系统中卸载GitLab
在Linux系统中卸载GitLab,主要可以通过包管理器(如apt、yum、rpm等)来实现,但具体步骤可能会因GitLab的安装方式(如使用包管理器安装、从源代码安装、使用Docker等)和Linux发行版的不同而有所差异。以下是一…...

基于STM32F407ZG的FreeRTOS移植
1.从FreeRTOS官网中下载源码 2、简单分析FreeRTOS源码目录结构 2.1、简单分析FreeRTOS源码根目录 (1)Demo:是官方为一些单片机移植FreeRTOS的例程 (2)License:许可信息 (3)Sourc…...

【IT领域新生必看】Java编程中的神奇对比:深入理解`equals`与`==`的区别
文章目录 引言什么是操作符?基本数据类型的比较示例: 引用类型的比较示例: 什么是equals方法?equals方法的默认实现示例: 重写equals方法示例: equals与的区别比较内容不同示例: 使用场景不同示…...

WEBHTTP
目录 理解HTTP协议请求流程 1 1 Web基础 2 Hosts文件 1 1 2网页与HTML 2 HTML概述 1 1 3静态网页与动态网页 1.2HTTP协议 1 2 1 HTTP协议概述 1 2 2 HTTP方法 HTTP支持几种不同的请求命令,这些命令被称为HTTP方法(HTTP method 表1一3 HTTP方法 表1&#…...

nodejs 获取客服端ip,以及获取ip一直都是127.0.0.1的问题
一、问题描述 在做登录日志的时候想要获取客户端的ip, 网上查了一下 通过 req.headers[x-forwarded-for] || req.connection.remoteAddress; 获取, 结果获取了之后不管是开发环境,还是生产环境获取到的一直都是 127.0.0.1,这是因为在配置N…...

微软与OpenAI/谷歌与三星的AI交易受欧盟重点关注
近日,欧盟委员会主管竞争事务的副主席玛格丽特维斯塔格(Margrethe Vestager)在一次演讲中透露,欧盟反垄断监管机构将就微软与OpenAI的合作,以及谷歌与三星达成的AI协议寻求更多第三方意见。这意味着微软与 OpenAI、谷歌与三星的 AI 交易及合作…...

微信小程序毕业设计-学生实习与就业管理系统项目开发实战(附源码+论文)
大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:微信小程序毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计…...

spring boot 接口参数解密和返回值加密
spring boot 接口参数解密和返回值加密 开发背景简介安装配置yml 方式Bean 方式 试一下启动项目返回值加密参数解密body 参数解密param和form-data参数解密 总结 开发背景 虽然使用 HTTPS 已经可以基本保证传输数据的安全性,但是很多国企、医疗、股票项目等仍然要求…...

C语言自定义类型——联合体、枚举
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、联合体(一)、联合体的声明(二)、联合体的特点(三)、联合体大小的计算!&a…...

【trition-server】pytorch 文档:使用 Triton 提供 Torch-TensorRT 模型
Serving a Torch-TensorRT model with Triton pytorch 的官方文档: Serving a Torch-TensorRT model with Triton 在有关机器学习基础设施的讨论中,优化和部署是密不可分的。一旦完成网络级优化以获得最大性能,下一步就是部署它。 然而,提供这种优化模型也有其自身的一系列…...

wps 表格如何实现vlookup高级模糊搜索
一、VLOOKUP 模糊搜索 在 WPS 表格中,可以通过使用 VLOOKUP 函数和通配符来实现高级模糊搜索。这里有一个具体的示例来帮助你理解如何进行这些操作。 示例:实现 VLOOKUP 高级模糊搜索 假设我们有以下数据集: AB产品编号产品名称001苹果00…...

第一天(点亮led灯+led灯闪烁)——Arduino uno R3 学习之旅
常识: 一般智能手机的额定工作电流大约为200mA Arduino Uno板上I/0(输入/输出)引脚最大输出电流为40 mA Uno板控制器总的输出电流为200 mA 点亮LED灯 发光二极管介绍 发光二极管(Light Emitting Diode,简称LED)是一种能够将电能转化为光能的固态的半导体器件…...

【C++题解】1561. 买木头
问题:1561. 买木头 类型:省赛、数组问题、二分答案、贪心、2015江苏省青少年信息学奥林匹克竞赛复赛 题目描述: 有 n 个木材供应商,每个供货商有长度相同一定数量的木头。长木头可以锯短,但短木头不能接长。有一个客…...

解决android native包webview,webview中的请求blocked by CORS policy
在stack overflow查,差不多查到的都是些webView.getSettings().setxxx,没用。在github上找别的类似的android native包webview运行pwa的项目,把它们的webView.getSettings().setxxx全搬过来,写了有一页多,一个有用的都…...

链篦机回转窑球团生产工艺
生球在回转窑氧化焙烧,回转窑头部设有燃烧器,燃料可以采用气体、固体、液体。 来自环冷机一冷却段的高温废气作为二次风进入窑内参与燃烧,烧成成品球进入环冷机。 环冷机采用鼓风冷却,热风风箱分为四段: 一段气体引至…...