养育成本/上海网站搜索排名优化哪家好
1. 引言
接口在系统设计中,以及代码重构优化中,是一个不可或缺的工具,能够帮助我们写出可扩展,可维护性更强的程序。
在本文,我们将介绍什么是接口,在此基础上,通过一个例子来介绍接口的优点。但是接口也不是任何场景都可以随意使用的,我们会介绍接口使用的常见场景,同时也介绍了接口滥用可能带来的问题,以及一些接口滥用的特征,帮助我们及早发现接口滥用的情况。
2. 什么是接口
接口是一种工具,在识别出系统中变化部分时,帮助从系统模块中抽取出变化的部分,从而保证系统的稳定性,可维护性和可扩展性。接口充当了一种契约或规范,规定了类或模块应该提供的方法和行为,而不关心具体的实现细节。
接口通常用于面向对象编程语言中,如 Java
和 Go
等。在这些语言中,类可以实现一个或多个接口,并提供接口定义的方法的具体实现。通过使用接口,我们可以编写更灵活、可维护和可扩展的代码,同时将系统中的变化隔离开来。
接口的实现在不同的编程语言中可能会有所不同。以下简单展示接口在Java
和 Go
语言中的示例。在Go
语言中,接口是一组方法签名的集合。实现接口时,类不需要显式声明实现了哪个接口,只要一个类型实现了接口中的所有方法,就被视为实现了该接口。
// 定义一个接口
type Shape interface {Area() float64Perimeter() float64
}// 实现接口的类型
type Circle struct {Radius float64
}func (c Circle) Area() float64 {return math.Pi * c.Radius * c.Radius
}func (c Circle) Perimeter() float64 {return 2 * math.Pi * c.Radius
}
在Java
语言中,接口使用 interface
定义,同时包含所有的方法签名。类需要通过使用 implements
关键字来实现接口,并提供接口中定义的方法的具体实现。
// 定义一个接口
interface Shape {double area();double perimeter();
}// 实现接口的类
class Circle implements Shape {private double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double area() {return Math.PI * radius * radius;}@Overridepublic double perimeter() {return 2 * Math.PI * radius;}
}
上面示例展示了Java
和 Go
语言中接口的定义方式以及接口的实现方式,虽然具体实现方式各不相同,但它们都遵循了相似的概念,接口用于定义规范和契约,实现类则提供方法的具体实现来满足接口的要求。
3. 接口的优点
在识别出系统变化的部分后,接口能够帮助我们将系统中变化的部分抽取出来,基于此能够降低了模块间的耦合度,能够提高代码的可维护性和代码的模块化程度,有助于创建更灵活、可扩展和易于维护的代码。下面我们通过一个简单的例子来进行说明,详细讨论这些好处。
3.1 初始需求
假设我们在构建一个商城系统,其中一个相对复杂且重要的模块为商品价格的计算,计算购物车中各种商品的总价格。价格计算过程相对复杂,包括了基础价格、折扣、运费的计算,然后每一块内容都会有比较复杂的业务逻辑。
基于此设计了OrderProcessor
结构体,其中的CalculateTotalPrice
实现商品价格的计算,设计了ShippingCalculator
来计算运费,同时还设计DiscountCalculator
来计算商品的折扣信息,通过这几部分的交互配合,共同来完成商家价格的计算。
下面我们通过一段代码来展示上面的计算流程:
type OrderProcessor struct {discountCalculator DiscountCalculatortaxCalculator TaxCalculator
}// 计算总价格
func (tpc OrderProcessor) CalculateTotalPrice(products []Product) float64 {total := 0.0for _, item := range cart {// 获取商品的基础价格basePrice := item.BasePrice// 获取适用于商品的折扣discount := tpc.discountCalculator.CalculateDiscount(item)// 计算运费shippingCost := tpc.shippingCalculator.CalculateShippingCost(item)// 计算商品的最终价格(基础价格 - 折扣 + 税费 + 运费)finalPrice := basePrice - discount + shippingCosttotal += finalPrice}return total
}// 运费计算
type ShippingCalculator struct {}
func (sc ShippingCalculator) CalculateShippingCost(product Product) float64 {return 0.0
}// 折扣计算
type DiscountCalculator struct {}
func (dc DiscountCalculator) CalculateDiscount(product Product) float64 {return 0.0
}
如果这里需求没有发生变化,这个流程可以很好得运转下去。假设这里需要根据商品的类型来应用不同的折扣,之后要怎么支持呢,可以对变化的部分抽取出一个接口,也可以不抽取,都可以支持,我们比较一下没有使用接口和使用接口的两种实现方式的区别。
3.2 不抽象接口
首先是不使用接口的实现,这里我们直接在DiscountCalculator
中叠加逻辑,支持不同类型商品的折扣:
type DiscountCalculator struct{}func (dc DiscountCalculator) CalculateDiscount(product Product) float64 {// 根据商品类型应用不同的折扣逻辑switch product.Type {case "TypeA":return dc.calculateTypeADiscount(product)case "TypeB":return dc.calculateTypeBDiscount(product)default:return dc.calculateDefaultDiscount(product)}
}func (dc DiscountCalculator) calculateTypeADiscount(product Product) float64 {// 计算 TypeA 商品的折扣return product.BasePrice * 0.1 // 例如,假设 TypeA 商品有 10% 的折扣
}func (dc DiscountCalculator) calculateTypeBDiscount(product Product) float64 {// 计算 TypeB 商品的折扣return product.BasePrice * 0.15 // 例如,假设 TypeB 商品有 15% 的折扣
}func (dc DiscountCalculator) calculateDefaultDiscount(product Product) float64 {// 默认折扣逻辑,如果商品类型未匹配到其他情况return product.BasePrice // 默认不打折
}
在这里,我们计算商品折扣,直接使用DiscountCalculator
来实现,根据商品的类型应用不同的折扣逻辑。这里使用了 switch
语句来确定应该应用哪种折扣。这种实现方式虽然在一个类中处理了所有的逻辑,但它可能会导致 DiscountCalculator
类变得庞大且难以维护,特别是当折扣逻辑变得更加复杂或需要频繁更改时。
3.3 抽象接口
下面我们给出一个使用接口的实现,将不同的折扣逻辑封装到不同的实现中,以下是使用接口的示例实现:
type OrderProcessor struct {// 计算商品价格,直接依赖接口discountCalculator DiscountCalculatorInterfacetaxCalculator TaxCalculatorshippingCalculator ShippingCalculator
}// 定义折扣计算器接口
type DiscountCalculatorInterface interface {CalculateDiscount(product Product) float64
}// 定义一个具体的折扣计算器实现
type TypeADiscountCalculator struct{}func (dc TypeADiscountCalculator) CalculateDiscount(product Product) float64 {// 计算 TypeA 商品的折扣return product.BasePrice * 0.1 // 例如,假设 TypeA 商品有 10% 的折扣
}// 定义另一个具体的折扣计算器实现
type TypeBDiscountCalculator struct{}func (dc TypeBDiscountCalculator) CalculateDiscount(product Product) float64 {// 计算 TypeB 商品的折扣return product.BasePrice * 0.15 // 例如,假设 TypeB 商品有 15% 的折扣
}
上述示例中,我们定义了一个 DiscountCalculatorInterface
接口以及两个不同的折扣计算器实现:TypeADiscountCalculator
和 TypeBDiscountCalculator
。 OrderProcessorWithInterface
结构体依赖于 DiscountCalculatorInterface
接口,这使得我们可以根据商品的类型轻松切换不同的折扣策略。
3.4 实现对比
下面我们通过比较上面两种实现,探讨在识别出系统的变化后,让系统依赖一个接口,相对于依赖一个具体类的优点。
首先是对于系统的可扩展性,假设现在需要支持新的类型的折扣,如果引入了接口,只需实现新的折扣计算器并满足相同的接口要求,就可以完成预期的功能。如果我们还是依赖一个具体的类,此时要么在DiscountCalculator
中通过if...else
叠加业务逻辑,相对于接口的引入,代码的可扩展性相比接口的使用就大大降低了。
对于系统的可测试性,如果是定义了接口,我们不需要验证其他DiscountCalculator
的实现,只需要验证当前新增的处理器即可。如果是依赖一个具体的类,此时如果进行测试,就需要对所有分支进行覆盖,很容易疏漏。其次,我们也可以轻松模拟不同的折扣计算器实现,验证 OrderProcessor
的行为。
还有代码可读性和可维护性,接口提供了一种清晰的契约,我们可以将DiscountCalculator
当作一个小的模块,OrderProcessor
通过接口与该模块进行交互,这使得代码更易于理解和维护,因为接口充当了文档,明确了每个模块的预期行为。
最后,通过接口的定义,OrderProcessor
将不再依赖具体的类,而是依赖一个抽象层,降低了系统的耦合度,不再需要关注折扣的计算,让折扣的计算变得更加灵活。
通过以上的讨论,我们认为如果识别出了系统的变化后,该模块可能存在多个不同方向的变化,应该尽量抽取出一个接口,这样能够提高系统的可扩展性,可测试性,代码的可读性以及可维护性都有一定程度的提高。
4. 何时使用接口
接口可以给我们带来一系列的优点,如松耦合,隔绝变化,提高代码的可扩展性等,但是滥用接口的话,反而会引入不必要的复杂性,并增加代码的理解和维护成本。
有一个核心的准则,尽量支持依赖具体的类,而不是抽取接口,不要为了使用接口而创造不必要的抽象,这可能会使代码变得混乱和难以理解。
如果真的使用接口,应该确定其在系统设计中起到促进松耦合和可维护性的作用,而不是增加复杂性。要在合适的场景下使用接口,并考虑接口设计的清晰性和可维护性。下面基于此,我们讨论一些接口可能适用的场景。
4.1 系统中存在变化部分
系统中存在变化的部分是使用接口的最核心场景之一 。 使用接口可以将这些变化部分从系统的其他部分隔离开来,使系统更具灵活性和可维护性。这种设计允许我们将变化的部分抽取为一个单独的模块,在变化时,只需要对该模块进行修改,而不必修改整个系统。接口充当了变化部分的契约,使不同的实现可以轻松地替换或添加,从而适应新的需求或变化的情况。
比如系统需要向用户发送邮件,可能不同的运营商提供了不同的API,然后我们系统中需要支持多个不同的运营商,在不同场景下使用不同运营商的接口。
此时我们通过定义接口,系统通过与该接口进行交互即可,而不需要关心底层的实现细节。如果将来要添加新的邮件服务提供商,只需创建一个新的类并实现接口即可,而不需要修改现有的代码。
这种方式使系统的变化部分与其余部分隔离开来,提高了系统的可维护性和可扩展性。此外,通过使用接口,我们可以创建模拟邮件发送器来验证系统的行为,更容易进行单元测试。
4.2 类库的可配置性
类库对外扩展和提供可配置性也是接口使用的重要场景之一。当开发一个类库或框架时,为了让用户能够轻松地扩展和自定义其行为,可以通过接口提供一组可配置的扩展点。这些扩展点允许用户提供自己的实现,以适应其特定需求。
举例来说,一个日志库可以定义一个接口 Logger
,并允许用户提供他们自己的 Logger
实现。用户可以选择使用默认的日志记录实现,也可以创建一个自定义的实现,以将日志信息发送到不同的地方(例如文件、数据库、远程服务器等)。这种可配置性使用户能够根据其项目的要求自由选择和调整库的行为。
通过提供接口和可配置性,类库或框架可以更具通用性和灵活性,使用户能够根据其特定的用例和需求来定制和扩展库的功能,从而提高了库的可用性和适用性。这种模块化的设计方式有助于减少代码的重复,促进了代码的复用,同时也提供了更好的可扩展性和可维护性。
4.3 模块间的交互
系统划分不同模块并使用接口来进行交互也是一个重要的场景。当将系统划分为不同的模块或组件时,使用接口定义模块之间的契约和互动方式是一种良好的实践。每个模块可以实现所需的接口,并与其他模块进行交互,这使得模块之间的界限更加清晰,易于理解和维护。
使用接口可以降低模块之间的耦合度。这意味着每个模块不需要关心其他模块的具体实现细节,只需要遵循接口定义的契约。这种模块化的设计方式有助于将复杂的系统拆分为更小、更易管理的部分,并降低了系统开发和维护的复杂性。
4.4 单元测试的使用
在需要解除一个庞大的外部系统的依赖时。有时候我们并不是需要多个选择,而是某个外部依赖过重,我们测试或其他场景可能会选择 mock 一个外部依赖,以便降低测试系统的依赖。
比如依赖多个外部rpc,单元测试时需要屏蔽外部的依赖,此时就比较有必要使用接口,通过框架生成一个mock的实现,从而解除对外部的依赖。
5. 潜在的误用和滥用
5.1 接口滥用带来的问题
虽然接口在合适的场景中非常有用,但滥用接口可能会导致代码变得复杂、难以理解和难以维护。引入过多的接口可能会增加系统的复杂性,使代码难以理解。每个接口都需要额外的抽象和实现,这可能不是必要的。其次使用接口有时会引入额外的性能开销,因为运行时需要进行接口解析。在性能敏感的应用中,这可能是一个问题。
最重要的一个问题,接口的目标是提供一种通用的抽象,给系统提供可配置项,但有时候过度一般化可能会导致不必要的复杂性。在某些情况下,直接使用具体的类可能更加简单和清晰。
我们应该在确保接口是必要的情况下使用它们,以避免不必要的复杂性和耦合。接口的设计应该基于真正的需求和系统架构,而不是仅仅为了使用接口而使用接口。
5.2 如何识别接口是否滥用
对于识别接口是否滥用,可以通过下面几个方面来检查,如果满足了下面的某一个条件,此时大概率就出现了接口滥用的情况。
是否过早的抽象,在引入该接口时,系统中是否足够的不同实现来正当地支持这些接口。如果没有的话,此时大概率过早接口的引入,增加了复杂性,而不带来真正的好处。
是否所有类之间引入接口,无论是否有必要,在这种情况下,接口的数量可能会急剧增加,导致代码难以理解和维护,可能还是存在一定滥用的情况。
如果接口经常发生变化,那么实现这些接口的类可能需要频繁地进行修改,这会增加维护的难度,此时要么接口是不必要的,要么接口的设计是不合理的,需要重新设计。
总的来说, 我们需要确保真正需要接口时才引入它们。应该谨慎考虑每个接口的设计,确保它们具有明确的用途(如隔绝变化,模块间交互的契约,方便单元测试),并且不引入不必要的复杂性。根据实际需求和系统架构来合理地使用接口,而不是为了使用接口而使用接口。
6. 总结
在本文,我们介绍了什么是接口,接口是一种契约,一种协议,用于模块间的交互。
在此基础上,通过一个例子来介绍接口的优点,了解到接口可以提高代码的可扩展性,可维护性,以及降低系统之间的耦合度。
但是接口也不是任何场景都可以随意使用的,我们会介绍接口使用的常见场景,包括隔绝系统的变化部分,以及一些类库设计时对外提供配置项的场景。
最后我们还介绍了接口滥用可能带来的问题,以及一些比较明显的特征,帮助我们更早识别出系统设计的坏味道。
基于此,完成了对接口的完整介绍,希望对你有所帮助。
相关文章:

接口使用的最佳时机
1. 引言 接口在系统设计中,以及代码重构优化中,是一个不可或缺的工具,能够帮助我们写出可扩展,可维护性更强的程序。 在本文,我们将介绍什么是接口,在此基础上,通过一个例子来介绍接口的优点。…...

freertos之任务运行时间统计实验
这里写目录标题 任务时间统计函数时间统计API函数使用流程实验 任务时间统计函数 void vTaskGetRunTimeStats(char * pcWriteBuffer); 时间统计API函数使用流程 实验 1.首先现在FreeRTOSConfig.h文件里将configGENERATE_RUN_TIME_STATS 和configUSE_STATS_FORMATTING_FUNCTIO…...

Js中一些数组常用API总结
前言 Js中数组是一个重要的数据结构,它相比于字符串有更多的方法,在一些算法题中我们经常需要将字符串转化为数组,使用数组里面的API进行操作。本篇文章总结了一些数组中常用的API,我们把它们分成两类,一类是会改变原…...

LlamaIndex:将个人数据添加到LLM
推荐:使用 NSDT场景编辑器 快速搭建3D应用场景 LlamaIndex是基于大型语言模型(LLM)的应用程序的数据框架。像 GPT-4 这样的 LLM 是在大量公共数据集上预先训练的,允许开箱即用的令人难以置信的自然语言处理能力。但是,…...

Mojo 摸脚语言,似乎已经可以安装
文章原地址:https://i.scwy.net/it/2023/090821-mojo/ Mojo 吹得很凶,面向AI编程,甩Python几十条街,融资上亿.... 但无缘一试,在Win和Ubuntu上试都不能通过。 由 LLVM 和 Swift 编程语言的联合创始人 Chris Lattner…...

Spark 6:Spark SQL DataFrame
SparkSQL 是Spark的一个模块, 用于处理海量结构化数据。 SparkSQL是用于处理大规模结构化数据的计算引擎 SparkSQL在企业中广泛使用,并性能极好 SparkSQL:使用简单、API统一、兼容HIVE、支持标准化JDBC和ODBC连接 SparkSQL 2014年正式发布,当…...

区块链智能合约编程语言 Solidity
文章目录 前言Solidity 介绍Solidity 文件结构许可声明编译指示数据类型函数事件访问区块元数据 简单的智能合约 前言 上文介绍了区块链生态发展,我们知道以太坊的到来可以使开发人员基于区块链开发DApp,本文介绍 Solidity 编程语言的使用,然…...

将SSL证书设置成HTTPS的详细步骤
在互联网上建立一个安全且可信任的网站,HTTPS是一种常用的解决方案。HTTPS是HTTP的安全版本,通过使用SSL/TLS协议对传输的数据进行加密,确保数据传输的安全性。要实现HTTPS,你需要将SSL证书设置到你的网站上。以下是详细的步骤&am…...

43、Flink之Hive 读写及详细验证示例
Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...

2023数模国赛C 题 蔬菜类商品的自动定价与补货决策-完整版创新多思路详解(含代码)
题目简评:看下来C题是三道题目里简单一些的,考察的点比较综合,偏数据分析。涉及预测模型和运筹优化(线性规划),还设了一问开放型问题,适合新手入门,发挥空间大。 题目分析与思路: 背景&#x…...

javaScript:DOM中常用尺寸
目录 前言(可以根据图示找到需要的尺寸,便于理解) 内尺寸 clientWidth 包含左右padding和宽度width(忽略滚动条的宽度) clientHeight 包含上下padding和height(忽略滚动条的高度) clientTo…...
决策树算法学习笔记
一、决策树简介 首先决策树是一种有监督的机器学习算法,其采用的方法是自顶向下的递归方法,构建一颗树状结构的树,其具有分类和预测功能。其基本思想是以信息熵为度量构造一棵熵值下降最快的树,到叶子节点处的熵值为零。决策树的构…...

Verilog_mode常用的几个用法
一:verilog mode中如何使用正则表达 在顶层实例化时,有大量的信号需要重新命名,使用模板的话会增加大量的注释内容,不过往往这些信号命名有特定的规律,我们可以使用正则表达式来处理,下面举几个例子&#…...

MySQL之MHA高可用配置及故障切换
目录 一、MHA概念 1、MHA的组成 2、MHA的特点 3、主从复制有多少种复制方法 二、搭建MySqlMHA部署 1.Master、Slave1、Slave2 节点上安装 mysql 2.修改 Master、Slave1、Slave2 节点的 Mysql主配置文件/etc/my.cnf 3. 配置 mysql 一主两从 4、安…...

java实现状态模式
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为。在状态模式中,对象将其行为委托给表示不同状态的状态对象,这些状态对象负责管理其行为。以下是在 Java 中实现状态模式的一般步骤: 创建一个状态接口ÿ…...

Selling a Menagerie(cf)
该题考察了拓扑排序dfs 题意:你是一个动物园的主人,该动物园由编号从1到n的n只动物组成。然而,维护动物园是相当昂贵的,所以你决定卖掉它!众所周知,每种动物都害怕另一种动物。更确切地说,动物…...

python-55-打包exe执行
目录 前言一、pyinstaller二、实践打包exe1、遇坑1:Plugin already registered2、遇坑2:OSError 句柄无效 三、总结 前言 你是否有这种烦恼? 别人在使用你的项目时可能还需要安装各种依赖包?别人在使用你的项目,可能…...

linux并发服务器 —— IO多路复用(八)
半关闭、端口复用 半关闭只能实现数据单方向的传输;当TCP 接中A向 B 发送 FIN 请求关闭,另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态),并没有立即发送 FIN 给 A,A 方处于半连接状态 (半开关),此时 A 可以接收 B…...

