师说
总有人疑惑:优秀的技术架构和平庸的,区别在哪里?确实,从开发到上线,似乎没什么区别,都能跑业务。但优秀的技术架构,可以让后续的运营,维护变得更简单,更便捷。
我们平时说的扩展性更好,包括两个层面:一是用户量、访问频次、数据规模增加后的扩展性;二是新的业务诉求和运营诉求的扩展性。理解这一点,就能明白:为什么优秀的技术研发能力,可以让企业发挥更大的价值。
回归技术,其实就是强调无数次的高复用、低耦合。有人说,这不是架构师的范畴么。架构师的确需要这样的概念和设计准则,但普通的研发工程师,难道就不需要了么?很多程序员都卡在了这个环节上,代码的可维护性、扩展性差。说起来,老板要的功能也都实现了,但只要稍微提出点新诉求,或面对一些更复杂的场景,就牵一发而动全身,到处都得修补删改。
重构,重构,还是重构,想想就崩溃。设计能力上不去,重构也仅仅能解决新诉求、新场景的问题,这样的重构,能坚持多久?而这些,就是设计模式要解决的问题。
如果你写了多年代码,编程功力却长进不大,面临稍复杂的代码设计和开发,写出的代码不仅杂乱,扩展性也很差。那你真该好好想一想,自己的认知和知识体系中,是否缺乏了设计模式这个环节。
要知道,代码光“够用”是远远不够的,还要“好用”。如果说数据结构和算法是教你如何写出高效的代码,那设计模式讲的就是如何写出可扩展、可读、可维护的高质量代码。算法 + 设计模式,奠定了一个工程师最基本的代码能力。
所以,设计模式与编码密切相关,能直接提高你的开发能力,是实打实的硬核技能。而且,设计模式更是大厂面试中的高频问题,大厂更加重视候选人的基本功,毕竟你代码写的好,后续的运营维护才会更简单、更敏捷。
从上面可知,设计模式是软件工程师的必备技能之一,在各种面试宝典里面是经常看到的,这意味着公司在面试过程(特别是编程面试)中会有非常大的几率碰到相关的题目。实际上,编程能力和设计技巧是对彼此很好的补充。一个好的程序员通常都是一个好的软件设计人员。他们知道怎么把一个问题分割成一段段代码或者软件设计,但这些能力和技巧并不能凭空而来。本学期课程的核心内容就是掌握熟悉 GoF 23 种设计模式,让大家从另外一个维度去理解面向对象设计的6大原则。
这23种设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解。当然,软件设计模式只是一个引导,在实际的软件开发中,必须根据具体的需求来选择。个人觉得,通俗地说,有4点是大家需要理解的:
- 设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
- 对于简单的程序,可能写一个简单的算法要比引入某种设计模式更加容易。
- 但是对于大型项目开发或者框架设计,用设计模式来组织代码显然更好。
- 本学期讲解的软件设计模式,用Java语言为例进行讲解,但是设计模式并不是 Java 的专利,它同样适用于 C++、Python、C#、Javascript 等其它面向对象的编程语言。
也可以看我大三以前的上这门课时的博客:
设计模式-软件设计模式概述
设计模式-GoF的23种设计模式
设计模式-优秀设计的特征
设计模式-如何正确使用设计模式
设计模式-开闭原则
设计模式-里氏替换原则
设计模式-依赖倒置原则
设计模式-单一职责原则
设计模式-接口隔离原则
设计模式-迪米特法则
设计模式-合成复用原则
设计模式-一句话总结软件设计七大原则
二十三种设计模式
创建型
单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式
结构型
代理模式、装饰器模式、适配器模式、外观模式、组合模式、享元模式、桥梁模式
行为型
策略模式、责任链模式、命令模式、中介者模式、模板方法模式、迭代器模式、访问者模式、观察者模式、解释器模式、备忘录模式、状态模式
三类设计模式的特点
六大原则
开放封闭(简称开闭)原则
Open-Close Principle(OCP):一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。目的就是保证程序的扩展性好,易于维护和升级。
开闭原则被称为面向对象设计的基石,实际上,其他原则都可以看作是实现开闭原则的工具和手段。意思就是:软件对扩展应该是开放的,对修改是封闭的,通俗来说就是,开发一个软件时,应该对其进行功能扩展,而在进行这些扩展时,不需要对原来的程序进行修改。
好处是:软件可用性非常灵活,扩展性强。需要新的功能时,可以增加新的模块来满足新需求。另外由于原来的模块没有修改,所以不用担心稳定性的问题。
单一职责原则
Single-Responsibilitiy Principle(SRP):对一个类而言,应该仅有一个引起它变化的原因。如果存在多于一个动机去改变一个类,那么这个类就具有多于一个的职责,就应该把多余的职责分离出去,再去创建一些类来完成每一个职责。
举个例子:一个人身兼数职,而这些事情相关性不大,甚至有冲突,那他就无法很好的解决这些问题职责,应该分到不同的人身上去做。
单一职责原则是实现高内聚低耦合的最好方法,没有之一。
1 | class CellPhone: |
上面的手机类虽然符合人们对手机的认识,但是实际上却拥有两个不同的职责:打电话、挂断电话和发短信、接受信息,因此引起它变化的原因就有多个,是比较脆弱的设计,应该将两种行为分离。
里氏代换原则
Liskov Substitution Principle:子类可以扩展父类的功能,但是不能改变父类原有的功能。
在第一条原则开放封闭原则中,主张“抽象”和“多态”。维持设计的封装性“抽象”是语言提供的功能,“多态”由继承语意实现。因此如何去度量继承关系中的质量?
答案是:继承必须明确确保超类(父类)所拥有的性质在子类中仍然成立。
在面向对象的思想中,一个对象就是一组状态和一系列行为的组合体。状态是对象的内在特性,行为是对象的外在特性。LSP表述的就是在同一继承体系中的队形应该具有共同的行为特征。
1 | class bird: |
在上述例子中,父类也就是对象鸟有两个行为也就是方法:eat()和fly(),在子类chicken中这两个行为也应该成立,但是现实中chicken是不能飞的,因此chicken中的fly()方法覆盖了父类中的方法。违反了VSP。
依赖倒置原则
Dependence Inversion Principle(DIP):是一个类与类之间的调用规则。这里的依赖就是代码中的耦合。高层模块不应该依赖底层模块,二者都应该依赖其抽象了;抽象不依赖细节;细节应该依赖抽象。接口编程。
主要思想就是:如果一个类中的一个成员或者参数成为一个具体的类型,那么这个类就依赖这个具体类型。如果在一个继承结构中,上层类中的一个成员或者参数为一个下层类型,那么就是这个继承结构高层依赖底层,就要尽量面向抽象或者接口编程。
举例:存在一个Driver类,成员为一个Car对象,还有一个driver()方法,Car对象中有两个方法start()与stop()。显然Driver依赖Car,也就是说Driver类调用了Car类中的方法。但是当增加Driver类对于Bus类的支持时(司机有需要开公交车),就必须更改Driver中的代码,就破坏了开放封闭原则。根本原因在于高层的的Driver类与底层的Car类仅仅的耦合在一起的。解决方法之一就是:对Car类和Bus类进行抽象,引入抽象类Automoble。而Car和Bus则是对Automobile的泛化。
经过这样的改造发现,原本的高层依赖底层,变成了高层与底层同时依赖抽象。这就是依赖倒转原则的本质。
接口隔离原则
接口隔离原则(Interface Segregation Principle):用于恰当的划分角色和接口,具有两种含义:1、用户不应该依赖它不需要的借口;2、类间的依赖关系应该建立在最小的的接口上。
将这两个定义概括为一句话:建立单一接口,代替庞大臃肿的接口。通俗来说就是:接口尽量细化,同时保证接口中的方法尽量的少。一个接口中包含太多的行为时,会导致它们与客户端的不正常依赖关系,要做的就是分离接口,从而实现解耦。
回到上述的单一职责原则,要求行为分离接口接口细化,感觉有些相同。但实际上,单一职责原则要求类与接口的职责单一,注重的是职责,没有要求接口尽量的少。
在接口隔离原则中,要求尽量使用多个专门的接口。专门的接口也就是提供给多个模块的接口。提供给几个模块就应该有几个接口,而不是建立一个臃肿庞大的接口,所有的模块都可以访问。
但是接口的设计是有限度的。接口的设计粒度越小系统越灵活,这是事实,但是接口太多这也就使得结构复杂,维护难度大。因此实际中,怎样把握就靠开发的经验和常识了。
迪米特原则
Law of Demeter(最小知识原则):一个对象应该对其他对象有最少的了解。通俗来说就是,一个类对自己需要耦合或者调用的类知道的最少,你类内部怎么复杂,我不管,那是你的事,我只知道你有那么多公用的方法,我能调用。
迪米特原则不希望类与类之间建立直接的接触。如果真的需要有联系,那么就通过它们的友元类来传达。举例来说:你需要买房子了,现在存在三座合适的楼盘A,B,C,但是你不必直接去楼盘买楼,而是在售楼处去了解情况。这样就减少了你(购房者)与楼盘两个类之间耦合。
但是应用迪米特原则很可能会造成一个后果:系统会存在大量的中介类,这些类(如上面的售楼处类)之所以存在是为了传递类之间的相互调用关系,这就一定会程度上增加了系统的复杂度。
迪米特原则核心观念就是:类间解耦,弱耦合。
示例代码
python实现23种设计模式示例代码地址如下:Github链接