设计原则是设计模式中重要的指导。设计原则往往比设计模式更重要,真是因为设计原则的存在,才指导完成设计模式。所有的设计模式都是通过设计原则推导出来的,如果某一个设计模式违背了设计原则,那么这个模式一定是有问题的。

依赖倒置原则 (DIP)

高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。

抽象(稳定)不应该依赖于实现细节(变化),实现细节(变化)应该依赖于抽象(稳定)。

例如:

class A 依赖 class B ,此时的 class A 为高层模块,class B 为低层模块。如果要进行修改使 class A 依赖 class C , 那么必须要修改 class A 的源码。此时,就发生了高层模块依赖低层模块。如果使用一个公共的接口 interface D 抽象 class Bclass C , 那么 class A 就可以依赖 interface D ,高层模块不依赖于低层模块。如果使 class A 也抽象一个抽象接口 interface IA,那么,只需要了解 interface IAinterface D 之间的关系即可,无需关心实现细节。此刻,便完成了高层模块和低层模块都依赖于接口,同时也完成了抽象不依赖于实现细节,实现细节依赖于抽象

我们采用抽象接口定义好规则规范,而不需要关系具体实现,提高了设计的稳定性。

我们定义了两个类,一个为 Movie 另一个为 Television,通过 Television.play() 方法进行内容播放。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Movie {
    fun show() {
        println("这是电影")
    }
}

class Television {

    fun play(movie: Movie) {
        movie.show()
    }

}

fun main() {
    val television = Television()
    val movie = Movie()
    television.play(movie) // 这是电影
}

目前来看没有问题,那么如果现在电视剧要播放动画呢?我们需要重新建立一个类 Cartoon,并且修改 play() 方法 和 main 函数。

此刻说明在设计上有问题,我们的高层模块(Television)依赖底层模块(Movie)。

如果先将 MovieCartoon 进行抽象,使 Television 不去依赖具体实现,而去依赖接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface ITVContent {
    fun show()
}

class Movie : ITVContent {
    override fun show() {
        println("这是电影")
    }
}

class Cartoon : ITVContent {
    override fun show() {
        println("这是卡通")
    }
}

class Television {

    fun play(tv: ITVContent) {
        tv.show()
    }

}

fun main() {
    val television = Television()
    val cartoon = Cartoon()
    television.play(cartoon)
}

经过修改后,Television不在依赖任何一个具体的类,而是依赖他们的抽象接口,以后无论增加播放什么,例如添加一个广告 Ad.class 只需要添加新的类,并且实现 ITVContent 接口,就可以完成新的播放内容的更换。

如果要更换播放平台呢?不在电视上播放,而在电影院进行播放。

我们只需要添加一个 IPlayer 的接口,并且有未实现的 paly 方法。让 TelevisionCinema 去实现即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface IPlayer {
    fun play(content: ITVContent)
}

class Cinema : IPlayer {
    override fun play(content: ITVContent) {
        content.show()
    }
}

class Television : IPlayer {
    override fun play(content: ITVContent) {
        content.show()
    }
}

fun main() {
    val television = Television()
    val cinema = Cinema()

    val cartoon = Cartoon()
    television.play(cartoon)

    val movie = Movie()
    cinema.play(movie)
}

经过简单的修改,无论是 Television 还是 Cinema 都不在依赖具体的类,而是依赖抽象的接口,以后无论扩展什么播放方法和播放内容,都不需要对之前的类进行修改。同时也不存在抽象的依赖具体实现,而是具体实现全部依赖于抽象。完成了解耦问题,将变换进行了隔离。

开闭原则 (OCP)

对扩展开发,对修改封闭,类模块应该是可以扩展的,但是不可以修改。

什么叫做修改,什么叫做扩展?修改是指对已有的源代码进行修改,而扩展是对当前没有的类或者方法进行添加。

对于上述示例,第一次代码还没有进行修改的时候,代码就进行了修改,每次修改必须对源码进行修改来实现新的功能。最终的代码版本,进行新功能的添加只需要扩展新的类即可,无需对已有的代码进行修改。这样便实现了开闭原则。

单一职责原则(SRP)

不要存在多于一个导致类变更的原因,一个类只负责一项原则。每个类负责的职责不同,进行修改的时候不会对其他类造成影响或者改变。

里氏 Liskov 替换原则(LSP)

子类必须能够替换它们的基类,继承表达类型抽象。LSP 是继承复用的基石,只有子类可以替换父类,并且之前功能不受到影响时,父类才能真正的被复用。是对开闭原则的补充。

接口隔离原则(ISP)

类之间的依赖关系应该建立在最小的接口上。建立单一的接口,不要建立庞大臃肿的接口,接口中的方法尽量少,而不是去建立一个庞大的接口供依赖去使用。

其他原则

优先使用对象组合,而不是继承

继承表达的一种类属关系,例如 大熊猫🐼继承动物,动物继承于生物,这是表达了一种类属关系(is-a)。

继承在某种程度上破坏了封装性,子类父类耦合度较高。

例如子类无法继承多个父类,父类中的方法子类无条件拥有,从父类继承的方法不能在运行时进行改变。这些都时继承所带来的缺点或者不足。

而对象组合只要求被组合的对象具有良好的定义接口。

针对接口编程,而不是针对实现编程

不要将变量类型声明为具体某个类型,而是声明为接口;客服端无需知道对象的具体类型,只需要知道对象的接口;减少系统中各部分的依赖关系,从而实现“高内聚,低耦合”的设计。

接口标准化是软件开发的重要举措,面向接口设计可以使工程分工,代码解耦。


不同的设计模型对设计原则有着不同的依赖,设计原则指导的设计模式的进行。