设计模式七大设计原则

文章目录

  • 设计模式
    • 七大设计原则
      • 开闭原则
      • 里氏替换原则
      • 依赖倒置原则
      • 接口隔离原则
      • 迪米特法则-最少知道原则
      • 单一职责原则
      • 合成复用原则

设计模式

面向对象的三个基本特征:

  1. 继承
  2. 封装
  3. 多态

设计模式体现了代码的耦合性、内聚性、可维护性、可扩展性、重用性、灵活性。

  1. 代码重用性:相同功能代码不用多次编写
  2. 可读性
  3. 可扩展性:添加新功能非常方便,可维护
  4. 可靠性:当我们添加新功能不影响原有的功能
  5. 使程序呈现高内聚、低耦合的特性

七大设计原则

开闭原则

原则:一个软件实体如类、模块、函数应该对拓展开发、对修改关闭

在程序需要拓展的时候,不能去修改原有代码,为了易于维护我们应该更多的学会使用接口和抽象类,在设计的时候尽量适应变化,提高系统的稳定性、灵活性。

场景:书店销售书籍

public class OpenClosePrinciple {interface Book{String getName();double getPrice();String getAuthor();}class NovelBook implements Book{private String name;private String author;private double price;public NovelBook(String name, String author, double price) {this.name = name;this.author = author;this.price = price;}@Overridepublic String getName() {return this.name;}@Overridepublic double getPrice() {return this.price;}@Overridepublic String getAuthor() {return this.author;}}@Testvoid client(){NovelBook novelBook = new NovelBook("斗罗大陆", "唐家三少", 55.8);System.out.println(novelBook.getName() + " " + novelBook.getAuthor() + " " + novelBook.getPrice());}}

假如我们想在双十一对书籍进行打折。