企微SCRM营销平台MarketGo-ChatGPT助力私域运营
一、前言 ChatGPT是由OpenAI(开放人工智能)研发的自然语言处理模型,其全称为"Conversational Generative Pre-trained Transformer",即对话式预训练转换器。它是GPT系列模型的最新版本,GPT全称为"Gene…...

linux C++ 海康截图Demo
项目结构 CMakeLists.txt cmake_minimum_required(VERSION 3.7)project(CapPictureTest)include_directories(include)link_directories(${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib/HCNetSDKCom) add_executable(CapPictureTest ${CMAKE_SOURCE_DIR}/src/CapPictureTes…...

MySQL的事务隔离级别
目录 事务隔离级别的概念 脏读(Dirty Read): 不可重复读(Non-Repeatable Read): 幻读(Phantom Read): 读未提交(Read Uncommitted) 读未提交…...

企业大语言模型智能问答的底层基础数据知识库如何搭建?
企业大语言模型智能问答的底层基础数据知识库搭建是一个复杂而关键的过程。下面将详细介绍如何搭建这样一个知识库。 确定知识库的范围和目标: 首先,需要明确知识库的范围,确定所涵盖的领域和主题。这可以根据企业的业务领域和用户需求来确…...

【腾讯云 Cloud Studio 实战训练营】使用python爬虫和数据可视化对比“泸州老窖和五粮液4年内股票变化”
Cloud Studio 简介 Cloud Studio是腾讯云发布的云端开发者工具,支持开发者利用Web IDE(集成开发环境),实现远程协作开发和应用部署。 现在的Cloud Studio已经全面支持Java Spring Boot、Python、Node.js等多种开发模板示例库&am…...

Linux之Shell概述
目录 Linux之Shell概述 学习shell的原因 shell是什么 shell起源 查看当前系统支持的shell 查看当前系统默认shell Shell 概念 Shell 程序设计语言 Shell 也是一种脚本语言 用途 Shell脚本的基本元素 基本元素构成: Shell脚本中的注释和风格 Shell脚本编…...

手写Spring:第2章-创建简单的Bean容器
文章目录 一、目标:创建简单的Bean容器二、设计:创建简单的Bean容器三、实现:创建简单的Bean容器3.0 引入依赖3.1 工程结构3.2 创建简单Bean容器类图3.3 Bean定义3.4 Bean工厂 四、测试:创建简单的Bean容器4.1 用户Bean对象4.2 单…...

在Windows上通过SSH公私钥实现无密码登录Linux
在Windows上通过SSH公私钥实现无密码登录Linux 在Windows上生成SSH密钥对: 打开命令提示符或PowerShell窗口。 输入以下命令生成SSH密钥对: ssh-keygen -t rsa -b 4096按照提示输入密钥的保存路径和密码(可选)。 在指定的路径下…...

使用ppt和texlive生成eps图片(高清、可插入latex论文)
一、说明 写论文经常需要生成高清的图片插入到论文中,本文以ppt画图生成高质量的eps图片的实现来介绍具体操作方法。关于为什么要生成eps图片,一个是期刊要求(也有不要求的),另一个是显示图像的质量高。 转化获得eps…...

html5学习笔记19-SSE服务器发送事件(Server-Sent Events)
https://www.runoob.com/html/html5-serversentevents.html 允许网页获得来自服务器的更新。类似设置回调函数。 if(typeof(EventSource)!"undefined"){var sourcenew EventSource("demo_sse.php");source.onmessagefunction(event){document.getElement…...

高效数据湖构建与数据仓库融合:大规模数据架构最佳实践
文章目录 数据湖和数据仓库:两大不同理念数据湖数据仓库 数据湖与数据仓库的融合统一数据目录数据清洗和转换数据安全和权限控制数据分析和可视化 数据湖与数据仓库融合的优势未来趋势云原生数据湖自动化数据处理边缘计算与数据湖融合 结论 🎉欢迎来到云…...

Java学习笔记——35多线程02
线程同步 线程同步卖票案例同步代码块同步方法块 线程安全的类StringBufferVectorHashtable Lock锁 线程同步 卖票案例 public class SellTicket implements Runnable{private int tickets10;Overridepublic void run(){while (true){if(tickets>0){System.out.println(Th…...