依赖倒置原则

One should depend upon abstractions, not on the concrete implementation.

一个应该依靠抽象,而不是具体的实现。

In a typical system, we have some higher-level, middle-level, and lower-level modules. If the system depends on the concrete implementation, then the higher-level module will depend on the middle-level modules. The middle-level module will depend on several lower-level modules. Control also flows in the same way.

在典型的系统中,我们有一些较高级别,中级和较低级别的模块。 如果系统依赖于具体的实现,则更高级别的模块将依赖于中间级别的模块。 中级模块将依赖于几个低级模块。 控制也以相同的方式流动。

Image for post

Now any change in the lower-level module will force the middle and higher-level modules to recompile as they are directly dependant on the lower-level module. In other words, a simple change in details will force the higher-level policy to change. This is fundamentally incorrect. Our higher-level policy should be immune to any changes in details that can happen in a lower-level module. To protect our higher-level policy from these changes we have to provide the abstraction in places where these changes can happen most.

现在,低级模块的任何更改都将迫使中级和高级模块重新编译,因为它们直接依赖于低级模块。 换句话说,细节上的简单更改将迫使更高级别的策略进行更改。 从根本上讲这是不正确的。 我们的高级策略应不受较低级别模块中可能发生的任何细节更改的影响。 为了保护我们的高层策略不受这些更改的影响,我们必须在这些更改最可能发生的地方提供抽象。

Let’s consider a typical example of a terms and conditions screen. The user will be presented with terms and conditions, he has two buttons to accept and deny it. We have to save the user’s selection inside the local storage.

让我们考虑一个条款和条件屏幕的典型示例。 将向用户显示条款和条件,他有两个按钮可以接受和拒绝。 我们必须将用户的选择保存在本地存储中。

class TermsAndConditionsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_terms_and_conditions)


        val tvTermsAndConditions = findViewById<TextView>(R.id.tvTermsAndConditions)
        tvTermsAndConditions.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas quis tellus quis erat rhoncus tempus sed sed ipsum. Aliquam ac ornare libero, a sodales ligula."


        val btnAccept = findViewById<Button>(R.id.btnAccept)
        btnAccept.setOnClickListener {
            val sharedPreference = getSharedPreferences("UserPreference", Context.MODE_PRIVATE)
            val editor = sharedPreference.edit()
            editor.putBoolean("terms_and_condition_accepted", true)
            editor.commit()
        }


        val btnDeny = findViewById<Button>(R.id.btnDeny)
        btnDeny.setOnClickListener {
            val sharedPreference = getSharedPreferences("UserPreference", Context.MODE_PRIVATE)
            val editor = sharedPreference.edit()
            editor.putBoolean("terms_and_condition_accepted", false)
            editor.commit()
        }
    }
}

Now the product team decides that instead of storing data on local storage, it should be sent to the server. Making these changes will force the entire codebase to recompile. Which implies that there is rigidity in code. Separation of concerns is also not followed here.

现在,产品团队决定将数据发送到服务器,而不是将数据存储在本地存储中。 进行这些更改将迫使整个代码库重新编译。 这意味着代码具有刚性。 这里也不遵循关注点分离。

Let’s divide this class into two different modules. The first module will deal with UI, the second will for managing data storage.

让我们将该类分为两个不同的模块。 第一个模块将处理UI,第二个模块将管理数据存储。

