软件设计原则

简介

什么是软件设计原则?是一组帮助我们避开不良设计的指导方针。

是由 Robert Martin 在《敏捷软件开发:原则、模式与实践》一书中整理收集而来。

应该避免不良设计的以下三个重要特点:

  • 僵化: 很难做改动,因为每一个细微的改动都会影响到系统大量的其他功能
  • 脆弱: 每当你做一次改动,总会引起系统中预期之外的部分出现故障
  • 死板: 代码很难在其他应用中重用,因其不能从当前应用中单独抽离出来

何为设计模式的原则

  • 程序员在编写程序的时候,应当遵守的准则
  • 设计模式的基础,依据
  • 不是孤立存在的,相互依赖的,相互补充的,每一种都很重要


1.开放封闭原则

官方定义:软件实体,如类、模块和函数,应该对扩展开放,对修改关闭

对扩展开放——提供方,对修改关闭——调用方


如何达到效果?

需要使用抽象类接口,因为抽象灵活性好。适用性广,只要抽象的合理,可以基本保持软件架构的稳定。

而软件中异变的细节可以从抽象派生来的实现类进行扩展,当软件需要发生变化时只需求重新派生实现类来扩展即可


案例

//皮肤抽象类
abstract class Skin{
    //打印方法
    public abstract void display();
}
//实现类1
class DefaultSkin extends Skin{
    @Override
    public void display() {
        System.out.println("默认皮肤");
    }
}
//实现类2
class KeFengSkin extends Skin{
    @Override
    public void display() {
        System.out.println("克峰皮肤");
    }
}
//搜狗输入法
class Sougoul{
    private Skin skin;

    public void setSkin(Skin skin){
        this.skin=skin;
    }
    public void display(){
        skin.display();
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        Sougoul sougoul=new Sougoul(); //创建搜狗输入法对象
        Skin skin=new DefaultSkin();  //创建皮肤对象
        sougoul.setSkin(skin);       //将皮肤设置到输入法中
        sougoul.display();          //显示皮肤
    }
}

备注:最基础最重要的设计原则,可以提高复用性和可维护性。



2.单一职责原则

官方定义:一个类,应该只有一个需要进行修改的原因

有且仅有一个原因引起类的变更,顾名思义就是各司其职。适用于: 接口方法

如何遵守?其实就是职责分解,关键从业务出发从需求出发,识别出同一个类型的职责。


优点&细节

  • 降低类的复杂度

    (职责少了,相应的复杂度也就降低了)

  • 提高可读性,可维护性

    (相应的复杂度降低了,代码量就会减少,可读性就提高了,可维护性自然也就提高了)

  • 降低变更引起的风险

    (一个类职责越多,变更的可能性就越大,带来的风险页就越大)

  • 通常情况下,我们应当遵守类级别的单一职责原则

    (只有逻辑足够简单,才可以在代码中违反单一原则)


案例

下图的 XXXDao 类,负则User表的增删改,同时又负则Order表的增删改,违反了单一职责

img

根据原则,将 XXXDao 的力度进行分解成两个单一职责的类

img

注意:单一职责不是面向对象语言特有的,只要是模块化的程序设计,都要遵守。



3.接口隔离原则

官方定义

客户端不应该被强制依赖他们不需要使用的接口

类间的依赖应该建立在最小接口上

通俗来说,不要再一个接口里面定义过多的方法,接口应该尽量细化。


优点

  • 提高系统的灵活性和可维护性
  • 提高了系统的内聚性,减少了对外交互,降低了系统的耦合性
  • 能减少项目工程中的代码冗余

案例

//行为
interface behavior{
    void Eating(); //进食
    void cannon(); //跑
    void fly(); //飞行
}

//鸟类
class bird implements behavior{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
    @Override
    public void fly() {}
}
//狗类
class dog implements behavior{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
    @Override
    public void fly() {}
}

很明显上面案例不符合 接口隔离原则 ,因为它们都存在着用不到的方法,比如狗类的飞行,但由于实现了接口 behavior ,所以也 必须要实现这些用不到的方法。这里把接口尽量细化后的效果如下:

//进食
interface eat{
    void Eating(); //进食
}
//飞禽
interface Birds{
    void fly(); //飞
}
//犬科
interface Canidae{
    void cannon(); //跑
}

//鸟类
class bird implements eat,Birds{
    @Override
    public void Eating() {}
    @Override
    public void fly() {}
}
//狗类
class dog implements eat,Canidae{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
}

