持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
1、开闭原则(Open Close Principle)
书面表达:开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
个人理解:简单来说就是我在新公司原有的程序上开发新的需求,一般我们Java业务工作就是CRUD
这是最简单的链路,基本上没什么技术可言。但我们业界有一句话就是不要重复造轮子,所以这里调用的Mapper方法、Service方法都有可能被多条链路在使用,假设你现在想要开发的需求需要修改到这个公用的方法(假设是MethodA),那么你必须将这个MethodA连接的所有链路都要理清,甚至重新测试(隔壁测试岗位的已经在下单40米长刀了),最难受的情况就是牵一发而动全身,到时候别的开发、测试、产品、项目经理一起站你后面
如果这条链路只有一个业务用到,那么你修改一下也无所谓,反正现在是开发新需求嘛。但是在多个链路共用就得好好考虑,在这种情况下,copy一个MethodA_copy出来,新需求修改成MethodA_copy_new,而原来的链路不变,新需求的链路单独从MethodA_copy_new经过,既不会影响原来的功能,又可以解决新的开发需求。
如果直接修改,导致原来的功能也被修改
基于开闭原则可以采用的方法
但这种方法的缺陷也是比较明显的,就是会产生重复代码,所以需要下面的单一职责原则来减少重复代码,当然没办法去掉所有重复代码,根据《重构-改善既有代码的设计》这本书,基于开闭原则之下,有少部分的重复代码是可以接受的。
2、单一职责原则(Single Responsibility Principle)
书面表达:一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;
个人理解:单一接口原则其实就是比较简单了,就是理清每个接口每个类属于自己的职责,但各位程序员扪心自问一下自己有没有写过那种山一样的代码,或者说遇到过,反正我是有遇到过一个方法里写着上千行的代码……我就纳闷了,公司又不是看代码量给钱的。
遇到这种代码心里只有一个声音:快跑!千万不要回头拍照!当然了,微信钱包+支付宝钱包拦住了我。
很多人的流水账式代码,逻辑操作你们懂的,少至一两行,多至……很多很多行
那么这里之间的逻辑操作我们可以拆分,将同样或者类似的逻辑封装起来
原来的代码:
后来的代码:
两种方式对比起来有什么改变:
(1)后来的两个代码加起来总行数比原来更多了?是的没错,但是好处在什么地方,假设你浏览到method这个方法,原来你需要将这段逻辑一行一行读下去,最不济也要略读。那么用接口单一原则扩展出来之后呢,浏览method方法的doSomeOperations这里时,一行代码就可以知道干了什么,当然了,对编写doSomeOperations这个方法的程序员有一定要求,命名规范+注释。
(2)这里的OneUtil只是用来举例用的,在实际开发中不仅可以抽到Util中、也可以抽到Convert中,甚至是抽到同一个类/接口做成private权限的方法也可以,根据《重构-改善既有代码的设计》这本书,一个方法的行数最好用你的显示器刚好装下--方便程序员阅读,对机器来说,只要字节码正确就好,但是我们程序员毕竟是肉眼看的代码。
3、里氏代换原则(Liskov Substitution Principle)
书面表达:里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
个人理解:基类就是父类,派生类就是子类,在Java里面,理想的子类继承父类是,子类一定比父类强大;
class GrandFather{
public void alive() {
// 活着
}
}
class Father extends GrandFather{
@Override
public void alive() {
super.alive();
}
public void eat() {
// 吃饭
}
}
class Son extends Father{
@Override
public void alive() {
super.alive();
}
@Override
public void eat() {
super.eat();
}
public void run() {
// 跑步
}
}
里式替换原则只在两个类有继承关系存在时才会显现出来,基于理想情况下(子类一定比父类强大:子类必须扩展新的方法,尽量不重写父类具体的方法--抽象方法则必须重写),子类替换父类,不会影响原来的功能。
原来:
public static void method() {
// 前置逻辑
GrandFather man = new GrandFather();
man.alive();
// 后置逻辑
}
后来:这两个都不能影响alive的逻辑
public static void method() {
// 前置逻辑
GrandFather man = new Father();
man.alive();
// 后置逻辑
}
public static void method() {
// 前置逻辑
GrandFather man = new Son();
man.alive();
// 后置逻辑
}
当然了,子类在继承父类时,可以扩展(应该说必须扩展,不然继承父类有啥意义呢)。当然,话是撂在这里了,但我们实际开发……违反里式替换原则也是经常发生的事。
4、依赖倒转原则(Dependence Inversion Principle)
书面表达:这个原则是开闭原则的基础,依赖倒置原则是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
个人理解:关键点就是依赖抽象,不依赖具体(细节),实际开发中用到比较多的就是Service层了,我们敲代码基本上都是先敲 IService 接口,里面放着没有方法体的抽象方法(JDk8之后可以有方法体不是这里的重点),再敲个 ServiceImple 实现 IService接口,重写里面所有的方法;依赖倒转原则其实就是面向过程写法到面向对象写法转化
面向过程的写法:
class Test{
public static void main(String[] args) {
new Person().operate(new QQEmail(), "手持方天画戟战三英之吕奉先");
}
}
class QQEmail {
public String sendMsg(){
return "这是QQ邮箱发送的消息";
}
}
class Person {
public void operate(QQEmail qqEmail, String userName){
System.out.println(userName + "发送的消息:" + qqEmail.sendMsg());
}
}
那么这时候我们想用网易的163邮箱发送怎么办
(1)添加163邮箱
(2)修改Person客户程序
(3)修改调用程序
class Test{
public static void main(String[] args) {
// new Person().operate(new QQEmail(), "手持方天画戟战三英之吕奉先");
new Person().operate(new WYEmail(), "七进七出救阿斗之常山赵子龙");
}
}
class QQEmail {
public String sendMsg(){
return "这是QQ邮箱发送的消息";
}
}
class WYEmail {
public String sendMsg(){
return "这是网易163邮箱发送的消息";
}
}
class Person {
// public void operate(QQEmail qqEmail, String userName){}
public void operate(WYEmail wyEmail, String userName){
System.out.println(userName + "发送的消息:" + wyEmail.sendMsg());
}
}
那么改为面向对象写法呢
class Test{
public static void main(String[] args) {
new Person().operate(new QQEmail(), "温酒斩华雄之关云长");
}
}
interface Email{
String sendMsg();
}
class QQEmail implements Email{
public String sendMsg(){
return "这是QQ邮箱发送的消息";
}
}
class Person {
public void operate(Email email, String userName){
System.out.println(userName + "发送的消息:" + email.sendMsg());
}
}
如果想增加一个网易163邮箱 (1)添加163邮箱
(2)修改调用程序
class Test{
public static void main(String[] args) {
new Person().operate(new QQEmail(), "人生得意须尽欢之诗仙李太白");
new Person().operate(new WYEmail(), "人生得意须尽欢之诗仙李太白");
}
}
class WYEmail implements Email{
public String sendMsg(){
return "这是网易163邮箱发送的消息";
}
}
从这个例子来看,用了依赖导致原则之后的面向对象,好像也没方便到哪里去……
就仅仅减少了一步,这是因为这里代码量不多,对于实际开发中,代码量远远比这里多,而且对于Person类来说不需要再修改,也符合开闭原则。
5、接口隔离原则(Interface Segregation Principle)
书面表达:这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
个人理解:基本上每个系统都会有用户这玩意儿,比如User/UserInfo等,有点B Number的都知道数据库存关于用户的也会有对应的表,那么问题来了,用户相关的东西是一堆堆的,姓名、性别、身份证号、身高、体重、账号密码等等,当然了不一样的系统还是会有区别,在后端代码基本上都有一个UserService接口来操作,随着系统的不断开发,就会越来越臃肿,而接口隔离原则就是使用多个隔离的接口代替臃肿的接口。
interface UserService{
// 账号
void operateAccount();
// 密码
void operatePassword();
// 姓名
void operateUserName();
// 性别
void operateGender();
// 身份证号
void operateIdentity();
// 地址
void operateAddress();
// 邮箱
void operateEmail();
// ......假设还有很多操作
}
这时候问题就很明显了,我想要找一个实现类去实现这个接口……好家伙,我得将里面所有的方法重写,那么我再找另一个类去实现,又得全部重写
那么这时候我刻意分成两个接口(也可以说是运用了单一接口原则),将账号安全的放一个接口,将一些常用信息放另一个接口,这样实现类实现接口可以选择性实现
interface UserAccountService{
// 账号
void operateAccount();
// 密码
void operatePassword();
// 邮箱
void operateEmail();
}
interface UserInfoService{
// 姓名
void operateUserName();
// 性别
void operateGender();
// 身份证号
void operateIdentity();
// 地址
void operateAddress();
}
6、迪米特法则,又称最少知道原则(Demeter Principle)
书面表达:最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
个人理解:强调只和朋友说话,不和陌生人说话,强调的是降低耦合(永远的目的)
作为一个开发仔,招聘我的是这家公司的老板,但实际上从笔试到面试到发工资,跟我直接对接的都是人事部门,而不是老板,老板是间接招聘我的,根据迪米特法则就是,我一个开发仔不能跟老板有任何接触。
class Test{
public static void main(String[] args) {
PitifulDeveloper pitifulDeveloper = new PitifulDeveloper();
pitifulDeveloper.work();
// 跟人事部门直接接触
System.out.println("开发仔获得报酬:"+pitifulDeveloper.getSalary(new PersonnelDept()));
}
}
// 老板
class Boss{
// 真正发工资
public String payroll(){
return "3000¥";
}
}
// 人事部门
class PersonnelDept{
// 直接发工资
public String payroll(){
// 真正发工资
return new Boss().payroll();
}
}
// 开发
class PitifulDeveloper{
// 工作
public void work(){
System.out.println("开发仔敲代码");
}
// 取得报酬
public String getSalary(PersonnelDept personnelDept){
return personnelDept.payroll();
}
}
7、合成复用原则(Composite Reuse Principle)
书面表达:合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
个人理解:众所周知,java四大特性包括:抽象、封装、继承、多态,众所又周知,Java尽可能不用继承……因为Java只支持单继承而不可多继承。
(1)继承破坏了封装,因为将父类的细节暴露给了子类;(子类能用父亲的一切非privete方法)
(2)继承会提高程序的耦合性,父类对自身修改会影响到子类;(子类被迫用父类修改过的方法)
(3)简单来说继承就是透明的,而组合是黑盒子,调用一方不需要知道被调用一方的修改;
其实这个还蛮容易理解的,在实际开发一般用的也是合成/聚合,最经典的就是MVC开发了 经典spring/springboot
@Controller
class UserController{
@Resource
private UserService userService;
public void method(){
userService.method();
}
}
@Service
class UserService{
public void method(){
System.out.println("知天易,逆天难");
}
}
担心有些小伙伴还没学框架,那么用其他的例子
继承方式:
class UserFather{
public String chant(){
return "爱你孤身走暗巷,爱你不跪的模样,爱你对峙过绝望不肯哭一场";
}
}
class UserSon extends UserFather{
public String chant(){
return new UserFather().chant();
}
}
class Test{
public static void main(String[] args) {
System.out.println("小明在唱:"+new UserSon().chant());
}
}
本来小学生小明在喜欢唱《孤勇者》,但是他爸爸除了孤勇者之外还喜欢唱喜羊羊,虽然小明觉得自己已经很成熟了,不能喜欢喜羊羊了,但是既然继承了父亲,那自己只能被迫会唱。
public String chantOther(){
return "别看我只是一只羊~";
}
那么用组合的方式:组合方式可以通过方法传参、构造器、setter方法等;用了组合的方式可以确定小明只会唱孤勇者了,爸爸即使会唱其他任何儿歌都不会影响到小明了。
class UserFather{
public String chant(){
return "爱你孤身走暗巷,爱你不跪的模样,爱你对峙过绝望不肯哭一场~";
}
public String chantOther(){
return "别看我只是一只羊~";
}
}
class UserSon{
public String chant(UserFather father){
return father.chant();
}
}
class Test{
public static void main(String[] args) {
System.out.println("小明在唱:"+new UserSon().chant(new UserFather()));
}
}
当然这里其实看不出什么降低耦合,如果抽象成接口就可以看出来了
class UserMother implements Person{
public String chant(){
return "别看我只是一只羊~";
}
}
class UserFather implements Person{
public String chant(){
return "爱你孤身走暗巷,爱你不跪的模样,爱你对峙过绝望不肯哭一场~";
}
public String chantOther(){
return "别看我只是一只羊~";
}
}
interface Person{
String chant();
}
class UserSon{
public String chant(Person person){
return person.chant();
}
}
class Test{
public static void main(String[] args) {
System.out.println("小明在唱:"+new UserSon().chant(new UserFather()));
System.out.println("小明在唱:"+new UserSon().chant(new UserMother()));
}
}
可以看到小明无论接收父亲还是母亲都不用修改自身代码了。这是利用了依赖倒转原则。
总结:一边查资料一边码字码了大半天,不一定是特别好的文章,但主要的目的是用自己语言理解、记住这七大原则。接收各位读者的意见与建议,但是不接受喷。希望各位看过之后有收获~