六大设计原则:依赖倒置原则

2,324 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

依赖倒置原则,学完后发现自己项目遵守这种原则的代码还是很多的,这说明了总结出来的六大设计原则肯定不是说为了显示自己设计高深莫测的样子,而是让我们在学完是能在项目中更好的设计。

定义

依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体的实现。

也可以说:高层模块不应该依赖底层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖抽象。

虽然可以看起来绕口一点,说白了对于java来言就是要面向接口编程,而不是面向实现编程,对于swift来说,也就是面向协议编程。

重点

依赖倒置原则的目的是通过面向接口的编程来降低类间的耦合性。那我们怎么去面向接口编程呢?

在软件设计中,细节是具有多变性,而抽象是相对稳定的,接口就是一种抽象,接口的实现就是细节,我们随意调用接口,而对于接口的内部实现是不用关心的,因为接口实现是一种具体操作,只有把这任务交给实现类去完成就好。

那我们应该怎么去做呢

  1. 我们每个类都尽量提供接口或抽象类,或者两者都具备。

  2. 变量的声明类型尽量是接口或者抽象类。

  3. 任何类都不应该从具体类派生。

  4. 使用继承时尽量遵循里氏替换原则。

优点

  • 降低类间的耦合性,依赖通过抽象发生,就能实现类之间不直接发生依赖关系
  • 提高系统的稳定性,只要接口是稳定的,任何一个更改都不用担心其它受到影响
  • 减少并行开发引起的风险
  • 提高代码的可读性和维护性

例子

就拿我国庆去玩订酒店作个例子,这次的例子比较简单,就是面向协议编程的简单运用。

今年国庆刚好去韶关的丹霞山玩,去丹霞山呢门票有效期是48个小时的,所以我作为旅客就决定在那里住一晚。既然去玩,那肯定得住的舒服一点,所以我就直接预订丹霞山星级酒店来住一晚。

需求也算是明确的,那我们就弄一个StarHotel的类,方法是live来居住。

/// 星级酒店
class StarHotel {
    func live() {
        print("StarHotel live")
    }
}

旅客就弄一个Traveler的类,预订就用方法为booking。

/// 旅客
class Traveler {
    func booking(starHotel: StarHotel) {
        starHotel.live()
    }
}

好,我们输出一下代码

var traveler = Traveler()
var starHotel = StarHotel()
traveler.booking(starHotel)

输出结果是:

StarHotel live

这显然没有问题。可是当要预订的时候,发现星级酒店是在丹霞山入口售票处外面,进去里面还有一段很长的路才到景区景点入口,景区里面有2个村,村里面还有不少民宿客栈。民宿客栈入住的话,第二天早上一早去看日出或者去景点入口就方便很多的,而且里面有个大停车场,吃的东西也不少。

这么一想,我觉得我还是订名宿客栈好了。毕竟方便很多。虽然平时100的房,变成400,也就4倍而已是吧。

需求变更了,那我们就弄Homestay类为民宿,也写一个live的方法。

/// 名宿
class Homestay {
    func live() {
        print("Homestay live")
    }
}

作为旅客,也添加一个booking民宿的方法。

/// 旅客
class Traveler {
    func booking(_ starHotel: StarHotel) {
        starHotel.live()
    }
    
    func booking(_ homeStey: Homestay) {
        homeStey.live()
    }
}

调用代码:

var traveler = Traveler()
var starHotel = StarHotel()
var homestay = Homestay()
traveler.booking(starHotel)
traveler.booking(homestay)

调用结果:

StarHotel live

Homestay live

可以看到,我们名宿也可以居住了。功能是可以实现了,但这里提出一个疑问,如果又有其它类型酒店可以提供,我们是不是又得在旅客Traveler这个类,再添加一个酒店的预订方法。显然这样我们就违背了**开闭原则**的实现。

而且我们耦合性太强了,旅客类Traveler和星级酒店StarHotel,名宿Homestay都有相互依赖,这完全就是面向实现编程了。

既然我们学了依赖倒置原则,那我们就尽可能使用面向协议编程。让细节依赖抽象。

这里我们先弄一个协议,也可以说是抽象。HotelProtocol协议,里面有居住live的协议方法。

protocol HotelProtocol {
    /// 居住
    func live()
}

不管是民宿也好,星级酒店也好,都遵循这个协议。

/// 名宿
class Homestay: HotelProtocol {
    func live() {
        print("Homestay live")
    }
}


/// 星级酒店
class StarHotel: HotelProtocol {
    func live() {
        print("StarHotel live")
    }
}

我们的旅客类Traveler,就预订这个酒店的协议HotelProtocol就好。

/// 旅客
class Traveler {
    
    func booking(_ hotel: HotelProtocol) {
        hotel.live()
    }
}

这样子的好处是什么呢,我们完全不用管实现方法要居住的是哪个酒店,只要是遵循这个协议的酒店,我们作为旅客,都可以预订。

输出调用代码:

var traveler = Traveler()
var starHotel = StarHotel()
var homestay = Homestay()
traveler.booking(starHotel)
traveler.booking(homestay)

输出结果:

StarHotel live

Homestay live

现在我们来捋一捋他们的关系,Traveler类依赖于协议HotelProtocol,而StarHotol和Homestay是对协议HotelProtocol的实现。这样一来耦合性是不是就大大降低了呢,而且再有什么酒店,遵循这个协议,怎么写都不会影响到Traveler。

总结

很多时候我们遵守一个原则,基本上也能做到遵守到其它原则,每个酒店都有自己的live方法,是不是就满足单一职责原则开闭原则的实现就是依赖倒置原则,如果把HotelProtocol换成Hotel的父类,也就是抽象类,是不是也能满足里氏替换原则

还想了解其它的原则吗?它们之间又什么共同点 和不同点?

欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章