小结:就是当我一个类通过接口依赖(使用)另一个类的时候,要保证依赖的该接口是最小的,接口里面有方法用不到的,就进行隔离,而做法就是对原来接口进行拆分为最小粒度,来避免耦合;


与单一职责对比

相同点:都要求对结构进行拆分,都要求更小粒度,都希望减少耦合。

不同点:审视角度不同,单一职责,注重的是职责,而接口隔离,要求尽量使用多个专门的接口,注重的是接口设计

使用接口隔离原则进行接口拆分的时候,要遵循单一职责原则



4.依赖倒置原则

官方定义

也称为DIP原则,上层模块不应该依赖底层模块,它们都应该依赖于抽象

抽象不应该依赖细节,细节应该依赖抽象

抽象:接口或者抽象类,细节:实现类

核心理念:相对于细节来说,抽象要稳定得多

要求我们面向接口编程:也就是,对抽象类和接口的设计


传递的三种方式

通过接口传递

interface IMessage{
	void sendMessage(Produce produce);
}
//消息生产者
interface Produce{
	void produceMessage();
}
//工作人员类
class Worke implements IMessage{
	@Override
	public void sendMessage(Produce produce) {
		produce.produceMessage();
	}
}

通过构造方法传递

interface IMessage{
	void sendMessage();
}
//消息生产者
interface Produce{
	void produceMessage();
}
//工作人员类
class Worker implements IMessage{
	public Produce produce;
	//构造器传递
	public Worker(Produce produce) {
		this.produce=produce;
	}
	@Override
	public void sendMessage() {
     this.produce.produceMessage();
    }
}

通过set方法传递

interface IMessage{
	void sendMessage();
}
//消息生产者
interface Produce{
	void produceMessage();
}
//工作人员类
class Worker implements IMessage{
	public Produce produce;
	public void setProduce(Produce produce) {
		this.produce=produce;
	}
	@Override
	public void sendMessage() {
		this.produce.produceMessage();
	}
}

案例

需求:假设有一个场景,现在有一个工作人员,收发钉钉消息

//测试类
public class InversionDemo{
	public static void main(String[] args) {
		new Worker().getMessage(new DingDing());
		new Worker().getMessage(new WeChat());
	}
}
//引入接口,制定消息规范
interface IMessage{
	void SendMessage();
}

class Worker{
	//公共接收消息,依赖接口
	public void getMessage(IMessage iMessage) {
		iMessage.SendMessage();
	}
}

//钉钉类
class DingDing implements IMessage{
	//发送消息
	@Override
	public void SendMessage() {
		System.out.println("钉钉上,老板找你加班啦");
	}
}

//微信类
class WeChat implements IMessage{
	//发送消息
	@Override
	public void SendMessage() {
		System.out.println("钉钉不回,老板打你微信电话");
	}
}

小结:本质上就是通过抽象(抽象类和接口)使得各个类或模块实现彼此独立,互相不影响,实现模块间耦合,要先顶层再细节得方式进行代码细节设计


注意事项&细节

  • 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
  • 变量得声明类型尽量是抽象类或接口,这样我们得变量引用和实际对象间,就存在一个缓冲量,利于程序扩展和优化
  • 继承时遵循里式替换原则


5.里氏替换原则

官方定义:所有引用基类得地方必须透明地使用其子类对象

通俗来说:子类可以扩展父类得功能,但是子类不能修改父类得功能,就是给继承性的使用制定了规范


继承优势

  • 提高代码得复用性(每个子类拥有父类得属性和方法)
  • 提高代码可扩展性

继承劣势

  • 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
  • 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
  • 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。

继承是把双刃剑-->如何正确合理使用继承呢?——>里式替换原则


案例

需求:有一个计算机(父类)可以完成加减,定义其子类

//测试类
public class InversionDemo{
	public static void main(String[] args) {
		int result = new Calculator().add(3, 5);
		System.out.println(result);
		int mul = new SuperCalculator().mul(3, 5);
		System.out.println("两数相加之和,与100求差的值为:"+mul);
	}
}

//创建一个更加基础的基类(定义更加基础的成员或者功能)
class Base{}

class Calculator extends Base{
	//定义加法
	public int add(int a,int b) {
		return a+b;
	}
	//定义减法
	public int sub(int a,int b) {
		return a-b;
	}
}

