软件架构设计原则
一、什么是软件设计原则
软件设计原则是为了让代码的可读性变高,复用性更好,更有利于后期的扩展和修改。即使不学习也完全可以写代码,只是后期面临业务的变更和需求的变化代码的修改极其困难。
二、为什么要遵循软件设计原则
直接目的: 为了在测试找到我们之后,修改代码更加的快捷、迅速。
长久目的: 设计原则是23中设计思想的基础,有的设计思想是使用一种软件设计原则,有的设计模式是使用多个设计原则结合使用。各种优秀的框架就是多种设计模式的联合使用。想要重构各式各样的框架或者是手写服务器又或者是进修架构师。设计原则也是提现在了各个地方。当然包括源码中也有很多地方使用。
三、七种设计原则的介绍
1、开闭原则(Open-Close Principle)
指的是一个软件实体(类、软件、模块)应该对扩展开放、对修改关闭。这里的开闭,指的就是对扩展和修改的两个行为的一个原则。强调的是使用抽象建立框架,用实现扩展细节,可以提高程序的可复用性和可维护性。开闭原则的主要思想为在不修改原来的代码的情况下扩展新的功能。
以下为举例说明
使用一个商店的商品为例,商品有三个属性:id、name、pric。当商品进行促销降价的时候,如何在不修改源代码的情况下完成降价。
以下为商品的接口
interface MyCoures{
Integer getId();
String getName();
Double getPrice();
}
以下Wie原本的商品的实现类
public class MyCouresImpl implements MyCoures {
private int id;
private String name;
private Double price;
@Override
public Integer getId() {
return id;
}
public MyCouresImpl(int id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public Double getPrice() {
return price;
}
}
现在要进行的是对商品进行打折促销,如果在源代码上进行修改,会存在风险,也是我们要注意开闭原则的原因。
所以在此处重写一个类继承这个类,并重写其中获取价格的方法。代码如下所示
public class MyDiscountCoureesImpl extends MyCouresImpl{
public MyDiscountCoureesImpl(int id, String name, Double price) {
super(id, name, price);
}
@Override
public Double getPrice() {
return super.getPrice() * 0.8;
}
}
类结构图如下所示
2、依赖倒置原则(Dependence Inversion Principle)
指的是在设计代码结构的时候,高层模块不应该依赖于底层模块,应该都依赖于抽象。抽象不应该依赖于细节,应该依赖于接口,通过依赖倒置,可以减少类与类之间的耦合度,提高程序的稳定性,提高代码的可读性和可维护性。
以下为使用依赖倒置原则的例子
以下是keys类,其中定义了学习java和JavaScript方法
public class Keys {
public void studyJavaCourse(){
System.out.println("keys正在学java");
}
public void studyJavaScriptCourse(){
System.out.println("keys正在学javaScript");
}
}
调用代码:
public static void main(String[] args) {
Keys keys = new Keys();
keys.studyJavaCourse();
keys.studyJavaScriptCourse();
}
此时假如需求改变了,keys的学性大发,想要学习更多的新知识,就需要在keys的类上添加新的方法,这对于一个已经上线的项目来说是存在风险的。不符合依赖倒置原则。理想的状态是不修改原来的代码,添加新的类就可以进行扩展。
以下为优化代码
将课程提升为接口,里面有一个study方法。
public interface MyCourse {
void study();
}
keys也有一个study的方法
public class KeysPlus {
public void study(MyCourse myCourse){
myCourse.study();
}
}
学习的方法实现接口
public class JavaCoures implements MyCourse{
@Override
public void study() {
System.out.println("keys正在学java");
}
}
public class JavaScriptCoures implements MyCourse{
@Override
public void study() {
System.out.println("keys正在学javaScript");
}
}
学习的实现
public static void main(String[] args) {
KeysPlus keys = new KeysPlus();
keys.study(new JavaCoures());
keys.study(new JavaScriptCoures());
}
如果这个时候想要学习新的知识,只需要新添加一个MyCourse的实现类,并实现方法,代码如下
public class GoCoures implements MyCourse{
@Override
public void study() {
System.out.println("keys正在学go");
}
}
新的测试如下
public class Test {
public static void main(String[] args) {
KeysPlus keys = new KeysPlus();
keys.study(new JavaCoures());
keys.study(new JavaScriptCoures());
keys.study(new GoCoures());
}
}
所以我们在编程的过程中,需要以抽象为基准搭建架构,因为它比以细节为架构的要稳健的多,所以需要我们面向接口编程。
3、单一职责原则(Simple Responsibility Principle)
单一职责指的是不要存在多于 一个导致一个类发生变更的原因(只有一个原因导致类发生变化)。
需要使用单一职责的原因:假设现在的一个类负责 两个职能,一旦需求发生变化,修改其中的一个职责的逻辑代码,有可能导致另一个职能的的功能发生变化。所以需要对着一个类的两个只能进行解耦合。将这个类写成两个类。也是一种解耦合的方式的体现,核心的思想在于如果到了不得不修改类的局面,要尽可能的修改较为少的代码。提高程序的可维护性。
言而总之就是要使得一个类、接口方法只负责一项职能。
4、接口隔离原则(Interface Segregation Principle)
定义 接口隔离原则指的是我们应该使用更为细则化的接口,,而不应该使用单一的臃肿的接口,客户端不应该依赖于他不需要的接口,这个原则有三个基本要求:
- 一个类对另一个类的依赖应该建立在最小的接口之上
- 建立单一化接口,不要使用一个臃肿的接口
- 尽量细化接口,接口中的方法尽量少(不是越少越好)
目的接口隔离原则符合我们常说的高内聚、低耦合的思想,可以使得类有更高的扩展性、可读性和可维护性。
举例说明:下面是描述动物行为的接口
没有使用接口隔离原则:使用单一的接口
public class Brid implements IAnimals {
@Override
public void eat() {
System.out.println("鸟为食亡");
}
@Override
public void fly() {
System.out.println("沙鸥翔集");
}
@Override
public void swim() {
}
}
public class Fish implements IAnimals {
@Override
public void eat() {
System.out.println("愿者上钩");
}
@Override
public void fly() {
}
@Override
public void swim() {
System.out.println("锦鳞游泳");
}
}
可以看到:brid中的swim方法和Fish中的fly方法没有实现具体的功能(而且也不应该出现在实现类中),这就是单一接口所造成的问题
以下是优化过的代码:也就是使用了单一接口原则后的代码
三个方法的接口
public interface IEatAnimals {
/**
* 民以食为天
*/
void eat();
}
public interface IEatAnimals {
/**
* 民以食为天
*/
void eat();
}
public interface ISwimAnimals {
/**
* 锦鳞游泳
*/
void swim();
}
两个实现类
public class Brid implements IEatAnimals, IFlyAnimals {
@Override
public void eat() {
System.out.println("鸟为食亡");
}
@Override
public void fly() {
System.out.println("沙鸥翔集");
}
}
public class Fish implements IEatAnimals, ISwimAnimals {
@Override
public void eat() {
System.out.println("愿者上钩");
}
@Override
public void swim() {
System.out.println("锦鳞游泳");
}
}
可以看到,这次使用了对应的原则之后,实现类中没有出现多于的方法,在后期维护也是大大减少了代码的修改量。
5、迪米特原则(Law of Demeter)
是指一个对象应该对其他对象保持最少的了解。迪米特原则强调纸盒朋友交流,不和陌生人说话,出现在成员变量、方法的输入、输出参数中的对象都可以成为朋友类,而出现在方法体内部的类不属于朋友类。
6、里氏替换原则(Liskov Substitution Principle)
如果一个软件的实体类适用于一个父类,那么他一定适用于其子类,所有可以引用这个父类的地方都必须能透明的应用这个子类,而且程序的逻辑不变。引申含义为:子类可以扩展父类的功能,但是不能改变父类原有的功能。
-
子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
-
子类可以添加自己的特有的功能
-
当子类重载父类的方法时,方法的参数输出的限制条件应该比父类的更加宽松
-
当子类实现父类的抽象方法时,输出的参数(返回值)应该比父类的更加严格或者一致
里氏替换原则的优点 -
约束继承泛滥,是开闭原则的一种体现
-
加强程序的健壮性,同时变更时做到了非常好的兼容性,提高程序的可维护性和扩展性,降低需求变更所带来的风险
7、合成复用原则
合成复用原则是指尽量使用对象组合,而不是使用继承的关系达到软件复用的目的。可以使得系统更加的灵活,降低类与类的耦合性一个类的变化。