软件设计原则
为了提高软件的可维护性和可复用性,增加软件的可扩展性和灵活性,要根据几个基本原则来开发软件
开闭原则
对扩展开放对修改关闭
-
在程序需要被扩展的时候,不去修改原有的代码,而是实现一种热插拔的效果*
-
就是要使用的时候插上就可以用,不用的时候就拔掉不使用
-
要实现这样的效果需要使用到接口和抽象类
- 就是在程序需要扩展的时候,不选择修改方法,而是重新创建一个实现类对象来扩展
例子
public abstract class AbstractSkin {
//抽象皮肤接口
public abstract void display();
}
//实现类
public class DaFaultSkin extends AbstractSkin{
@Override
public void display() {
System.out.println("默认皮肤");
}
}
public class NewSkin extends AbstractSkin{
@Override
public void display() {
System.out.println("自定义皮肤");
}
}
//调用的一个总的集合
public class Sougou输入法 {
//这里是通过赋值的方式来将对应的实现类传入到该总类中
private AbstractSkin skin;
public Sougou输入法(AbstractSkin skin) {
this.skin = skin;
}
public void display(){
skin.display();
}
}
//Main
public static void main(String[] args) {
Sougou输入法 s = new Sougou输入法(new DaFaultSkin());
//这个就是使用默认皮肤了
s.display();
new Sougou输入法(new NewSkin()).display();
//这个就是使用自定义的皮肤了
}
里氏代换原则
任何基类可以出现的地方,子类一定可以出现
- 就是子类可以扩展父类的功能,但不能改变父类原有的功能
- 就是子类继承父类时,尽量只是添加新的方法完成新功能,对旧有的父类方法尽量不去重写他
例子
正方形不是长方形
因为正方形是一种特殊的长方形,所以在定义正方形中,就可以让正方形去继承长方形的类
先来一个负面例子
public class changfangxing {
//长方形类
private double width;
private double length;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
}
//正方形类
public class zhengfangxing extends changfangxing{
@Override
public void setLength(double length){
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double length){
super.setLength(length);
super.setWidth(length);
}
}
这里如果我对上面2个类都调用一个扩宽方法
public static void resize(changfangxing changfangxing){
//如果宽如果比长小,就把它扩宽
while(changfangxing.getLength()>=changfangxing.getWidth()){
changfangxing.setWidth(changfangxing.getWidth()+1);
}
}
运行长方形没有问题,但是正方形就会进入死循环,因为正方形的长度设置是长宽一起增长的,所以方法进入死循环
- 这样就不符合里氏代换原则了
- 所以需要作出修改
改进
//定义的四边形的接口
public interface shibianxing {
double getLength();
double getWidth();
//分别定义获取长和宽的方法
}
//正方形类
public class zhengfangxing implements shibianxing{
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
//长方形类
public class changfangxing implements shibianxing{
private double length;
private double width;
@Override
public double getLength() {
return this.length;
}
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getWidth() {
return this.width;
}
}
使用这种方法来定义的长方形和正方形类就没有明确的父子关系,只有相同的源(四边形接口)
这样就无法使用对扩宽函数传递正方形对象了
依赖倒转原则
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 就是在需要A继承B的时候,不然A继承B,而是从B中抽取一个出一个接口,让A继承该接口
- 抽象不应该依赖细节,细节应该依赖
- 主要是面向抽象接口的编程,而不是面对实现类进行编程,来实现降低客户与实现模模块的耦合度
例子:组装电脑
- 如果对于电脑的每一个配件(像是CPU,GPU,内存等)都独立创建一个类
- 再将这些类放入一个大类(computer类)的话,如果要修改使用的配件的话,就要对整个电脑类进行修改,非常麻烦
- 这样就违反了开闭原则
改进
- 使用接口,让每一配件都实现对应的配件需要的接口
- 这样computer类的扩展性就非常高了
单一职责原则
- 单个接口或类不应该有多个职责,应该尽可能的划分职责,通过组合的方式,完成更为复杂的业务
- 感觉这个和接口隔离原则差不多
作用
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性,可维护性
- 降低变更引起的风险
注意
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则
例子 : 学生工作安排例子
- 学生工作分为生活辅导和学业辅导
- 如果将这2个工作都交给同一人进行的话,有可能在生活辅导方面这个人做得不好要换人,但却因为这个人学业辅导做得很好换不了
- 所以这2个工作分为2部分,分别交给辅导员和指导老师处理
接口隔离原则
- 客户端不应该被迫依赖于它不使用的方法
- 其中B类就是客户端
- 一个类对另一个类的依赖应该建立在最小的接口上
- 就是将A类对应的所有方法分别分割为几个小的接口
- 客户端只要选自己需要的接口继承就可以
例子 : 安全门
- 现在定义了一个安全门的接口,里面有防盗,防火,解锁功能
- 如果我有一个安全门的品牌继承了这个接口,就一定要实现里面的所有功能
- 如果我想要做的品牌是不生产带防火功能的安全门,那么就会被接口限制不得不去实现该功能
- 这样就违反了接口隔离原则了
改进
- 将对应的几个功能分割为3个接口
- 让要生产的门分别继承接口即可
- 这样就可以生产出可以只防盗,不能解锁和防火的门了
迪米特法则
-
最少知识原则
- 只和你的朋友交谈,不和’陌生人’交谈
- 这里的朋友指的是 :当前对象本身,当前对象的成员对象,当前对象所创建的对象,当前对象的方法参数等
- 这些对象同当前对象存在关联,聚合或组合关系,可以直接访问这些对象的方法
-
如果2个软件实体无需直接通信的话,就不应该发生直接的相互调用,可以通过第三方去转发该调用
- 这样可以降低类与类之间的耦合度,提高独立性
例子 : 明星和经纪人的关系
- 对于一个明星,他会拥有粉丝和对应的合作公司
- 明星肯定不可能直接和这些对象沟通
- 所以需要使用一个中间对象(经纪人)来帮忙处理这些事务
主要还是为了降低明星和这几个类之间的耦合度
合成复用原则
- 尽量先使用组合或聚合等关联关系来实现类的复用
- 其次再考虑使用继承关系来实现
通常类的复用分为合成复用和继承复用
例子 : 汽车分类管理系统
- 可以看到如果使用继承复用的话,会产生很多的子类
- 每增加一种新的动力的汽车,就需要再重新定义新的类
改进
- 在汽车类中聚合一个color(颜色)的接口
- 这样在添加新动力的汽车的是时候只需要调用Car类中对应的color方法添加对应的颜色即可