设计模式前要-七大软件架构设计原则

目录

一:开闭原则

二:依赖倒置原则

三:单一职责原则

四:接口隔离原则。

五:迪米特法则

六:里氏替换原则

七:合成复用原则

设计原则总结:


一:开闭原则

开闭原则(Open-Closed Principle,OCP),指一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。强调的是用抽象构建框架用实现扩展细节,可以提高软件的可复用性以及可维护性。开闭原则可以指导我们不修改源码,但是可以增加新功能。

比如我们的service层,dao层用接口来实现,用实现类来扩展功能。

二:依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置可以降低类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并降低修改程序带来的风险。

比如有一个Tom类,Tom学习Java,Python课程。

public class Tom {
	public void studyJavaCourse(){
		System.out.println("tom  学习Java课程");
	}
	public void studyPythonCourse(){
		System.out.println("tom  学习Python课程");
	}
}

在使用的时候如下:

public class CourseMain {
	public static void main(String[] args) {
		Tom tom = new Tom();
		tom.studyJavaCourse();
		tom.studyPythonCourse();
	}
}

如果后面Tom需要学习新的课程,就需要修改Tom的源码继续增加其它课程的方法。代码要从底层到高层(调用层)依次修改。如此一来系统发布以后维护很不稳定。

因此要把这个功能分成抽象接口来实现。

首先有个课程的接口ICourse

public interface ICourse {
      void study();
}

Java课程单独实现一个类。

public class JavaCourse implements ICourse {
	@Override
	public void study() {
		System.out.println("学习Java");
	}
}

Python课程单独实现一个类。

public class PythonCourse implements ICourse {

	@Override
	public void study() {
		System.out.println("学习Python");
	}
}

对于Tom类,依赖课程的抽象ICourse.

public class Tom {
	public void study(ICourse course){
		System.out.println("tom 正在");
		course.study();
	}
}

调用的时候:

public class CourseMain {
	public static void main(String[] args) {
		Tom tom = new Tom();
		tom.study(new JavaCourse());
		tom.study(new PythonCourse());
	}
}

这个时候看代码,无论Tom再想学什么课程,只要新增课程的实现类,再高层调用的时候传入就行了,而不需要修改底层的代码。实际上这是一中非常熟悉的方式,依赖注入,注入的方式还有构造器方法注入和Setter方法注入,只是向Tom传递ICourse实现类的方式不一样而且。

注意:

以抽象为基准比以细节为基准搭建起来的架构要稳定的多,因此大家在拿到需求后要面向接口编程,按照先顶层再细节的顺序设计代码结构。

三:单一职责原则

单一职责原则(Simple Responsibility Principle)SRP,指不要存在一个以上导致类变更的原因,假设一个类负责两个功能,一旦需求发生变化,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。因此我们需要用两个类进行实现两个不同的职责功能,进行解耦。总体来说,一个Class,Interface,Method只负责一项职责。

这个比较好理解,就不举例子了。

四:接口隔离原则。

接口隔离原则(Interface Segregation Principle ,ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。因此设计接口时应当注意一下几点:

(1)一个类对另外一个类的依赖应该建立在最小接口上。

(2)建立单一接口,不要建立庞大臃肿的接口。

(3)尽量细化接口,接口中方法尽量少(也不是越少越好,要适度)

接口隔离原则符合"高内聚  低耦合"的设计思想,使得类具有很好的可读性,可扩展性和可维护性。在接口设计的时候要多花时间思考,要考虑业务模型,包括还要对以后可能发生变更的地方做一些预判。所以,在实际开发中,我们对抽象,业务模型的理解是非常重要的。

假如有个动物的顶级接口:
 

public interface IAnimal {
	void eate();
	void fly();
	void swim();
}

实现一只狗:

package com.my.ioc.pojo.design;
public class Dog implements IAnimal {
	@Override
	public void eate() {
		System.out.println("dog 吃东西");
	}
	@Override
	public void fly() {
	}
	@Override
	public void swim() {
	}
}

实现一只鸟:

package com.my.ioc.pojo.design;

public class Bird implements IAnimal {
	@Override
	public void eate() {
	}
	@Override
	public void fly() {
		System.out.println("bird fly");
	}
	@Override
	public void swim() {
	}
}

由以上两个实现类看出Dog类中的fly方法只能空着,Bird中的swim方法也只能空着,这个时候就需要针对不同的动物行为来设计不同的接口,分别设计IEateAnimal,ISwimAnimal,IFlyAnimal接口。

public interface IEateAnimal {
	void eate();
}
public interface IFlyAnimal {
	void fly();
}
public interface ISwimAnimal {
	void swim();
}

针对不同的动物有不同的行为来实现不同的接口。

public class DogNew implements IEateAnimal,ISwimAnimal {
	@Override
	public void eate() {
		System.out.println("dog 要吃东西");
	}
	@Override
	public void swim() {
		System.out.println("dog 其实是可以游泳的");
	}
}

public class BirdNew implements IFlyAnimal,IEateAnimal {
	@Override
	public void eate() {
		System.out.println("bird 吃东西");
	}
	@Override
	public void fly() {
		System.out.println("bird 要飞起来");
	}
}

五:迪米特法则

迪米特法则(Law of Demeter ,LoD)又叫最少知道法则,指一个对象应该对其它对象保持最少的了解,尽量降低类与类之间的耦合。

它主要强调只和朋友交流,不和陌生人说话。出现在成员变量,方法的输入输出参数中的类都可以称为成员朋友,而出现在方法体内部的类不属于朋友类。

迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

比如:我们经常使用的三层架构,controller层中调用service层,在service层中调用dao层,在dao层中访问数据库,而不是跳过中间层直接controller访问dao即使service层有时候并不处理任何业务逻辑,只起到友元类的作用。

六:里氏替换原则

        里氏替换原则(Liskov Substitution Principle ,LSP)指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。

       定义可以理解为: 一个软件实体如果适用于一个父类,则一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。换句话说,子类可以扩展父类的功能,但不能改变父类原有的功能。根据这个理解,我们对其总结如下:

1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

2)子类可以增加自己特有的方法。

