SOLID是什么,它是如何帮助我们写更好的代码的?
SOLID原则由以下5个概念组成:
简单来说,这些原则帮助我们创造更易维护,易于理解和灵活的软件。因此,随着我们应用的扩大,我们可以降低其复杂性,并在以后为我们省去很多麻烦。
像我们从字面意思看到的一样,该原则指出,一个类仅应承担一种责任。此外,它应该只有一个改变的理由(不能同时有很多理由来修改它)。
那么,这个原则是怎么帮助我们写更好的软件的?让我们先看一下它的优点:
下面我们以代码示例说明:
我们用Book类代表一本书,其属性包括:书名(name),作者(author)和内容(text)
/**
* 实体:书
*/
public class Book {
private String name;
private String author;
private String text;
/**
* 文本替换
* @param word
* @return
*/
public String replaceWordInText(String word){
return text.replaceAll(word, text);
}
/**
* 是否包含指定的文本
* @param word
* @return
*/
public boolean isWordInText(String word){
return text.contains(word);
}
}
现在我们的程序运行的很好,我们能存储任意的书的内容。但是我们无法将书的内容打印出来,无法阅读该怎么办?那么,我们就在新增一个打印内容的方法,如:
void printTextToConsole(){
// 输出文本
}
好的,现在已经可以实现打印内容了。但是,我们这样就违背了“单一职责”(书本身不与打印有什么关系)。为了解决我们的问题,需要实现一个单独的类,该类仅与打印书籍有关:
/**
* 书籍打印
*/
public class BookPrinter {
/**
* 打印到控制台
* @param text
*/
void printTextToConsole(String text){
// printing the text
}
/**
* 打印到其他媒介
* @param text
*/
void printTextToAnotherMedium(String text){
// do something
}
}
上面的类不仅与书籍本身解耦,而且能够实现通过各种媒介进行打印内容,本身又是一个职责单一的案例(打印)。
简单来说,一个类应对扩展是打开的,对修改是关闭的(open for extension, closed for modification)。这样一来,我们就可以避免修改现有代码,从而引入新的潜在的问题。当然有个特例,就是如果现有代码中存在已有bug,我们还是应该去解决它的。
比如现在有一个吉他,可以实现基本功能的弹奏:
/**
* 吉他
*/
public class Guitar {
private String make;
private String model;
private int volume;
}
但是用了一段时间后,觉得有点无聊想增加一些音节,让它用起来更加的摇滚。
我们如果直接在原有的Guitar类上修改,可能会把原有的功能破坏掉从而引入新的问题,所以根据“开闭原则”,我们应该在原有基础上进行扩展而不是修改:
/**
* 更酷炫的音节
*/
public class SuperCoolGuitarWithFlames extends Guitar {
private String flameColor;
}
通过扩展实现,我们可以保证现有的功能不会受到破坏。
这个原则字面意思较难理解,简单来说就是,如果一个类A是类B的子类,那么我们在不中断程序行为的情况下可以把B替换成A,而不影响程序原有的功能。
我们用代码示例说明:
/**
* 车
*/
public interface Car {
/**
* 打开引擎
*/
void turnOnEngine();
/**
* 加速
*/
void accelerate();
}
我们定义了一个接口,里面有两个方法,可以实现引擎打开和车加速功能。
下面来看下具体的实现类:
/**
* 摩托车
*/
public class MotorCar {
private Engine engine;
public void turnOnEngine() {
//turn on the engine!
engine.on();
}
public void accelerate() {
//move forward!
engine.powerOn(1000);
}
}
可以看出摩托车属于车的一种,实现了打开引擎和加速能力,我们继续看其他实现:
/**
* 电车
*/
public class ElectricCar {
public void turnOnEngine() {
throw new AssertionError("I don't have an engine!");
}
public void accelerate() {
//this acceleration is crazy!
}
}
可以看出,上面的电车虽然实现了Car,但是它没有引擎,所以不具有打开引擎的功能,那么这个就改变了程序的行为,违背了我们说的“里氏替换原则”。
简单来说,就是将大的接口切分为更小的接口,这样我们就可以确保实现类只需要关心它们感兴趣的方法。
举个例子,假如我们在动物园工作,具体是熊的“看护人”,那么可以这样定义:
public interface BearKeeper {
/**
* 给熊洗澡
*/
void washTheBear();
/**
* 给熊喂食
*/
void feedTheBear();
/**
* 抚摸熊
*/
void petTheBear();
}
我们可以开心的喂养熊,但是抚摸熊可能有危险,但是我们的接口定义的相当大,我们别无选择。那么,现在我们将接口拆分一下:
public interface BearCleaner {
void washTheBear();
}
public interface BearFeeder {
void feedTheBear();
}
public interface BearPetter {
void petTheBear();
}
通过拆分,我们就可以自由实现我们感兴趣的方法,
public class BearCarer implements BearCleaner, BearFeeder {
public void washTheBear() {
}
public void feedTheBear() {
}
}
我们可以将抚摸熊的“福利”给那么胆大或疯狂的人:
public class CrazyPerson implements BearPetter {
public void petTheBear() {
//Good luck with that!
}
}
这个原则主要是为了解耦。代替低级模块依赖高级模块,二者都应该依赖抽象。
举个例子,假如我们现有一台windows98电脑:
public class Windows98machine {}
但是没有显示器和键盘对我们来说有什么用呢?于是我们给这台电脑新增显示器和键盘:
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
上面代码运行的很好,我们的电脑已经实现了显示器和键盘,那么问题解决了吗?没有,我们又把这三个类紧密结合在一起了。这不仅使Windows98Machine难以测试,而且还失去了将StandardKeyboard替换为其他类的能力。
让我们把Keyboard抽象出来:
public interface Keyboard { }
public class Windows98Machine{
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
public class StandardKeyboard implements Keyboard { }
现在Windows98Machine就与StandardKeyboard解耦了,通过依赖反转(依赖抽象而不是具体类)完成了解耦。
在本文中我们深入研究了面向对象的SOLID原则,并通过代码示例说明其原理和实现。深入设计原则-SOLID