实现方案:

  1. 修改接口:在Book类中新增一个打折接口;但是这样做其他所有实现类都需要改动。

  2. 修改实现类方法:NovelBook类修改原有的getPrice()方法,实现打折逻辑,但是这样做的话违背了开闭原则,如果双十一过去了是不是还有再把代码回退??

  3. 实现类新增方法:NovelBook类新增getDiscountPrice()方法,这个看起来不错,但是一个类中提供两个获取价格的接口对调用者不是很友好,而且随着业务需求越来越多这个类不断添加新的方法,本来我们设计这个类的目的只是想获取书籍的基本信息,但是现在又混合了很多业务逻辑,那么这个类就违背了单一职责原则。

  4. 派生出一个打折类:该类继承NovelBook基类,用来专门处理打折逻辑,这样不用修改原有实现类不会对上层调用者产生影响,利用拓展实现功能,其实这种对基类已实现的方法进行覆盖违背了里氏替换原则,如果想不违背里氏替换原则只能在派生类中添加新的方法,并且在基类的方法中添加final保证不会被重写,这里我们只是讲解开闭原则,就不考虑那么多。

        class DiscountNovelBook extends NovelBook{public DiscountNovelBook(String name, String author, double price) {super(name, author, price);}@Overridepublic double getPrice() {System.out.println("打折后价格");return super.getPrice() * 0.88;}}@Testvoid client(){//老的业务逻辑正常调用NovelBook novelBook = new NovelBook("斗罗大陆", "唐家三少", 55.8);System.out.println(novelBook.getName() + " " + novelBook.getAuthor() + " " + novelBook.getPrice());System.out.println("----------------");//新促销业务调用打折代码DiscountNovelBook discountNovelBook = new DiscountNovelBook("斗罗大陆", "唐家三少", 55.8);System.out.println(discountNovelBook.getName() + " " + discountNovelBook.getAuthor() + " " + discountNovelBook.getPrice());}
    

    引用:https://www.jianshu.com/p/d36da4f136c4

里氏替换原则

原则:所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则与继承特性密切相关。

相信我们经常用到继承,那你知道继承有哪些优点?

  1. 子类拥有父类的所有方法和属性,从而减少创建类的工作量。
  2. 提高了代码的重用性。
  3. 提高了代码的拓展性,子类不但拥有父类的所有功能,还可以添加自己的功能。

缺点:

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

里氏替换原则对继承进行了规则上的约束

  1. 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的已实现的方法。
  2. 子类可以增加自己特有的方法。
  3. 当子类重载父类的方法时,方法的形参要比父类的输入参数更宽松。(只能重载不能重写)
  4. 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

约束详细解释:

  1. 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的已实现的方法。

    首先子类必须实现父类的抽象方法,不然编译无法通过;

    如果重写父类的方法,当用子类替代父类后会出现意想不到的错误,如果想避免这种问题我们可以将父类不得重写的方法添加final关键字,这样在语法层面就可以避免违反里氏替换原则;

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

    子类可以对父类功能进行拓展。

  3. 当子类重载父类的方法时,方法的形参要比父类的输入参数更宽松

    定义一个父类Father#run(Map map)利用子类重载run方法Son#run(HashMap map)如果此时HashMap map = new HashMap()作为参数传入son.run(map)

    public class LiskovSubPrinciple {public class Parent {void run(Map map){System.out.println("父类执行...");};}public class Son extends Parent{void run(HashMap map){System.out.println("子类执行...");}}@Testvoid run(){HashMap<Object, Object> map = new HashMap<>();Parent parent = new Parent();parent.run(map);Son son = new Son();son.run(map);}//结果//父类执行...//子类执行...
    }
    

    结果会发现用同一个参数子类和父类调用结果却不同,会出现程序调用混乱,由于子类形参为HashMap导致父类方法没有被重写的情况下调用了子类。

    如果我们把子类的形参范围设置的比父类更大就不会出现上述问题。

    public class LiskovSubPrinciple2 {public class Parent {void run(HashMap map){System.out.println("父类执行...");};}public class Son extends Parent{void run(Map map){System.out.println("子类执行...");}}@Testvoid run(){HashMap<Object, Object> map = new HashMap<>();Parent parent = new Parent();parent.run(map);Son son = new Son();son.run(map);}//父类执行...//父类执行...
    }
    

    这次会发现两次调用结果一致。

  4. 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格

    如果子类的返回值类型比父类范围更广编译无法通过。

依赖倒置原则

原则:面向接口编程,不要面向实现。

场景:小明去不同的商店购物电器,但是商店有很多种

public class DependenceInversionPrinciple {public interface Shop{void sell();}public class GomeShop implements Shop{@Overridepublic void sell() {System.out.println("国美商店...");}}public class SuningShop implements Shop{@Overridepublic void sell() {System.out.println("苏宁易购...");}}public class People{String name;public People(String name) {this.name = name;}void shopping(Shop shop){System.out.print(this.name);shop.sell();}}@Testvoid  testMain(){People people = new People("小明购物:");people.shopping(new GomeShop());people.shopping(new SuningShop());/*小明购物:国美商店...小明购物:苏宁易购...*/}
}

如果将方法形参的Shop接口改成实现类需要新增很多种方法不符合依赖倒置原则。

接口隔离原则

原则:使用多个隔离的接口比使用单一接口更好。

场景:现在有一个接口声明了1、2、3、4这四个方法,A类需要用到1、2方法,B类需要用到3、4方法,C类需要用到1、2、3、4方法。

public class InterfaceSegregationPrinciple {public interface I{void method1();void method2();void method3();void method4();}/*** A类需要用到方法1 2*/public class A implements I{@Overridepublic void method1() {System.out.println("实现方法1");}@Overridepublic void method2() {System.out.println("实现方法2");}@Overridepublic void method3() {//不实现}@Overridepublic void method4() {//不实现}}public class B implements I{@Overridepublic void method1() {//不实现}@Overridepublic void method2() {//不实现}@Overridepublic void method3() {System.out.println("实现方法3");}@Overridepublic void method4() {System.out.println("实现方法4");}}public class C implements I{@Overridepublic void method1() {System.out.println("实现方法1");}@Overridepublic void method2() {System.out.println("实现方法2");}@Overridepublic void method3() {System.out.println("实现方法3");}@Overridepublic void method4() {System.out.println("实现方法4");}}
}

可以看出A和B类存在很多空方法,这种方式对调用者来说不是很友好,并且接口很臃肿,不符合接口隔离原则。

让我们重新设计一下

public class InterfaceSegregationPrinciple2 {public interface I{void method1();void method2();}public interface K{void method3();void method4();}/*** A类需要用到方法1 2*/public class A implements I{@Overridepublic void method1() {System.out.println("实现方法1");}@Overridepublic void method2() {System.out.println("实现方法2");}}public class B implements K{@Overridepublic void method3() {System.out.println("实现方法3");}@Overridepublic void method4() {System.out.println("实现方法4");}}public class C implements I,K{@Overridepublic void method1() {System.out.println("实现方法1");}@Overridepublic void method2() {System.out.println("实现方法2");}@Overridepublic void method3() {System.out.println("实现方法3");}@Overridepublic void method4() {System.out.println("实现方法4");}}
}

将接口拆分为两个接口,这样每个类只需要实现方法不需要空方法,符合接口隔离原则。

迪米特法则-最少知道原则

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

原则:只和直接的朋友交流、减少对朋友的了解

概念:

​ 直接朋友:成员变量、方法入参、返回参数;例如A类方法参数是B类对象、返回值是C类对象则A与B、C是直接朋友。

​ 朋友:两个对象耦合就会成为朋友;例如A类中方法代码块中使用了B类对象,AB是朋友。


只和直接的朋友交流

应用场景:

​ 现在有三个角色老师Teacher、班长GroupLeader、学生Student,老师让班长去统计全班人数。

​ 实现一:

public class DemeterPrinciple {private static class Teacher{/*** 发号命令* @param groupLeader*/int command(GroupLeader groupLeader){//创建学生数组Student[] students = new Student[20];for (int i = 0; i < 20; i++){students[i] = new Student(i);}//让班长统计人数return groupLeader.count(students);}}private static class GroupLeader{int count(Student[] students){return students.length;}}private static class Student{int id;public Student(int id) {this.id = id;}}public static void main(String[] args) {Teacher teacher = new Teacher();int total = teacher.command(new GroupLeader());System.out.println("人数: " + total);}
}

让我们来分析一下上面代码:

​ Teacher类朋友关系:GroupLeader(直接朋友)、Student(朋友)

​ 由于Teacher类中和非直接朋友有关系,这就违背了demeter法则。方法是类中的一个行为,类竟然不知道自己的行为与其他类产生依赖关系,这严重违反了迪米特法则。

让我们做如下修改:

public class DemeterPrinciple2 {private static class Teacher{/*** 发号命令* @param groupLeader*/int command(GroupLeader groupLeader){//让班长统计人数return groupLeader.count();}}private static class GroupLeader{Student[] students;public GroupLeader(Student[] students) {this.students = students;}int count(){return students.length;}}private static class Student{int id;public Student(int id) {this.id = id;}}public static void main(String[] args) {Teacher teacher = new Teacher();//创建学生数组Student[] students = new Student[20];for (int i = 0; i < 20; i++){students[i] = new Student(i);}int total = teacher.command(new GroupLeader(students));System.out.println("人数: " + total);}
}

再看看Teacher、GroupLeader类中不依赖朋友而只有直接朋友,类之间解耦了。


减少对朋友的了解

尽量减少一个类对外暴露的方法。

场景:人使用咖啡机获得咖啡。

public class DemeterPrinciple3 {private static class People{private CoffeeMachine coffeeMachine;public People(CoffeeMachine coffeeMachine) {this.coffeeMachine = coffeeMachine;}void takeCoffee(){coffeeMachine.addCoffeeBean();coffeeMachine.addWater();coffeeMachine.makeCoffee();}}private static class CoffeeMachine{public void addCoffeeBean(){System.out.println("添加咖啡豆");}public void addWater(){System.out.println("加水");}public void makeCoffee(){System.out.println("制作咖啡");}}public static void main(String[] args) {CoffeeMachine coffeeMachine = new CoffeeMachine();People people = new People(coffeeMachine);people.takeCoffee();}}

上述代码中People类虽然只有一个CoffeeMachine类的直接朋友,但是他对这个朋友过多的了解,其实我们不用关系怎么去制作咖啡过程,结合现实中的例子拿到我们使用咖啡机需要按三个按钮才能出咖啡?如果咖啡机底层逻辑发生改变我们人还要去按更多的按钮?

让我们做下优化

public class DemeterPrinciple4 {private static class People{private CoffeeMachine coffeeMachine;public People(CoffeeMachine coffeeMachine) {this.coffeeMachine = coffeeMachine;}void takeCoffee(){coffeeMachine.makeCoffee();}}private static class CoffeeMachine{private void addCoffeeBean(){System.out.println("添加咖啡豆");}private void addWater(){System.out.println("加水");}private void doMakeCoffee(){System.out.println("制作咖啡");}public void makeCoffee(){addCoffeeBean();addWater();doMakeCoffee();}}public static void main(String[] args) {CoffeeMachine coffeeMachine = new CoffeeMachine();People people = new People(coffeeMachine);people.takeCoffee();}}

这样修改以后人不用去关系咖啡机的制作过程,只需要咖啡机提供的一个按钮就可以喝到美美的咖啡啦!

引用:https://www.jianshu.com/p/4b244f132439

单一职责原则

原则:一个类只负责一项职责,同样适用于方法

通常情况下,我们应该遵守单一职责原则,只有当逻辑足够简单,可以通过在类中添加不同方法违反单一职责原则。

public class SingleResponsibilityPrinciple {/*** 交通工具类* 逻辑简单直接在类中添加方法即可*/class Vehicle{void run(String car){System.out.println(car + " run");}void fly(String airplane){System.out.println(airplane + " fly");}}@Testvoid testRun(){Vehicle vehicle = new Vehicle();vehicle.run("奔驰");vehicle.fly("播音747");}
}

合成复用原则

原则:将已有的对象加入新对象中,作为新对象的成员对象来实现,新对象可以调用已有对象的功能,达到复用。

在java中优先使用组合而不是继承。如果要使用继承关系必须严格遵循里氏替换原则,而合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现。

复用分为:继承复用、合成复用

继承复用缺点:

  • 会破坏类的封装性,继承会将父类的实现细节暴露给子类,又称“白箱复用”。
  • 父类与子类耦合度高,父类发生修改会影响子类功能,不利于类的拓展与维护。
  • 限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,运行时不能发生变化。

采用组合复用优点:将已经有对象作为成员对象成为新对象的一部分

  • 维护了类封装性,成员对象不会暴露实现细节,这种方式称为“黑箱复用”
  • 新旧类耦合度低,只需要利用成员对象接口访问。
  • 复用的灵活性,这种复用可以在运行时动态进行,新对象可以动态地引用于成员对象类型相同的对象。

场景:汽车按照动力分为汽油汽车、电动汽车等,颜色有红色、黑色、白色等,如果同时考虑这两种组合我们应该怎么实现?

如果利用继承方式:

在这里插入图片描述

利用继承方式会出现多个子类,随着汽车种类越多子类会膨胀,并且每添加一个颜色都得继承汽油、电动这两个类都要修改源代码,如果利用组合可以避免这种问题。

在这里插入图片描述

引用:http://c.biancheng.net/view/1333.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从框架源码中学习结构型设计模式

文章目录从框架源码学习结构型设计模式适配器模式应用实例案例一&#xff1a;dubbo框架日志适配器Logger接口日志实现类Logger适配器接口LoggerAdapter实现类Logger日志工厂桥接模式应用场景案例&#xff1a;dubbo源码-远程调用模块channelHandler设计ChannelHandler是一个SPI拓…

MDC日志logback整合使用

MDC日志logback整合使用 为什么使用MDC记录日志&#xff1f; 场景&#xff1a; 由于我的搜索服务并发量比较高&#xff0c;而处理一次搜索请求需要记录多个日志&#xff0c;因此日志特别多的情况下去查一次搜索整个日志打印情况会比较复杂。 解决方案&#xff1a; 可以使用用…

如何合理的配置线程数?

文章目录题记Java并发编程实战美团技术团队追求参数设置合理性线程池参数动态化题记 我想不管是在面试中、还是工作中&#xff0c;我们总会面临这种问题&#xff0c;那么到底有没有一种计算公式去告诉我们如何去配置呢&#xff1f; 答案是&#xff1a;没有 想要合理的配置线…

基于CompletableFuture并发任务编排实现

文章目录并发任务编排实现不带返回值/参数传递任务串行执行并行执行并行执行-自定义线程池阻塞等待&#xff1a;多并行任务执行完再执行任意一个任务并发执行完就执行下个任务串并行任务依赖场景带返回值/参数传递任务带返回值实现串行执行多线程任务串行执行对任务并行执行&am…

搜索研发工程师需要掌握的一些技能

文章目录基础语言数据结构与算法工程方面搜索相关搜索主要模块电商搜索流程分词相关搜索召回相似度算法相关词推荐排序相关国美搜索搜索算法工程师需要掌握的技能基础 语言 大部分公司用的是Solr、ElasticSearch&#xff0c;都是基于Java实现的&#xff0c;因此熟悉掌握Java语…

Flink入门看完这篇文章就够了

文章目录第一章&#xff1a;概述第一节&#xff1a;什么是Flink&#xff1f;第二节&#xff1a;Flink特点&#xff1f;第三节&#xff1a;Flink应用场景&#xff1f;第四节&#xff1a;Flink核心组成第五节&#xff1a;Flink处理模型&#xff1a;流处理和批处理第六节&#xff…

word小结

域代码/域结果显示设置 word选项---->>高级------>>显示域代码而非域值将样式传给其它文件使用 首先启动Word打开包含这些样式的一个文件&#xff0c;然后选择“工具”---->“模板和加载项”。在弹出的对话框中单击“管理器”按钮。在弹出的“管理器”对话框中&…

线程属性总结

今天面试那哥们问起线程属性&#xff0c;me竟然就说出了一个&#xff0c;囧 学习&#xff1a;http://blog.csdn.net/zsf8701/article/details/7842392 http://blog.csdn.net/jxhnuaa/article/details/3254299 http://blog.sina.com.cn/s/blog_9bd573450101hgdr.html int pthre…

百度2015校园招聘软件开发笔试题及答案

简单题&#xff08;本题共30分&#xff09; 请简述Tcp-ip的3次握手以及4次挥手过程&#xff1f;并解释为何关闭连接需要4次挥手(10分) 详细答案参见TCP/IP协议三次握手与四次握手流程解析 TCP三次握手、四次挥手过程如下: 通常情况下&#xff0c;一个正常的TCP连接&#xf…

linux ps 命令使用

Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照&#xff0c;就是执行ps命令的那个时刻的那些进程&#xff0c;如果想要动态的显示进程信息&#xff0c;就可以使用top命令。 linux上进程有5种状态 ps命令使…

UML序列图总结

序列图主要用于展示对象之间交互的顺序。 序列图将交互关系表示为一个二维图。纵向是时间轴&#xff0c;时间沿竖线向下延伸。横向轴代表了在协作中各独立对象的类元角色。类元角色用生命线表示。当对象存在时&#xff0c;角色用一条虚线表示&#xff0c;当对象的过程处于激活…

UML用例图总结

用例图主要用来描述 用户、需求、系统功能单元 之间的关系。它展示了一个外部用户能够观察到的系统功能模型图。 【用途】&#xff1a;帮助开发团队以一种可视化的方式理解系统的功能需求。 用例图所包含的元素如下&#xff1a; 1. 参与者(Actor) 表示与您的应用程序或…

Linux网络编程常见面试题

概述 TCP和UDP是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。 TCP&#xff1a;传输控制协议&#xff0c;一种面向连接的协议&#xff0c;给用户进程提供可靠的全双工的字节流&#xff0c;TCP套接口是字节流套接口(stream socket)的一种。UDP&#xff1a;用户…

linux动态库查找路径以及依赖关系梳理

编译时与运行时库的路径 linux下&#xff0c;编译时与运行时库的搜索路径是不同的 运行时动态库的路径搜索顺序 LD_PRELOAD环境变量&#xff0c;一般用于hack 编译目标代码时指定的动态库搜索路径(指的是用 -wl,rpath 或-R选项而不是-L)&#xff0c;readelf -d命令可以查看编…

eclipse--android开发环境搭建教程

引言 在windows安装Android的开发环境不简单也说不上算复杂&#xff0c;但由于国内无法正常访问google给android开发环境搭建带来不小的麻烦。现将本人搭建过程记录如下&#xff0c;希望会对投身android开发的小伙伴有所帮助。 android开发环境部署过程 安装JDK环境 下载安装…

eclipse--python开发环境搭建

pydev插件介绍 PyDev is a Python IDE for Eclipse pydev官方网站&#xff1a;http://www.pydev.org/ 在Eclipse中安装pydev插件 启动Eclipse, 点击Help->Install New Software… 在弹出的对话框中&#xff0c;点Add 按钮。 Name中填:Pydev, Location中填http://pydev.or…

Win7虚拟无线AP以及Android手机抓包

设备要求 Windows7操作系统装有无线网卡的笔记本或台式机无线网卡必须支持“承载网络” 查看无线网卡是否支持“承载” 方法一: 开始菜单→所有程序→附件→命令提示符→右键“以管理员权限运行”; 键入命令“netsh wlan show drivers”,查看“支持承载网络”这一项,如果是…

CMD命令之BAT脚本路径信息

CD命令解疑 cd是chdir的缩写&#xff0c;命令详解参见cd /? 可以看到/d参数的解释如下&#xff1a; 使用 /D命令行开关&#xff0c;除了改变驱动器的当前目录之外&#xff0c;还可改变当前驱动器。 通常我们在xp系统中打开cmd窗口时&#xff0c;会显示 C:\Documents and Se…

【ubuntu 22.04】安装vscode并配置正常访问应用商店

注意&#xff1a;要去vscode官网下载deb安装包&#xff0c;在软件商店下载的版本不支持输入中文 在ubuntu下用火狐浏览器无法访问vscode官网&#xff0c;此时可以手动进行DNS解析&#xff0c;打开DNS在线查询工具&#xff0c;解析以下主机地址&#xff08;复制最后一个IP地址&a…