干货来袭!Android架构演进史你又知道多少?
ccwgpt 2024-10-11 11:12 66 浏览 0 评论
一.MVC架构
1.概述
MVC架构是第一个应用于Android开发的成熟架构,由Model、View、Controller三部分组成:
- Model:负责数据的存储及相关逻辑。
- View:负责界面展示。
- Controller:负责业务逻辑。
MVC架构将代码逻辑分成了数据逻辑、渲染逻辑、业务逻辑三部分,三部分逻辑分别封装在Model层、View层、Controller层。理想条件下,三者呈单向调用,如下图所示:
但实际上,由于Android中负责页面展示的组件(Activity或Fragment)同时承担了View层和Controller层两部分的职责,导致MVC架构在实际使用中退化。退化后的MVC架构如下图所示:
在一些业务场景中,由于View层与Controller层的逻辑耦合在一起,使得二者可以相互调用,进一步衍生出调用关系更加复杂的MVC架构,如下图所示:
此时的MVC架构仅保持了Model层的独立,而View层和Controller层没有得到有效的隔离。
2.例子
点击按钮从网络获取用户信息,经过解析后展示到界面上。
1)Model层
data class UserInfo(
var name: String,
var age: Int,
var height: Int,
var weight: Int
)
interface NetResponse<T> {
fun onSuccess(data: T)
fun onError(e: Throwable)
}
class NetModel {
fun getUserInfo(callback: NetResponse<UserInfo>) {
// 模拟网络获取
val dataStr = getStringFromNet()
val info = dataStr.parseJson<UserInfo>()
return callback.onSuccess(info)
}
}
复制代码
2)Controller层
class MainActivity : AppCompatActivity() {
private val netModel = NetModel()
// Controller调用Model
private fun getUserInfo(callback: (UserInfo) -> Unit) {
netModel.getUserInfo(object : NetResponse<UserInfo> {
override fun onSuccess(data: UserInfo) {
callback.invoke(data)
}
override fun onError(e: Throwable) {
callback.invoke(getNetErrorData())
}
})
}
private fun getNetErrorData(): UserInfo {
return UserInfo(name = "", age = 0, height = 0, weight = 0)
}
}
复制代码
3)View层
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnNet?.setOnClickListener {
// View调用Controller
getUserInfo {
tvName?.text = it.name
tvAge?.text = it.age.toString()
tvHeight?.text = it.height.toString()
tvWeight?.text = it.weight.toString()
}
}
}
}
复制代码
在MVC架构中,Controller实际上就是对Model代码的封装,如果在btnNet的点击回调中调用getUserInfo方法,则是View调用Controller。如果在点击回调中直接调用netModel的getUserInfo方法,那就变成了View调用Model。
二.MVP架构
1.概述
MVP架构是MVC架构的升级版,由Model、View、Presenter三部分组成:
- Model:负责数据的存储及相关逻辑。
- View:负责界面展示。
- Presenter:负责业务逻辑。
MVP架构也将代码逻辑分成了数据逻辑、渲染逻辑、业务逻辑三部分。与MVC架构不同的是,在MVP架构中,负责业务逻辑的Controller层升级为Presenter层。Presenter层不再像Controller层一样耦合在View层中,而是作为独立的类与View层和Model层进行双向的通信,三者调用关系如下图所示:
MVP架构有效的将View层与Model层隔离,同时,由于Presenter层仅持有View层和Model层的接口,因此对于Presenter层逻辑的测试变得更加的方便。而由于所有的逻辑代码都集中到Presenter层中,因此Presenter层变得比原本的Controller层更臃肿。
2.例子
点击按钮从网络获取用户信息,经过解析后展示到界面上。
1)Model层
data class UserInfo(
var name: String,
var age: Int,
var height: Int,
var weight: Int
)
interface NetResponse<T> {
fun onSuccess(data: T)
fun onError(e: Throwable)
}
class NetModel {
fun getUserInfo(callback: NetResponse<UserInfo>) {
// 模拟网络获取
val dataStr = getStringFromNet()
val info = dataStr.parseJson<UserInfo>()
return callback.onSuccess(info)
}
}
复制代码
2)Presenter层
class Presenter(private val view: IView) {
private val model = NetModel()
fun getUserInfo() {
model.getUserInfo(object : NetResponse<UserInfo> {
override fun onSuccess(data: UserInfo) {
view.setName(data.name)
view.setAge(data.age)
view.setHeight(data.height)
view.setWeight(data.weight)
}
override fun onError(e: Throwable) {
val data = getNetErrorData()
view.setName(data.name)
view.setAge(data.age)
view.setHeight(data.height)
view.setWeight(data.weight)
}
})
}
private fun getNetErrorData(): UserInfo {
return UserInfo(name = "", age = 0, height = 0, weight = 0)
}
}
复制代码
3)View层
interface IView {
fun setName(name: String)
fun setAge(age: Int)
fun setHeight(height: Int)
fun setWeight(weight: Int)
}
class MainActivity : AppCompatActivity(), IView {
private val presenter = Presenter(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnNet?.setOnClickListener {
presenter.getUserInfo()
}
}
override fun setName(name: String) {
tvName?.text = name
}
override fun setAge(age: Int) {
tvAge?.text = age.toString()
}
override fun setHeight(height: Int) {
tvHeight?.text = height.toString()
}
override fun setWeight(weight: Int) {
tvWeight?.text = weight.toString()
}
}
复制代码
三.MVVM架构
1.概述
MVVM架构最早由微软提出,并在微软的桌面客户端UI框架WPF中实现,由Model、View、ViewModel三部分组成:
- Model:负责数据的存储及相关逻辑。
- View:负责界面展示。
- ViewModel:负责业务逻辑。
MVVM架构是MVP架构的简化版,它同样的解决了View层与Model层的隔离问题。不同于MVP架构中的View层与Presenter层之间的双向通信,MVVM架构采用了一种View层与ViewModel层绑定的方式,当ViewModel层中的UI数据或属性发生变化时,View层的UI会跟随一起变化,这样原本Presenter层中操作View层接口的逻辑通过绑定的方式被简化,层级变得更加轻量,简化后的Presenter层与View层之间只存在数据驱动的关系,Presenter层被简化成了ViewModel层。三个层级的调用关系如下图所示:
在MVVM架构的早期发展中, 开发者们使用谷歌官方的DataBinding框架实现数据与视图的绑定,但DataBinding框架使用复杂,且每当有一处代码需要修改,就会联动修改多处代码。后来,谷歌官方推荐使用Jetpack组件集中的LiveData+ViewModel。通过使用LiveData+ViewModel,大大减少了在使用MVVM架构中模版代码的编写。再后来,为了更好的在协程中使用MVVM架构,以及更方便的编写流式代码,谷歌官方推荐使用StateFlow。
MVVM架构既做到了View层与Model层的隔离,又保证了ViewModel层的简洁不臃肿。View层与ViewModel层的交互完全依赖数据驱动,方便层级的逻辑复用与测试以及多人的协作开发。但同时,由于MVVM架构比MVC架构和MVP架构更抽象,因此在使用时需要编写更多的模版代码。当UI界面复杂时,由于每个UI需要有不同的数据,因此会造成数据过于分散不易维护。
2.例子
点击按钮从网络获取用户信息,经过解析后展示到界面上。
1)Model层
data class UserInfo(
var name: String,
var age: Int,
var height: Int,
var weight: Int
)
interface NetResponse<T> {
fun onSuccess(data: T)
fun onError(e: Throwable)
}
class NetModel {
fun getUserInfo(callback: NetResponse<UserInfo>) {
// 模拟网络获取
val dataStr = getStringFromNet()
val info = dataStr.parseJson<UserInfo>()
return callback.onSuccess(info)
}
}
复制代码
2)ViewModel层
class NetViewModel : ViewModel() {
private val model = NetModel()
private val name = MutableLiveData<String>("")
private val age = MutableLiveData<Int>(0)
private val height = MutableLiveData<Int>(0)
private val weight = MutableLiveData<Int>(0)
fun getUserInfo() {
model.getUserInfo(object : NetResponse<UserInfo> {
override fun onSuccess(data: UserInfo) {
name.postValue(data.name)
age.postValue(data.age)
height.postValue(data.height)
weight.postValue(data.weight)
}
override fun onError(e: Throwable) {
val data = getNetErrorData()
name.postValue(data.name)
age.postValue(data.age)
height.postValue(data.height)
weight.postValue(data.weight)
}
})
}
private fun getNetErrorData(): UserInfo {
return UserInfo(name = "", age = 0, height = 0, weight = 0)
}
fun name(): LiveData<String> = name
fun age(): LiveData<Int> = age
fun height(): LiveData<Int> = height
fun weight(): LiveData<Int> = weight
}
复制代码
3)View层
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: NetViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(NetViewModel::class.java)
viewModel.name().observe(this, Observer<String> {
tvName?.text = it
})
viewModel.age().observe(this, Observer<Int> {
tvAge?.text = it.toString()
})
viewModel.height().observe(this, Observer<Int> {
tvHeight?.text = it.toString()
})
viewModel.weight().observe(this, Observer<Int> {
tvWeight?.text = it.toString()
})
btnNet?.setOnClickListener {
viewModel.getUserInfo()
}
}
}
复制代码
四.Clean架构
1.概述
Clean架构由Robert.C.Martin(Bob大叔)提出,最基础的Clean架构由Entities(实体层)、Use Cases(用例层)、Interface Adapters(接口适配层)、Frameworks & Drivers(框架驱动层)四部分组成:
- Entities:用于封装企业业务规则,指业务的核心数据模型和接口,一般情况下不会发生变化,只有当业务规则改变才会改变实体层。
- Use Cases:用于封装软件应用的业务规则,包含实现整个应用所需全部功能的用例,通过调用各种实体的规则实现用例的功能。用例层的改变不会影响实体层,同时外部UI与数据库的改变也不会影响到用例层。
- Interface Adapters:用于将外部数据转换为实体层与用例层需要的数据结构或将实体层与用例层返回的数据转换为外部需要的数据结构。Clean-MVP中Presenter就是在这一层。
- Frameworks & Drivers:由一些框架与工具组成,如数据库、UI框架等。这层更多的是用于编写技术细节,而不需要考虑业务逻辑。
Clean架构通过规范层级间的单向依赖关系,从而形成一层包裹一层的层级结构。如下图所示:
Clean架构的层级并不是固定的。根据不同的业务特点,Clean架构也可有更多的层级(最少四层)。由此可见,Clean架构并非传统意义上的架构,它更像是一种理念。Clean架构可以与MVC架构、MVP架构、MVVM架构结合形成Clean-MVC架构、Clean-MVP架构、Clean-MVVM架构。谷歌官方借鉴Clean的理念推出Clean-MVVM架构。
Clean架构具有单向依赖、层级分明、数据驱动的优点。因此在Clean架构中,各个层级的代码逻辑更容易被测试,缺陷与问题更容易被定位,代码的可读性得到显著提升。Clean架构的结构十分复杂。它被称为洋葱架构,不仅是因为它层层包裹的结构,也是因为当你意识到需要写多层模版代码时,Clean架构会让你哭泣。同时,Clean架构还存在用例层代码复用率低、急剧增加的用例导致类膨胀的问题。
2.例子
点击按钮从网络获取用户信息,经过解析后展示到界面上。
1)Entities层
data class UserInfo(
var name: String,
var age: Int,
var height: Int,
var weight: Int
)
复制代码
2)Use Cases层
interface UseCase<T, R> {
fun execute(param: T, callback: R)
}
interface UseCaseCallback<T> {
fun success(data: T)
fun fail(e: Throwable)
}
class GetUserInfoUseCase : UseCase<Unit, UseCaseCallback<UserInfo>> {
private val repository = NetRepository()
override fun execute(param: Unit, callback: UseCaseCallback<UserInfo>) {
repository.getUserInfo(object : NetResponse<UserInfo> {
override fun onSuccess(data: UserInfo) {
callback.success(data)
}
override fun onError(e: Throwable) {
callback.fail(e)
}
})
}
}
复制代码
3)Interface Adapters层
interface NetResponse<T> {
fun onSuccess(data: T)
fun onError(e: Throwable)
}
class NetRepository {
fun getUserInfo(callback: NetResponse<UserInfo>) {
// 模拟网络获取
val dataStr = getStringFromNet()
val info = dataStr.parseJson<UserInfo>()
return callback.onSuccess(info)
}
}
interface IView {
fun setName(name: String)
fun setAge(age: String)
fun setHeight(height: String)
fun setWeight(weight: String)
}
class Presenter(private val view: IView) : UseCaseCallback<UserInfo> {
private val getUserInfoUseCase = GetUserInfoUseCase()
fun getUserInfo() =
getUserInfoUseCase.execute(Unit, this)
override fun success(data: UserInfo) {
view.setName(data.name)
view.setAge(data.age.toString())
view.setHeight(data.height.toString())
view.setWeight(data.weight.toString())
}
override fun fail(e: Throwable) {
val data = getNetErrorData()
view.setName(data.name)
view.setAge(data.age.toString())
view.setHeight(data.height.toString())
view.setWeight(data.weight.toString())
}
private fun getNetErrorData(): UserInfo {
return UserInfo(name = "", age = 0, height = 0, weight = 0)
}
}
复制代码
4)Frameworks & Drivers层
class MainActivity : AppCompatActivity(), IView {
private val presenter = Presenter(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnNet?.setOnClickListener {
presenter.getUserInfo()
}
}
override fun setName(name: String) {
tvName?.text = name
}
override fun setAge(age: String) {
tvAge?.text = age
}
override fun setHeight(height: String) {
tvHeight?.text = height
}
override fun setWeight(weight: String) {
tvWeight?.text = weight
}
}
复制代码
五.MVI架构
1.概述
MVI架构是MVVM架构的升级版,由Model、View、Intent三部分组成:
- Model:负责存储视图的数据和状态。
- View:负责界面的展示。
- Intent:负责封装与发送用户的操作。
MVI架构在MVVM架构的基础上强调数据的单向流动与状态的集中管理,保证数据的唯一性。在MVVM架构中三者的调用关系如下图所示:
在MVVM架构中,View会调用ViewModel执行具体的逻辑操作。而在MVI架构中,用户对View层的交互会被抽象成Intent。当交互发生时,View会发送对应的Intent到ViewModel中处理,通过Intent隔离View对ViewModel的调用。如下图所示:
在MVVM架构中,ViewModel内部对View的数据与属性的维护是分散的,具体表现为每个View都会有一个Data。而在MVI架构中,这些Data将会被整合在一起,形成统一的State,如下图所示:
在MVI架构中,Model层指的就是State。通过引入State,可以更加方便ViewModel的管理。同时隔离ViewModel对View的调用,如下图所示:
这样,原本在MVVM架构中由于双向绑定引起的代码耦合问题,在MVI架构中通过引入Intent与State得以解决。同时,架构整体的数据流向更加清晰,如下图所示:
在MVI架构中,通过状态集中管理减少了模版代码。通过数据单向流动保证了数据流向的一致性,开发者可以更方便的对状态进行跟踪与调试。MVI也存在着一些问题,由于状态集中管理,当页面功能复杂时,State易膨胀。每次更新状态时,都需要更新整体的状态,对内存有一定开销,而且不支持局部刷新。
2.例子
点击按钮从网络获取用户信息,经过解析后展示到界面上。
1)Model层
data class PageState(
val response: PageResponse = PageResponse.Default,
val userInfo: UserInfo = UserInfo()
)
sealed class PageResponse {
class Success<T>(data: T) : PageResponse()
class Fail(e: Throwable) : PageResponse()
object Loading : PageResponse()
object Default : PageResponse()
}
data class UserInfo(
val name: String = "",
val age: Int = 0,
val height: Int = 0,
val weight: Int = 0
)
复制代码
2)Intent层
sealed class UserIntent {
object GetUserInfo : UserIntent()
object Default : UserIntent()
}
复制代码
3)ViewModel层
class NetViewModel : ViewModel() {
private val model = NetRepository()
private val state = MutableStateFlow(PageState())
private val intent = MutableStateFlow<UserIntent>(UserIntent.Default)
init {
handleUserIntent()
}
fun state() =
state.asStateFlow()
fun sendIntent(userIntent: UserIntent) =
viewModelScope.launch { intent.emit(userIntent) }
private fun handleUserIntent() =
intent.onEach {
when (it) {
is UserIntent.GetUserInfo -> getUserInfo()
else -> {}
}
}.launchIn(viewModelScope)
private fun getUserInfo() {
state.value = state.value.copy(response = PageResponse.Loading)
model.getUserInfo(object : NetResponse<UserInfo> {
override fun onSuccess(data: UserInfo) {
state.value =
state.value.copy(response = PageResponse.Success(data), userInfo = data)
}
override fun onError(e: Throwable) {
state.value =
state.value.copy(response = PageResponse.Fail(e), userInfo = getNetErrorData())
}
})
}
private fun getNetErrorData(): UserInfo {
return UserInfo(name = "", age = 0, height = 0, weight = 0)
}
}
interface NetResponse<T> {
fun onSuccess(data: T)
fun onError(e: Throwable)
}
class NetRepository {
fun getUserInfo(callback: NetResponse<UserInfo>) {
// 模拟网络获取
val dataStr = getStringFromNet()
val info = dataStr.parseJson<UserInfo>()
return callback.onSuccess(info)
}
}
复制代码
4)View层
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: NetViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(NetViewModel::class.java)
viewModel.state().onEach {
when (it.response) {
is PageResponse.Success<*> -> {
tvName?.text = it.response
tvAge?.text = it.userInfo.age.toString()
tvHeight?.text = it.userInfo.height.toString()
tvWeight?.text = it.userInfo.weight.toString()
}
else -> {}
}
}.launchIn(lifecycleScope)
btnNet?.setOnClickListener {
viewModel.sendIntent(UserIntent.GetUserInfo)
}
}
}
复制代码
六.总结
1.从MVC架构到MVI架构
开发者们在早期就意识到数据逻辑、业务逻辑、渲染逻辑要分离,因此引入了MVC架构。但由于Android页面组件的特殊性,导致在实际使用中MVC架构的数据逻辑、业务逻辑与渲染逻辑并没有完美分离,因此产生了MVP架构。MVP架构虽然解决了渲染逻辑与数据逻辑的隔离问题,但由于Presenter层需要与View层和Model层双向通信,导致承载业务逻辑的Presenter层过于臃肿,因此引入了MVVM架构。MVVM架构通过绑定的通信方式,解决了业务逻辑层由于层级间通信而过于臃肿的问题。随着时间推移,绑定框架也在不断发展:从DataBinding到LiveData,再从LiveData到StateFlow。由于当界面UI复杂时,MVVM架构的数据过于分散不易维护,因此产生了MVI架构。MVI架构引入了虚拟的“用户层”,框架的数据流动从“用户层”开始,到“用户层”结束,单方向流动。
2.从clean code到clean coder
纵观Android架构的发展历程,数据逻辑、业务逻辑与渲染逻辑的分离关注点正在从代码逐渐转移到用户,这是一种由“物”向“人”的转变。
这种转变在Clean架构也中有所体现。Clean架构的提出者Bob大叔早年间一直致力于保持代码的整洁与条理清晰,于是出版了《Clean Code》。后来,经过探索研究发现,想要保持代码的“干净”,最重要的还是要提升开发者的自身素质,于是诞生了《 The Clean Coder》。Clean架构通过复杂细致的分层设计,规范每个层级的功能,通过依赖关系保证了代码的整洁。但每层内代码的整洁,仍然需要开发者通过代码素养来保证。只有保持每一层代码的整洁,整体代码才能整洁。
3.MVI架构之后
谷歌官方最早推荐开发者使用MVVM架构,在MVI架构推出后,转而推荐使用MVI架构。那么,在MVI架构之后的下一个主流框架又将是什么呢?这一点恐怕无人知晓。但通过分析可以发现,谷歌官方推荐使用MVI架构,本质上是在推荐状态集中管理和单向数据流动的开发思想,而这一开发思想正好是响应式编程的基础。因此可以合理预测下一个主流架构可能就是某个响应式框架。谷歌官方这几年也一直在开发响应式开发框架Compose,但Compose框架在未来能否取代MVI框架仍然是个未知数。
原文:https://juejin.cn/post/7123550800265084959
相关推荐
- 自己动手写Android数据库框架_android开发数据库搭建
-
http://blog.csdn.net/feiduclear_up/article/details/50557590推荐理由关于Android数据库操作,由于每次都要自己写数据库操作,每次还得去...
- 谷歌开源大模型评测工具LMEval,打通谷歌、OpenAI、Anthropic
-
智东西编译|金碧辉编辑|程茜智东西5月28日消息,据科技媒体TheDecoder5月26日报道,当天,谷歌正式发布开源大模型评测框架LMEval,支持对GPT-4o、Claude3.7...
- 工信部:着力推动大模型算法、框架等基础性原创性的技术突破
-
工信部新闻发言人今日在发布会上表示,下一步,我们将坚持突出重点领域,大力推动制造业数字化转型,推动人工智能创新应用。主要从以下四个方面着力。一是夯实人工智能技术底座。通过科技创新重大项目,着力推动大模...
- 乒乓反复纠结“框架不稳定”的三个小误区
-
很多球友由于对框架的认知不清晰,往往会把“框架不稳定”当成一种心理负担,从而影响学球进度,其典型状态就是训练中有模有样,一旦进入实战,就像被捆住了手脚。通过训练和学习,结合“基本功打卡群”球友们交流发...
- 前AMD、英特尔显卡架构师Raja再战GPU,号称要全面重构堆栈
-
IT之家8月5日消息,知名GPU架构师拉贾科杜里(RajaKoduri)此前曾先后在AMD和英特尔的显卡部门担任要职。而在今日,由Raja创立的GPU软件与IP初创企...
- 三种必须掌握的嵌入式开发程序架构
-
前言在嵌入式软件开发,包括单片机开发中,软件架构对于开发人员是一个必须认真考虑的问题。软件架构对于系统整体的稳定性和可靠性是非常重要的,一个合适的软件架构不仅结构清晰,并且便于开发。我相...
- 怪不得别人3秒就知道软考案例怎么做能50+
-
软考高级统一合格标准必须三科都达到45分,案例分析也一直是考生头疼的一门,但是掌握到得分点,案例能不能50+还不是你们说了算吗?今天就结合架构案例考点,分享实用的备考攻略~一、吃透考点,搭建知识框架从...
- UML统一建模常用图有哪些,各自的作用是什么?一篇文章彻底讲透
-
10万+爆款解析:9大UML图实战案例,小白也能秒懂!为什么需要UML?UML(统一建模语言)是软件开发的“蓝图”,用图形化语言描述系统结构、行为和交互,让复杂需求一目了然。它能:降低沟通成本避...
- 勒索软件转向云原生架构,直指备份基础设施
-
勒索软件组织和其他网络犯罪分子正越来越多地将目标对准基于云的备份系统,对久已确立的灾难恢复方法构成了挑战。谷歌安全研究人员在一份关于云安全威胁演变的报告中警告称,随着攻击者不断改进数据窃取、身份泄露和...
- ConceptDraw DIAGRAM:释放创意,绘就高效办公新未来
-
在当今数字化时代,可视化工具已成为提升工作效率和激发创意的关键。ConceptDrawDIAGRAM,作为一款世界顶级的商业绘图软件,凭借其强大的功能和用户友好的界面,正逐渐成为众多专业人士的首选绘...
- APP 制作界面设计教程:一步到位_app界面设计模板一套
-
想让APP界面设计高效落地,无需繁琐流程,掌握“框架搭建—细节填充—体验优化”三步法,即可一步到位完成专业级设计。黄金框架搭建是基础。采用“三三制布局”:将屏幕横向三等分,纵向保留三...
- MCP 的工作原理:关键组件_mcp部件
-
以下是MCP架构的关键组件:MCP主机:像ClaudeDesktop、GitHubCopilot或旅行助手这样的AI智能体,它们希望通过MCP协议访问工具、资源等。MCP主机会...
- 软件架构_软件架构师工资一般多少
-
软件架构师自身需要是程序员,并且必须一直坚持做一线程序员。软件架构应该是能力最强的一群程序员,他们通常会在自身承接编程任务的同时,逐渐引导整个团队向一个能够最大化生产力的系统设计方向前进。软件系统的架...
- 不知不觉将手机字体调大!老花眼是因为“老了吗”?
-
现在不管是联系、交友,还是购物,都离不开手机。中老年人使用手机的时间也在逐渐加长,刷抖音、看短视频、发朋友圈……看手机的同时,人们也不得不面对“视力危机”——老花眼,习惯眯眼看、凑近看、瞪眼看,不少人...
- 8000通用汉字学习系列讲座(第046讲)
-
[表声母字]加(续)[从声汉字]伽茄泇迦枷痂袈笳嘉驾架咖贺瘸(计14字)嘉[正音]标准音读jiā。[辨形]上下结构,十四画。会意形声字,从壴从加,加也表声。注:从壴,字义与鼓乐有关;从加,字义与...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- mfc框架 (52)
- abb框架断路器 (48)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- orm框架有哪些 (51)
- 知识框架图 (52)
- ppt框架 (55)
- 框架图模板 (59)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)