3)当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类方法更宽松。

4)当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的返回值要比父类的方法更加严格或相等。

用正方形,矩形,和四边形的关系说明里氏替换原则,我们都知道正方形是一个特殊的长方形,可以新建一个长方形父类Rectangle.

package com.my.ioc.pojo.design;

public class Rectangle {
	private long heigth;
	private long width; 

	public long getHeigth() {
		return heigth;
	}

	public void setHeigth(long heigth) {
		this.heigth = heigth;
	}

	public long getWidth() {
		return width;
	}

	public void setWidth(long width) {
		this.width = width;
	}
}

创建正方形继承长方形:

package com.my.ioc.pojo.design;

public class Squar extends Rectangle {
	private long length;

	public long getLength() {
		return length;
	}

	public void setLength(long length) {
		this.length = length;
	}

	@Override
	public long getHeigth() {
		return getLength();
	}

	@Override
	public void setHeigth(long heigth) {
		setLength(heigth);
	}

	@Override
	public long getWidth() {
		return getLength();
	}

	@Override
	public void setWidth(long width) {
		setLength(width);
	}
}

 使用:

public class CourseMain {
	public static void main(String[] args) {
		Rectangle rectangle = new Rectangle();
		rectangle.setHeigth(10);
		rectangle.setWidth(20);
		resize(rectangle);
	}

	public static void resize(Rectangle rectangle){
		while (rectangle.getWidth()>=rectangle.getHeigth()){
			rectangle.setHeigth(rectangle.getHeigth()+1);
			System.out.println("width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth());
		}
		System.out.println("end:      width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth());
	}
}
> Task :spring-myselftest:CourseMain.main()
width:20heigth11
width:20heigth12
width:20heigth13
width:20heigth14
width:20heigth15
width:20heigth16
width:20heigth17
width:20heigth18
width:20heigth19
width:20heigth20
width:20heigth21
end:      width:20heigth21

由运行结果可知,高比宽还大,这在长方形中是不正常的。看下如果把长方形换成子类正方形看下结果:

	public static void main(String[] args) {
		Squar rectangle = new Squar();
		rectangle.setHeigth(10);
		rectangle.setWidth(20);
		resize(rectangle);
	}

结果运行是个死循环:
 

	public static void main(String[] args) {
		Squar rectangle = new Squar();
		rectangle.setHeigth(10);
		rectangle.setWidth(20);
		resize(rectangle);
	}
width:12190heigth12190
width:12191heigth12191
width:12192heigth12192
width:12193heigth12193
width:12194heigth12194
width:12195heigth12195
width:12196heigth12196
width:12197heigth12197
width:12198heigth12198
width:12199heigth12199
width:12200heigth12200
width:12201heigth12201
> Task :spring-myselftest:CourseMain.main() FAILED

在将父类替换为子类之后,程序运行结果没有达到预期,违背了里氏替换原则,因此代码设计处在一定的风险。里氏替换原则只存在于父类与子类之间,约束继承泛滥。

再来新建一个四边形接口-QuardRangle

public interface QuardRangle {
	long getWidth();
	long getHeigth();
}

public class Rectangle implements QuardRangle{
	private long heigth;
	private long width;

	public long getHeigth() {
		return heigth;
	}

	public void setHeigth(long heigth) {
		this.heigth = heigth;
	}

	public long getWidth() {
		return width;
	}

	public void setWidth(long width) {
		this.width = width;
	}
}

public class Squar implements QuardRangle {
	private long length;

	public void setLength(long length) {
		this.length = length;
	}

	@Override
	public long getWidth() {
		return length;
	}

	@Override
	public long getHeigth() {
		return length;
	}
}

如果此时把resize方法的参数换成Squar就会报错,Squar不再是Rectangle的子类,约束了继承。

七:合成复用原则

合成复用原则(Composite/Aggregate Reuse Principle,CARP)指尽量使用对象组合(has-a)或对象聚合(contanis-a)的方式实现代码复用,而不是通过继承关系达到代码复用的目的。降低类之间的耦合,使得一个类的变化对另外的类影响相对较小。

继承,又称为白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合又称为黑盒复用对类以外的对象是无法获取实现细节的。

 

设计原则总结:

设计原则是设计模式的基础,在实际开发过程中不一定所有代码都遵循设计原则,但是我们需要考虑业务场景进行权衡代码的设计。

           设计原则                 归纳  目的
          开闭原则对扩展开放,对修改关闭降低代码维护带来的新风险
       依赖倒置原则高层不应该依赖底层更有利于代码结构的升级扩展
        单一职责原则一个类只做一件事便于理解,提高代码可读性
       接口隔离原则一个接口只做一件事功能解耦,高内聚,低耦合
       迪米特法则不该知道的不知道只和朋友交流不和陌生人说话,减少代码臃肿
     里氏替换原则子类重写方法功能发生改变,不应该影响父类方法的含义防止继承泛滥
    合成复用原则尽量使用组合代码复用,而不是继承降低代码耦合

 

 

 

 

 

 

 

 

 

 

参考书籍《设计模式就该这样学》-谭勇德

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姑苏冷

您的打赏是对原创文章最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值