class SuperCalculator extends Base{
	//变为依赖关系
	private Calculator calculator=new Calculator();
	//增补需求,两数相加再加5
	public int add(int a,int b) {
		return a+b+5;
	}
	//两数相加之和,与100求差
	public int mul(int a,int b) {
		int count=calculator.add(a, b);
		return 100-count;
	}
}

小结:之间继承Calculator 类会无意间重写父类add方法,需要他俩继承同一个基类


注意事项

  1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
  2. 子类中可以扩展自己的方法
  3. 里式替换原则并非让我们避免使用继承
  4. 里式替换原则是实现开闭原则的重要方法之一


6.迪米特法则

官方定义

所以迪米特法则又叫做最少知识原则

只与直接的朋友通信

一个对象应该对其他对象有最少的了解

什么是朋友?只要两个对象有依赖关系,朋友关系

什么是直接朋友?

  1. 成员变量
  2. 方法的参数类型
  3. 方法的返回值类型

案例

需求:应该学校,下属有各个学院和总部,现要求打印总部员工的ID和学院员工的ID

//客户端
public class Demo{
	public static void main(String[] args) {
		new SchoolManager().printAIIEmployee(new CollegeManager());
	}
}
//总部员工的基类
class SchoolEmployee{
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
}
//学校员工的基类
class CollegeEmployee{
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
}
//学院管理类
class CollegeManager{
	//获取学院员工
	public List<CollegeEmployee> getCollegeEmployees(){
		ArrayList<CollegeEmployee> collegeEmployees =new ArrayList<>();
		for (int i = 0; i <10; i++) {
			CollegeEmployee collegeEmployee=new CollegeEmployee();
			collegeEmployee.setId("学院员工,ID是"+i);
			collegeEmployees.add(collegeEmployee);
		}
		return collegeEmployees;
	}
	//打印学院员工
	public void printollegeEmployee(CollegeManager collegeManager) {
		List<CollegeEmployee> collegeEmployees = collegeManager.getCollegeEmployees();
		for (CollegeEmployee employee:collegeEmployees) {
			 System.out.println(employee.getId());
		}
	}
}
//总部管理类
class SchoolManager{
	//获取总部员工
	public List<SchoolEmployee> getSchoolEmployees(){
		ArrayList<SchoolEmployee> schoolEmployees =new ArrayList<>();
		for (int i = 0; i <10; i++) {
			SchoolEmployee schoolEmployee=new SchoolEmployee();
			schoolEmployee.setId("总部员工,ID是"+i);
			schoolEmployees.add(schoolEmployee);
		}
		return schoolEmployees;
	}
	//打印方法
	public void printAIIEmployee(CollegeManager collegeManager) {
		//打印总部员工
		List<SchoolEmployee> schoolEmployees = this.getSchoolEmployees();
		for (SchoolEmployee employee:schoolEmployees) {
			System.out.println(employee.getId());
		}
		System.out.println("---------------------------------------------");
		//打印学院员工
		collegeManager.printollegeEmployee(collegeManager);
	}
}

类的依赖关系:两个类?具有依赖关系?凡是类中用到了对方


注意事项

  • 核心是降低类之间的耦合
  • 从被依赖者的角度来说,尽量将逻辑封装在类的内部,对外除了提供的public方法,不泄露任何信息
  • 从依赖者的角度来说,只依赖应该依赖的对象
  • 切忌不要为了用而用


7.合成复用原则

官方定义:尽量使用组合/聚合的方式,而不是使用继承


案例

需求:现在假设有一个A类,里面有两个方法,有一个类B,想要复用这两个方法,请问有几种方案

方式一:继承

image-20220808113757421

方式二:组合或者聚合(业务逻辑)

image-20220808113657192

方式三:依赖关系

image-20220808113420632

设计原则总结

  1. 开闭原则:要求对扩展开发,对修改关闭
  2. 里氏替换:不要破坏继承关系
  3. 依赖倒置原则:要求面向接口编程
  4. 单一职责原则:实现类职责单一
  5. 接口隔离原则:在设计接口的时候要简单单一
  6. 迪米特法则:只与直接的朋友的通信
  7. 合成复用原则:尽量使用组合和聚合的方式,二不是使用继承


核心思想

  • 找出应用中可能需要变化之处,独立出来,不要和不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 为了交互对象的松耦合设计二努力

遵循设计原则·就是为了让程序高内聚地耦合

posted @ 2022-10-08 10:58  克峰同学  阅读(51)  评论(0编辑  收藏  举报