(1)单一职责原则(SRP)
一个类应该只有一个发生变化的原因.这是一种对复杂类的分割思想。
看一个简单的代码
class Animal{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("猪");
}
}
如果哪天要加上“鱼呼吸水”的功能。可以改成如下的样子
class Terrestrial{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
class Aquatic{
public void breathe(String animal){
System.out.println(animal+"呼吸水");
}
}
public class Client{
public static void main(String[] args){
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe("牛");
terrestrial.breathe("羊");
terrestrial.breathe("猪");
Aquatic aquatic = new Aquatic();
aquatic.breathe("鱼");
}
}
这便符合单一职责原则。每个类分别只做了自己的事。将来,无论是animal还是aquatic的需求发生变化,只要各自更改自己的类就行了。不过,显然,如果代码量小到一目了然,这样做是完全没有必要的。
(2)开放封闭原则
这是一种抽象化应对扩展的思想。
一个软件实体应该对扩展开放,对修改关闭。就是通过抽象化的方法,在增加系统的扩展时,不必修改源代码。
其中,chartDisplay中display方法为
......
if (type.equals("pie")) {
PieChart chart = new PieChart();
chart.display();
}
else if (type.equals("bar")) {
BarChart chart = new BarChart();
chart.display();
}
......
如果要扩展一种新图,势必要修改chartDisplay中的内容。
改为
(3)里氏替换原则
里氏原则通俗的来讲,就是子类可以扩展父类的功能,但是不能改变父类原有的功能。
显然,子类增加自己的方法或者实现父类的抽象方法都不会违反里氏替换原则。唯一的问题出在方法的重写上(override)。显然,子类所重写的方法应该具有输入要求更严格,逻辑一致,输出要求更宽松的特征。
里氏替换原则有助于增加代码的正确性。即在代码中体现子类是父类的特化。另外一个是让系统更具有扩展性,同时减少代码重复。
改为
这样,显然,符合里氏替换原则的系统,可以更灵活的使用基类对象的引用。
(4)依赖倒置原则
面向接口编程的思想
我们编程序的时候,复杂的业务逻辑实现类往往要依赖很多类,起到一个中枢的功能。但是,如果要改变这种依赖关系,就必须要修改这个类。修改业务逻辑复杂的类显然不是我们愿意看到的。
解决的方案是面向接口编程的思想。将类A改为依赖接口I。而B,C分别实现I.这样,就必要改变A类的源代码了。
class Book{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
class Mother{
public void narrate(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
}
}
改为
class Newspaper implements IReader {
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}
为什么叫做依赖倒置呢?在传统编程中,高层次的模块需要依赖低层模块,因为它实现了复杂逻辑和依赖于低层次的类。而采用面向接口编程的思想后,不是低层次如何实现决定后才能决定高层次如何封装的问题,而是高层次实现决定后,给出接口规范,低层次才能开工。实际上,面向接口编程后,只要接口被规定,高层次和低层次可以同时开工,互不干扰。
(5)接口隔离原则
代码中尽可能减少不必要的逻辑
应该使用多个专门的接口,而不是使用单一的总接口。
比如一个设计的实例如下
看似代码变简单了,因为接口内部的函数多了,总接口数变少了。但是,每一个接口中的函数都需要实现。这样,在实现类中增加了很多不必要的代码,在调用类中也会引起不必要的混淆。所以,改变后的系统架构如下
(6)合成复用原则
因为依赖注入的可能,组合的调整要比继承的调整容易的多
尽量使用对象的组合,而不是通过继承来达到复用的目的。如图,假设,我们用DBUtil封装了一些mysql的数据库。我们采用customerDAO继承它,然后通过调用父类方法的方式进行操作。但是,如果我们想将DBUtil换为oracleDBUtil,就需要改变coustomerDAO中类的内容。这违反了开闭原则。
如上完成重构后,如果我们使用依赖注入,我们只需要改变平台的配置文件,不需要更改任何代码就可以实现功能了。