class TermsAndConditionsActivity : AppCompatActivity() {
    private lateinit var termsAndConditionsPresenter: TermsAndConditionsPresenter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_terms_and_conditions)


        termsAndConditionsPresenter = TermsAndConditionsPresenter(this)


        val btnAccept = findViewById<Button>(R.id.btnAccept)
        btnAccept.setOnClickListener {
            termsAndConditionsPresenter.onAccept()
        }


        val btnDeny = findViewById<Button>(R.id.btnDeny)
        btnDeny.setOnClickListener {
            termsAndConditionsPresenter.onDeny()
        }
    }


    fun showTermsAndCondition(termsAndConditions: String) {
        val tvTermsAndConditions = findViewById<TextView>(R.id.tvTermsAndConditions)
        tvTermsAndConditions.text = termsAndConditions
    }


    override fun onDestroy() {
        termsAndConditionsPresenter.onDestroy()
        super.onDestroy()
    }
}
class TermsAndConditionsPresenter(
    private var termsAndConditionsActivity: TermsAndConditionsActivity?,
    private val termsAndConditionsRepository: TermsAndConditionsRepository
) {


    init {
        termsAndConditionsActivity?.showTermsAndCondition("Lorem ipsume")
    }


    fun onAccept() {
        termsAndConditionsRepository.updateTermsAndConditionPreference(true)
    }


    fun onDeny() {
        termsAndConditionsRepository.updateTermsAndConditionPreference(false)
    }


    fun onDestroy() {
        termsAndConditionsActivity = null
    }
}

These two will be part of our UI module. Now from the TermsAndConditionsPresenter, if we directly access the concrete implementation of TermsAndConditionsRepository class it will create a direct dependency. Any changes made in TermsAndConditionsRepository will force TermsAndConditionsPresenter and TermsAndConditionsActivity to recompile.

这两个将成为我们UI模块的一部分。 现在从TermsAndConditionsPresenter,如果我们直接访问TermsAndConditionsRepository类的具体实现,它将创建一个直接依赖项。 对TermsAndConditionsRepository进行的任何更改都将强制重新编译TermsAndConditionsPresenter和TermsAndConditionsActivity。

Instead of doing it, I will create an interface TermsAndConditionsRepository and add it to the UI module.

而不是这样做,我将创建一个TerminalAndConditionsRepository接口并将其添加到UI模块。

interface TermsAndConditionsRepository {
    fun updateTermsAndConditionPreference(accepted: Boolean)
}

Inside the second module, we will have the implementation of this interface.

在第二个模块内部,我们将实现此接口。

class TermsAndConditionsLocalRepository(context: Context): TermsAndConditionsRepository {
    private val sharedPreference = context.getSharedPreferences("UserPreference", Context.MODE_PRIVATE)




    override fun updateTermsAndConditionPreference(accepted: Boolean) {
        with(sharedPreference.edit()) {
            putBoolean(TERMS_AND_CONDITION_KEY, accepted)
            commit()
        }
    }


    companion object {
        private const val TERMS_AND_CONDITION_KEY = "terms_and_condition_preference"
    }
}

As we have added an interface between TermsAndConditionsPresenter and TermsAndConditionsRemoteRepository, we have reversed the direction of dependency. Now our data management module depends on the UI module. Any changes in the data management module won't force any of the other modules to recompile. Now we can easily change the storage of data from the local storage to remote server and the rest of the code will not know about it.

当我们在TermsAndConditionsPresenter和TermsAndConditionsRemoteRepository之间添加了一个接口时,我们已经颠倒了依赖的方向。 现在,我们的数据管理模块依赖于UI模块。 数据管理模块中的任何更改都不会强制其他任何模块重新编译。 现在,我们可以轻松地将数据存储从本地存储更改为远程服务器,其余代码将不知道。

class TermsAndConditionsRemoteRepository(private val termsAndConditionsAPI: TermsAndConditionsAPI) :
    TermsAndConditionsRepository {
    override fun updateTermsAndConditionPreference(accepted: Boolean) {
        termsAndConditionsAPI.updateTermsAndConditionPreference(accepted)
    }
}

The second hardest thing in software programming after the naming is managing the dependencies. If we learn to manage them properly most of the issues we face in day to day life will get resolved. We should use abstraction in places where the probability of change is high or we are dealing with code on which we don’t have any control e.g. libraries. This will protect us from future changes, thus make our code more maintainable.

在命名之后,软件编程中第二难的事情是管理依赖项。 如果我们学会适当地管理它们,我们在日常生活中面临的大多数问题都将得到解决。 我们应该在发生变化的可能性很高或正在处理没有任何控件的代码的地方使用抽象,例如库。 这将保护我们免受将来的更改,从而使我们的代码更具可维护性。

翻译自: https://proandroiddev.com/dependency-inversion-principle-4cde793c5bb3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值