设计模式就是在软件开发过程中所总结形成的一系列准则。当我们遇到一些场景的时候,使用恰当的设计模式可以使得软件设计更加健壮,增强程序的扩展性。
设计模式的六大原则如下:
开闭原则
依赖倒置原则
单一职责原则
接口隔离原则
里氏替换原则
迪米特法则
一、开闭原则(Open Close Principle,OCP):
开闭原则是指一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。也就是说,通过开闭原则,我们可以通过扩展行为来实现新的功能,而不是通过修改已有的代码。开闭原则可以提高软件系统的可复用性和可维护性。
代码讲解:
假设课程接口如下:
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
JavaCourse实现ICourse接口:
public class JavaCourse implements ICourse{
private Integer Id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
this.Id = id;
this.name = name;
this.price = price;
}
public Integer getId() {
return this.Id;
}
public String getName() {
return this.name;
}
public Double getPrice() {
return this.price;
}
}
此时,若我们有打折活动,对ICourse接口增加方法:
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
Double getDiscountPrice();
}
那么,实现类也增加对应实现方法:
public Double getDiscountPrice(){
return this.price * 0.8;
}
看起来是实现了功能,但在开发过程中,假设我们的课程很多,那么所有的课程实现类都要实现该方法,课程很多就会很麻烦,而且我们的接口应该是不经常变化的,是稳定且可靠的,否则接口作为契约这个作用就失去了。
所以我们换一种思路,用一个具有java课程打折功能的类继承JavaCourse类:
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getDiscountPrice(){
return super.getPrice()*0.8;
}
}
我们修改的是比较偏应用级的代码,底层的接口并没有修改,防止了风险的扩散,也就是说,我们的接口如果方法很多,实现类里面逻辑很非常复杂,增加功能时如果直接修改接口,实现类也跟着变化,就会很容易引起bug,所以我们应该对扩展开放,对修改关闭。
二、依赖倒置原则(Dependence Inversion Principle,DIP):
依赖倒置原则是指高层模块不应该依赖于底层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。在Java中,接口和抽象类都是抽象,而其实现类就是细节。也就是说,我们应该做到面向接口编程,而非面向实现编程。
依赖倒置原则,可以减少类间的耦合性、提高系统的稳定性,提高代码的可读性和可维护性,可降低修改程序所造成的风险。
我们来看一个讲述学习课程的例子。
public class Wen {
public void studyJavaCourse(){
System.out.println("Wen在学习Java课程");
}
public void studyFECourse(){
System.out.println("Wen在学习FE课程");
}
}
测试用例:
public class Test {
public static void main(String[] args) {
Wen wen = new Wen();
wen.studyJavaCourse();
wen.studyFECourse();
}
}
Test类属于高层模块,Wen类属于低层模块,高层次模块的实现依赖低层次模块的实现,是不符合依赖倒置原则的。所以我们换一种思路:
添加ICourse接口:
public interface ICourse {
void studyCourse();
}
两个实现接口ICourse的类:
public class JavaCourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Wen在学习Java课程");
}
}
public class FECourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Wen在学习FE课程");
}
}
Wen类:
public class Wen {
public void studyCourse(){
iCourse.studyCourse();
}
}
Test类:
public class Test {
public static void main(String[] args) {
Wen wen = new Wen();
wen.studyCourse(new JavaCourse());
wen.studyCourse(new FECourse());
}
}
此时,课程的拓展类编程是面向接口ICourse编程的,而不是面向Wen这个具体的实现类,这样做到了Test类和Wen类是解耦的,同时Wen类和具体的课程实现是解耦的。面向接口编程之后,我们可以定义多种课程,只要该课程实现ICourse接口即可。
以抽象为基础搭建起来的架构比以细节为基础搭建起来的稳定得多。
三、单一职责原则(Single responsibility principle,SRP):
单一职责规定了一个类应该只有一个发生变化的原因。如果一个类承担了多个职责,则会导致多个职责耦合在一起。但部分职责发生变化的时候,可能会导致其余职责跟着受到影响,也就是说我们的程序耦合性太强,不利于变化。单一职责原则降低类的复杂程度、提高类的可读性,提高系统的可维护性,降低业务逻辑变化导致的风险,一个接口的修改只对相应的实现类有影响,对其他接口无影响。
举个例子,我们来看下边的一个接口ICourse:
public interface ICourse {
String getCourseName();
byte[] getCourseVideo();
void studyCourse();
void refundCourse();
}
ICourse类中拥有获取课程名字和视频的方法,还有学习课程和退款课程的功能。那么,ICourse类其实就是违反了单一职责原则。我们可以将其拥有到职责进行划分,一类是关于课程内容的接口,另一类是关于课程管理的接口。
public interface ICourseContent {
String getCourseName();
byte[] getCourseVideo();
}
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
这样就符合单一职责原则。
四、接口隔离原则(Interface Segregation Principle, ISP):
接口隔离原则是指客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
接口隔离原则的使用原则:
根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
接口需要高内聚,提高接口,类和模块的处理能力,减少对外的交互。
定制服务,单独为一个个体提供优良服务(只提供访问者需要的方法)。
接口设计要有限度,接口设计的太小,容易造成开发难度增加或者可维护性降低。
接口隔离原则和单一职责原则很相似,它们的区别在于观察角度不一样。单一职责是从模块、类或方法自身的角度来看的,强调的是职责,接口隔离原则是从调用者的角度来看的。
五、迪米特法则(Law of Demeter,LoD):
迪米特法则也叫最少知道原则,是指一个对象应该对其依赖的对象有最少的了解。该类不需要知道其依赖类的具体实现,只需要依赖类给其提供一个公开对外的public方法即可,其余一概不需要了解。
迪米特法则的核心就是解耦合,减弱类间的各个耦合,提高类的复用率。
六、里氏替换原则(Liskov Substitution Principle,LSP):
里氏替换是指所有父类可以出现的地方,子类就都可以出现,使用子类来替换父类,调用方不需要关心目前传递的父类还是子类。
通过里氏替换原则,我们可以将子类对象做为父类对象来使用,屏蔽了不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。里氏替换之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
接下来,我们一起看一个Demo:
public class LiSiTest {
public static void main(String[] args) {
// 通过传入子类对象来替换父类
eat(new Dog());
eat(new Cat());
}
private static void eat(Animals animal){
animal.eat();
}
}
abstract class Animals{
abstract void eat();
}
class Dog extends Animals{
@Override
void eat() {
System.out.println("我是小狗,喜欢吃大肉");
}
}
class Cat extends Animals{
@Override
void eat() {
System.out.println("我是小猫,喜欢吃小鱼干");
}
}
里氏替换原则可以增强程序的健壮性,子类可以任意增加和缩减,我们都不需要修改接口参数。在实际开发中,实现了传递不同的子类来完成不同的业务逻辑。
接下来,我们对六大设计原则做一个简单的总结。
单一职责原则:类或者接口要实现职责单一
里氏替换原则:使用子类来替换父类,做出通用的编程
依赖倒置原则:面向接口编程
接口隔离原则:接口的设计需要精简单一
迪米特法则:降低依赖之间耦合
开闭原则:对扩展开放,对修改关闭