设计模式概述以及七大设计原则

2024-04-23 10:48

本文主要是介绍设计模式概述以及七大设计原则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 什么是设计模式
  • 设计模式的作用
  • 设计模式使用原则
  • 设计模式七大原则
      • 单一职责原则
      • 接口隔离原则
      • 依赖倒置原则
      • 里氏替换原则
      • 开闭原则
      • 最少知道原则
      • 合成复用原则

什么是设计模式

设计模式是指经过多年编程实践验证的,针对面向对象语言的一套有用的编程模式。

设计模式的作用

正确的使用设计模式可以降低代码间的耦合层度,便于代码的扩展以及维护。

设计模式使用原则

使用设计模式前应该明白:

  • 设计模式是有用的,前提是你在正确的场景下使用正确的设计模式.23种设计模式针对23种不同的场景,应该根据自己的场景来选择使用设计模式。
  • 设计模式的使用可能是有利有弊的,比如在保证系统的可扩展性的同时,可能导致类个数的快速膨胀,因此不要为了使用设计模式而使用,避免模式滥用。
  • 设计模式的背后是七大设计原则,七大设计原则的背后就是一个字:分!!

设计模式七大原则

单一职责原则

每个方法,每个类,每个框架都只做一件事.尽可能的切割方法,类,避免将不同的功能代码块放在一个函数中.
这样也就要求方法尽可能的短,类尽可能的精简.根据马丁福勒的理论:一个函数甚至不应该超过6行,虽然我觉得有点太极端,但是往这方面追求总是没错的。

  • 场景:
    读取文件然后判断其中有多少个字符,应该将加载文件和分割字符分在两个函数,便于后面变化带来的需求调整.如果需要判断单词个数,句子个数,可以复用加载文件的函数.

接口隔离原则

在定义接口的时候,要尽量小,将真正需要放在一起的接口放在一起.比如我写一个动物的接口:

interface Animal{void eat();void fly();void swim();
}

这个接口明显没有达到接口隔离的标准,因为并不是所有的动物都会飞,都会游泳,所以最好的方式就是:

interface Eatable{void eat();
}
interface Fliable{void fly();
}
interface Swimable{void swim();
}

这样就将接口之间隔离开来,不定义总接口.这也暗合了Go中的接口尽量小的设计思想.

依赖倒置原则

上层代码不应该依赖下层,他们都应该依赖抽象.上层是指调用其他方法的是上层,被其他方法调用的是下层.
反例:

public class Dog {public Dog() {}void eat() {System.out.println("Dog is eating...");}
}public class Person {private Dog dog;public Person(Dog dog) {this.dog = dog;}void feed() {System.out.println("Person starts to feed dog...");this.dog.eat();}
}public class Main {public Main() {}public static void main(String[] args) {Dog dog = new Dog();Person person = new Person(dog);person.feed();}
}

上述代码可以完成人喂狗的操作,但是如果有一天,想要人喂猫,应该怎么处理呢?如果沿着上述代码的思路,就是:

public class Cat {void eat() {System.out.println("Cat is eating...");}
}public class Dog {void eat(){System.out.println("Dog is eating...");}
}public class Person {private Dog dog;private Cat cat;public Person(Dog dog) {this.dog = dog;}public Person(Cat cat) {this.cat = cat;}void feedDog() {System.out.println("Person starts to feed dog...");dog.eat();}void feedCat() {System.out.println("Person starts to feed cat...");cat.eat();}
}public class Main {public static void main(String[] args){Dog dog = new Dog();Person person = new Person(dog);person.feedDog();Person person1 = new Person(new Cat());person1.feedCat();}
}

这倒是满足了现在的需求,但如果现在要求person喂鸟,喂鱼,喂…等,难道每个都要新建类然后改动Person里面的函数?这显然不是好的设计.此时每当下游发生改动,上层代码都要更改,违反了依赖倒转原则.

此时正确的写法为:新建一个Animal接口,让Person依赖于Animal接口,下层的动物如猫狗等应该实现该Animal接口,此时原来的上下层都依赖该接口Animal.
只要实现该接口的类就可以传进去.

里氏替换原则

所有父类对象出现的位置都可以使用子类对象无条件替换,且要保证业务不受影响。
在继承中有两个限制:

  • 子类方法不允许比父类方法的访问限制更严格
  • 子类方法不允许比父类方法抛出更多的异常
    上述的限制其实就是在要求继承关系要满足里式替换原则。

关于继承,我们通常说他们需要满足is-a关系,但是里式替换要求我们不光要满足is-a关系,还要满足业务场景逻辑的正确。所有,正方形是长方形吗?不一定。
在下面的业务逻辑下,计算长方形的面积,此时Squre确实是Rectangle的子类,满足继承关系,因为他们的业务逻辑是一样的。

class Rectangle {private int length;private int width;public int area() {return length * width;}
}class Squre extends Rectangle {private int length;@Overridepublic int area() {return length * length;}
}

但下面的业务逻辑下就不成立了:

public class Main {public static void main(String[] args) {Rectangle rectangle = new Rectangle();rectangle.setWidth(12);rectangle.setLength(20);Util.op(rectangle);}
}class Rectangle {private int length;private int width;public int getLength() {return length;}public void setLength(int length) {this.length = length;}public int getWidth() {return width;}public void setWidth(int width) {this.width = width;}public int area() {return length * width;}
}class Squre extends Rectangle {private int length;@Overridepublic int getLength() {return length;}@Overridepublic void setLength(int length) {length = length;}@Overridepublic int getWidth() {return length;}@Overridepublic void setWidth(int width) {length = width;}@Overridepublic int area() {return length * length;}
}class Util {/*业务场景:不断累加宽,直到它比长多1为止*/public static void op(Rectangle rectangle) {while (rectangle.getWidth() <= rectangle.getLength()) {rectangle.setWidth(rectangle.getWidth() + 1);System.out.println(rectangle.getLength()+ "      "+ rectangle.getWidth());}}
}

在上面的情形下,如果将父类main函数(业务逻辑实现处)Rectangle对象替换为Squre对象,会照成代码的死循环,这显然是不符合里式替换原则的。所以在这种情况下,并不能简单的认为正方形是长方形的子类。

继承关系中,除了看是否有is-a关系外,更重要的是在替换的情况下,代码的业务逻辑是否准确无误。

开闭原则

对扩展开放,对修改关闭.开闭原则在七大原则中优先级很高,有时会为了符合该原则而牺牲其他原则.
加新功能的同时必须保证原有代码的稳定,保证原有功能的稳定.

如果全是自己的源代码,不允许修改原来的源代码,如果是导入的jar包,你甚至想改也改不到.

程序员分为两种,以某框架为例,可以分为框架的作者和框架的用户.但是这两个角色都应该严格符合开闭原则.

最少知道原则

  • 上层类对下层类的实现细节要知道的越少越好
    主要体现封装的思想。一个类应该尽量将自己的实现细节留在自己的内部,不要暴露太多细节给其他类。
    现在有一个Computer类,现在要模仿它关机的执行流程,反例代码如下:
public class Computer {public void saveData() {//保存数据System.out.println("saveData");}public void closeScreen() {//关闭屏幕System.out.println("closeScreen");}public void killProcess() {//杀死进程System.out.println("killProcess");}public void powerOff() {//关掉电源System.out.println("powerOff");}
}class Person{private Computer computer = new Computer();public void shutDownComputer() {//太多实现细节在这里暴露computer.saveData();computer.killProcess();computer.closeScreen();computer.powerOff();}
}

如上面的例子,Person对象只是想关掉电脑,他并不需要知道电脑关机的具体流程,这样的架构不但让代码冗余,而且可能会带来其他的问题。倘若客户并不清楚电脑关闭的操作步骤呢?可以直接关闭电源而没有保存数据呢?这都是暴露太多细节带来的问题。正确的做法应该是:

public class Computer {private void saveData() {System.out.println("saveData");}private void closeScreen() {System.out.println("closeScreen");}private void killProcess() {System.out.println("killProcess");}private void powerOff() {System.out.println("powerOff");}public void shutDown() {this.saveData();this.killProcess();this.closeScreen();this.powerOff();}
}class Person{private Computer computer = new Computer();public void shutDownComputer() {computer.shutDown();}
}

重构之后,电脑关机的细节全部定义在Computer类内部,保证了操作步骤的正确性,毕竟该类的作者才是最最懂关机细节的人。Computer对外暴露shutDown函数,Person只需调用该方法就完成整个关机流程。代码更整洁漂亮,而且不会出错。

  • 只和朋友进行通信
    朋友的定义:
    • 类中的字段类型
    • 类中的方法的参数类型
    • 类中的方法的返回值类型
    • 方法中直接实例化出来的类型(如使用new)
      总结一句话就是:该类依赖的所有类都是自己的朋友。
      那么,哪种类不算自己的朋友呢?
public class AppTest {private Foo foo;void f1() {Bee bee = foo.getBee();}
}class Foo{Bee getBee() {return new Bee();}
}class Bee{}

Bee类就不是AppTest类的朋友,不符合上述朋友的任何一条。我们应该尽量不在AppTest中调用Bee的方法。此时Bee对象可以作为参数。

只和朋友通信是理想情况,实际开发中要视自己的业务需求来做判断。可以适当违背这一条。

合成复用原则

尽量使用组合而不是继承。继承关系会将类紧紧耦合在一起,而且在某些业务场景下会导致类数目的爆炸性增长。

这篇关于设计模式概述以及七大设计原则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/928585

相关文章

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

C#继承之里氏替换原则分析

《C#继承之里氏替换原则分析》:本文主要介绍C#继承之里氏替换原则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#里氏替换原则一.概念二.语法表现三.类型检查与转换总结C#里氏替换原则一.概念里氏替换原则是面向对象设计的基本原则之一:核心思想:所有引py

Python datetime 模块概述及应用场景

《Pythondatetime模块概述及应用场景》Python的datetime模块是标准库中用于处理日期和时间的核心模块,本文给大家介绍Pythondatetime模块概述及应用场景,感兴趣的朋... 目录一、python datetime 模块概述二、datetime 模块核心类解析三、日期时间格式化与

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多