开端的夸姣
没有前史包袱,就没有压力,便是夸姣的。
假定项目发动了这样一个事务——造车:出产一辆小轿车(Car
),分别在不同的零件车间(车架(Sheel
)、发动机(Engine
)、车轮(Wheel
))装置相应的零件,一切零件装置完结后回到提车车间就能够提车。
这样的需求开发起来很简略:
- 数据实体
data class Car(
var shell: Shell? = null,
var engine: Engine? = null,
var wheel: Wheel? = null,
) : Serializable {
override fun toString(): String {
return "Car: Shell(${shell}), Engine(${engine}), Wheel(${wheel})"
}
}
data class Shell(
...
) : Serializable
data class Engine(
...
) : Serializable
data class Wheel(
...
) : Serializable
- 零件车间(以车架为例)
class ShellFactoryActivity : AppCompatActivity() {
private lateinit var btn: Button
private lateinit var back: Button
private lateinit var status: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_shell_factory)
val car = intent.getSerializableExtra("car") as Car
status = findViewById(R.id.status)
btn = findViewById(R.id.btn)
btn.setOnClickListener {
car.shell = Shell(
id = 1,
name = "比亚迪车架",
type = 1
)
status.text = car.toString()
}
back = findViewById(R.id.back)
back.setOnClickListener {
setResult(RESULT_OK, intent.apply {
putExtra("car", car)
})
finish()
}
}
}
class EngineFactoryActivity : AppCompatActivity() {
// 和装置车架流程相同
}
class WheelFactoryActivity : AppCompatActivity() {
// 和装置车架流程相同
}
- 提车车间
class MainActivity : AppCompatActivity() {
private var car: Car? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
car = Car()
refreshStatus()
findViewById<Button>(R.id.shell).setOnClickListener {
val it = Intent(this, ShellFactoryActivity::class.java)
it.putExtra("car", car)
startActivityForResult(it, REQUEST_SHELL)
}
findViewById<Button>(R.id.engine).setOnClickListener {
val it = Intent(this, EngineFactoryActivity::class.java)
it.putExtra("car", car)
startActivityForResult(it, REQUEST_ENGINE)
}
findViewById<Button>(R.id.wheel).setOnClickListener {
val it = Intent(this, WheelFactoryActivity::class.java)
it.putExtra("car", car)
startActivityForResult(it, REQUEST_WHEEL)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK) return
when (requestCode) {
REQUEST_SHELL -> {
Log.i(TAG, "装置车架完结")
car = data?.getSerializableExtra("car") as Car
}
REQUEST_ENGINE -> {
Log.i(TAG, "装置发动机完结")
car = data?.getSerializableExtra("car") as Car
}
REQUEST_WHEEL -> {
Log.i(TAG, "装置车轮完结")
car = data?.getSerializableExtra("car") as Car
}
}
refreshStatus()
}
private fun refreshStatus() {
findViewById<TextView>(R.id.status).text = car?.toString()
findViewById<Button>(R.id.save).run {
isEnabled = car?.shell != null && car?.engine != null && car?.wheel != null
setOnClickListener {
Toast.makeText(this@MainActivity, "提车咯!", Toast.LENGTH_SHORT).show()
}
}
}
companion object {
private const val TAG = "MainActivity"
const val REQUEST_SHELL = 1
const val REQUEST_ENGINE = 2
const val REQUEST_WHEEL = 3
}
}
即便是初学者也能看出来,事务完结起来很简略,通过Activity
的startActivityForResult
就能跳转到相应的零件车间,装置好零件回到提车车间就完事了。
开端迭代
往往事务的第一个版别便是这么简略,感觉也没什么好重构的。
可是事务难免会进行迭代。比方事务迭代到1.1版别:客户想要给轿车装上行车电脑,而装置行车电脑不需求跳转到另一个车间,而是在提车车间操作,可是需求很长的时刻。
看起来也简略,新增一个Computer
实体类和ComputerFactoryHelper
:
object ComputerFactoryHelper {
fun provideComputer(block: Computer.() -> Unit) {
Thread.sleep(5_000)
block(Computer())
}
}
data class Computer(
val id: Int = 1,
val name: String = "行车电脑",
val cpu: String = "麒麟90000"
) : Serializable {
override fun toString(): String {
return "$name-$cpu"
}
}
再在提车车间新增按钮和逻辑代码:
findViewById<Button>(R.id.computer).setOnClickListener {
object : Thread() {
override fun run() {
ComputerFactoryHelper.provideComputer {
car?.computer = this
runOnUiThread { refreshStatus() }
}
}
}.start()
}
现在看起来也没啥难的,那是因为咱们模拟的事务场景足够简略,可是相信许多实际项目的屎山代码,便是通过这样的事务迭代,一点一点地堆积而成的。
从迭代到溃散
咱们来采访一下最近被一个小小的事务迭代需求搞溃散的Android开发——小王。
记者:小王你好,听说最近你Emo了,甚至多次萌生了就地辞去职务的想法?
小王:最近AI不是很火吗,产品给我提了一个需求,在上传音乐时能够选择在后端生成一个AI视频,然后一起上传。
记者:哦?这不是一个小需求吗?
小王:可是我翻开现在上传事务的代码就傻了啊!就说Activity吧,有:BasePublishActivity,BasePublishFinallyActivity,SinglePublishMusicActivity,MultiPublishMusicActivity,PublishFinallyActivity,PublishCutMusicFinallyActivity, Publish(好多好多)FinallyActivity… 当然,这只是冰山一角。再说上传流程。假如只上传一首音乐,需求先调一个接口
/sts
拿到一个Oss Token,再调用第三方的Oss库上传文件,拿到一个url,然后再把这个url和其他的信息(标题、标签等)组成一个HashMap,再调用一个接口/save
提交到后端,相当于调3个接口… 假如要批量上传N个音乐,就要调3 * N个接口,假如还要给每个音乐配M个图片,就要调3 * N+3 * N * M个接口… 假如上传一个音乐配一个本地视频,就要调3 * 2 * N个接口,而且,上传视频流程还不相同的是,需求在调用/save
接口之后再调用第三方Oss上传视频文件…再说数据类。上面说到上传过程中需求增加图片、视频、活动类型啥的,代码里封装了一个EditInfo类足足有30个特点!,由于是Java代码而且完结了Parcelable
接口,光一个Data类就有400多行!你认为这就完了?EditInfo需求在上传时转成PublishInfo类,PublishInfo还能够转成PublishDraft,PublishDraft能够保存到数据库中,从数据库中能够读取PublishDraft然后转成EditInfo再从头编辑…记者:(感觉小王精神状态有点问题,于是掐掉了直播画面)
相信小王的这种情况,许多事务开发同学都经历过吧。回头再看一下前面的造车事务,其实和小王的上传事务相同,便是一开端很简略,迭代个7、8个版别就开端陷入一种窘境:即便迭代需求再小,开发起来都很困难。
高雅地迭代事务代码?
假如咱们想要高雅地迭代事务代码,应该怎样做呢?
小王遇到的这座屎山,咱们现在就不要去碰了,先就从前面说到的造车事务开端吧。
许多同学会想到重构,俺也相同。接下来,我就要讨论一下如何高雅而安全地重构既有事务代码。
先抛出一个观点:对于程序员来说,想要保持“高雅”,最重要的质量便是笼统。
❓ 这时可能有同学就要反驳我了:过早的笼统没有必要。
❗ 别急,我现在要说的笼统,并不是代码层面的笼统,而是对事务的笼统,甚至对技能思想的笼统。
什么是代码层面的笼统?比方刚刚的Shell/Engine/WheelFactoryActivity
,其实是能够笼统为BaseFactoryActivity
,然后通过完结其中的零件类型就行了。但我不会主张你这么做,为啥?看看小王方才的疯言疯语就明白了。各个XxxFactoryActivity
看着差不多,但在实际项目中很可能会开枝散叶,各自迭代出不同的事务细节。到那时,项目里便是各种BaseXxxActivity
,XxxV1Activity
,XxxV2Activity
…
那什么又是事务的笼统?直接上代码:
interface CarFactory {
val factory: suspend Car.() -> Car
}
造车事务,无论在哪个环节,都是在Car
上装置零件(或者任何出其不意的操作),然后产生一个新的Car
;别的,这个环节无论是跳转到零件车间装置零件,还是在提车车间里装置行车电脑,都是耗时操作,所以需求加suspend
关键词。
❓ 这时可能有同学说:害!你这和BaseFactoryActivity
有啥差异,不便是把笼统类换成接口了吗?
❗ 别急,我并没有要让XxxFactoryActivity
去继承CarFactory
啊,想想小王吧,这个XxxFactoryActivity
就相当于他的同事在两年前写的代码,小王肯定打死都不会想去修正这儿面的代码的。
Computer
是新事务,咱们只改它。首要咱们根据这个接口把ComputerFactoryHelper
改一下:
object ComputerFactoryHelper : CarFactory {
private suspend fun provideComputer(block: Computer.() -> Unit) {
delay(5_000)
block(Computer())
}
override val factory: suspend Car.() -> Car = {
provideComputer {
computer = this
}
this
}
}
那么,在提车车间就能够这样改:
private var computerFactory: CarFactory = ComputerFactoryHelper
findViewById<Button>(R.id.computer).setOnClickListener {
lifecycleScope.launchWhenResumed {
computerFactory.factory.invoke(car)
refreshStatus()
}
}
❓ 那么XxxFactoryActivity
相关的流程又应该怎样重构呢?
Emo时刻
我先反诘一下咱们,为啥咱们Android程序员总是盯着Activity
不放呢?
我再反诘一下咱们,为啥咱们Android程序员总是盯着Activity
不放呢?
我再再反诘一下咱们,为啥咱们Android程序员总是盯着Activity
不放呢?
甚至,许多人即便学了Compose
和Flutter
,依然对Activity
心心念念。
当你在一千个日日夜夜里,重复地写着
XxxActivity
,写onCreate/onResume/onDestroy
,写startActivityForResult/onActivityResult
时,当你每次想要换工作,翻开面经背诵Activity
的发动形式,生命周期,AMS
原理时,可曾对Activity
有过厌恶,可曾思考过编程的含义?你也曾努力查阅
Activity
的源码,学习MVP
/MVVM
/MVI
架构,企图让你的Activity
保持清洁,但无论你怎样努力,却一向活在Activity
的暗影之下。你有没有想过,咱们正在被
Activity
PUA?
说实话,作为一名INFP,本人不是很适合做程序员。比较技能栈的丰厚和技能原理的深化,我更看重的是写代码的感触。假如写代码都能被PUA,那我还怎样愉快的写代码?
当我Emo了很久之后,我意识到了,我一向在被代码PUA,不光是同事的代码,也有自己的代码,甚至有Android结构,以及外国大佬不断推出的各种新技能新结构。
对对对!你们都没有问题,是我太菜了555555555
高雅回身
Emo过后,还是得回到残酷的职场啊!可是咱们要高雅地回身回来!
❓ 方才不是说要处理XxxFactoryActivity
相关事务吗?
❗ 这时我就要说到别的一种笼统:技能思想的笼统!
Activity?F*ck off!
Activity
的跳转返回啥的,也无非便是一次耗时操作嘛,咱们也应该将它笼统为CarFactory
,便是这个东东:
interface CarFactory {
val factory: suspend Car.() -> Car
}
根据这个信仰,我从回忆中想到这么一个东东:ActivityResultLauncher
。
说实话,我以前都没用过这玩意儿,可是我这时好像抓到了救命稻草。
随意搜了个教程并谢谢他,参阅这篇博客,咱们能够把startActivityForResult
和onActivityResult
这套流程,封装成一次异步调用。
open class BaseActivity : AppCompatActivity() {
private lateinit var startActivityForResultLauncher: StartActivityForResultLauncher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityForResultLauncher = StartActivityForResultLauncher(this)
}
fun startActivityForResult(
intent: Intent,
callback: (resultCode: Int, data: Intent?) -> Unit
) {
startActivityForResultLauncher.launch(intent) {
callback.invoke(it.resultCode, it.data)
}
}
}
让MainActivity
继承BaseActivity
,就能够绕过Activity
了,后边的事情就简略了。只需咱们了解过协程,就能轻易想到异步转同步这一一般操作:suspendCoroutine
。
于是,咱们就能够在不修正XxxFactoryActivity
的情况下,写出根据CarFactory
的代码了。还是以车架车间为例:
class ShellFactoryHelper(private val activity: BaseActivity) : CarFactory {
override val factory: suspend Car.() -> Car = {
suspendCoroutine { continuation ->
val it = Intent(activity, ShellFactoryActivity::class.java)
it.putExtra("car", this)
activity.startActivityForResult(it) { resultCode, data ->
(data?.getSerializableExtra("car") as? Car)?.let {
Log.i(TAG, "装置车壳完结")
shell = it.shell
continuation.resumeWith(Result.success(this))
}
}
}
}
}
然后在提车车间,和Computer
事务同样的运用方法:
private var shellFactory: CarFactory = ShellFactoryHelper(this)
findViewById<Button>(R.id.shell).setOnClickListener {
lifecycleScope.launchWhenResumed {
shellFactory.factory.invoke(car)
refreshStatus()
}
}
最终,在咱们的提车车间,依靠的便是一些CarFactory
,一切的事务操作都是笼统的。抵达这个阶段,相信咱们都有了自己的一些想法了(比方保护一个carFactoryList
,用Hilt
管理CarFactory
依靠,泛型封装等),想要继续怎样重构/保护,就全看自己的实际情况了。
class MainActivity : BaseActivity() {
private var car: Car = Car()
private var computerFactory: CarFactory = ComputerFactoryHelper
private var engineFactory: CarFactory = EngineFactoryHelper(this)
private var shellFactory: CarFactory = ShellFactoryHelper(this)
private var wheelFactory: CarFactory = WheelFactoryHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
refreshStatus()
findViewById<Button>(R.id.shell).setOnClickListener {
lifecycleScope.launchWhenResumed {
shellFactory.factory.invoke(car)
refreshStatus()
}
}
findViewById<Button>(R.id.engine).setOnClickListener {
lifecycleScope.launchWhenResumed {
engineFactory.factory.invoke(car)
refreshStatus()
}
}
findViewById<Button>(R.id.wheel).setOnClickListener {
lifecycleScope.launchWhenResumed {
wheelFactory.factory.invoke(car)
refreshStatus()
}
}
findViewById<Button>(R.id.computer).setOnClickListener {
lifecycleScope.launchWhenResumed {
Toast.makeText(this@MainActivity, "稍等一会儿", Toast.LENGTH_LONG).show()
computerFactory.factory.invoke(car)
Toast.makeText(this@MainActivity, "装好了!", Toast.LENGTH_LONG).show()
refreshStatus()
}
}
}
private fun refreshStatus() {
findViewById<TextView>(R.id.status).text = car.toString()
findViewById<Button>(R.id.save).run {
isEnabled = car.shell != null && car.engine != null && car.wheel != null && car.computer != null
setOnClickListener {
Toast.makeText(this@MainActivity, "提车咯!", Toast.LENGTH_SHORT).show()
}
}
}
}
总结
- 笼统是程序员保持高雅的最重要才能。
- 笼统不该限制在代码层面,而是要上升到事务,甚至技能思想上。
- 有意识地对代码PUA说:No!
- 学习新技能时,不该只学会调用,也不该迷失在技能原理上,更重要的是花哨的技能和朴实的编程思想之间的化学反应。