首页 > 其他分享 >面向对象设计原则

面向对象设计原则

时间:2023-08-25 15:11:18浏览次数:48  
标签:return room 原则 代码 面向对象 模块 设计 父类

前言

在面向对象的软件设计中,只有尽量降低各个模块之间的耦合度,才能提高代码的复用率,系统的可维护性、可扩展性才能提高。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,如果把设计模式比作武功招式,那么设计原则就好比是内功心法。常用的设计原则有七个,本文将具体介绍单一职责原则。

设计原则简介

  • 单一职责原则:专注降低类的复杂度,实现类要职责单一;
  • 开放关闭原则:所有面向对象原则的核心,设计要对扩展开发,对修改关闭;
  • 里式替换原则:实现开放关闭原则的重要方式之一,设计不要破坏继承关系; (只要有父类出现的地方,都可以用子类来替换)
  • 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;
  • 接口隔离原则:要求接口的方法尽量少,接口尽量细化;
  • 迪米特法则:降低系统的耦合度,使一个模块的修改尽量少的影响其他模块,扩展会相对容易;
  • 组合复用原则:在软件设计中,尽量使用组合/聚合而不是继承达到代码复用的目的。

这些设计原则并不说我们一定要遵循他们来进行设计,而是根据我们的实际情况去怎么去选择使用他们,来让我们的程序做的更加的完善。

单一职责原则

就一个类而言,应该仅有一个引起它变化的原因,通俗的说,就是一个类只负责一项职责。此原则的核心就是解耦和增强内聚性。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计。

优点

(1)降低类的复杂度;

(2)提高类的可读性,提高系统的可维护性;

(3)降低变更引起的风险(降低对其他功能的影响)。


链接:https://juejin.cn/post/6960839149699989512

 

开放关闭原则

简介

开放封闭原则是这样表述的:

软件实体(类、模块、函数)应该对扩展开放,对修改封闭。

这个说法是 Bertrand Meyer 在其著作《面向对象软件构造》(Object-Oriented Software Construction)中提出来的,它给软件设计提出了一个极高的要求:不修改代码。

或许你想问,不修改原有代码,那我怎么实现新的需求呢?答案就是靠扩展。用更通俗的话来解释,就是新需求应该用新代码实现。

开放封闭原则向我们描述的是一个结果,就是我们可以不修改原有代码而仅凭扩展就完成新功能。但是,这个结果的前提是要在软件内部留好扩展点,而这正是需要我们去设计的地方。因为每一个扩展点都是一个需要设计的模型

解释

举个例子,假如我们正在开发一个酒店预订系统,针对不同的用户,我们需要计算出不同的房价。比如,普通用户是全价,金卡是 8 折,银卡是 9 折,代码写出来可能是这样的:

class HotelService {
  public double getRoomPrice(final User user, final Room room) {
    double price = room.getPrice();
    if (user.getLevel() == Level.GOLD) {
      return price * 0.8;
    }
    
    if (user.getLevel() == Level.SILVER) {
      return price * 0.9;
    }
    
    return price;
  }
}

 

这时,新的需求来了,要增加白金卡会员,给出 75 折的优惠,如法炮制的写法应该是这样的:

class HotelService {
  public double getRoomPrice(final User user, final Room room) {
    double price = room.getPrice();
    if (user.getLevel() == UserLevel.GOLD) {
      return price * 0.8;
    }
    
    if (user.getLevel() == UserLevel.SILVER) {
      return price * 0.9;
    }
    
    if (user.getLevel() == UserLevel.PLATINUM) {
      return price * 0.75;
    }
    
    return price;
  }
}

显然,这种做法就是修改代码的做法,每增加一个新的类型就要修改一次代码。但是,一个有各种级别用户的酒店系统肯定不只是房价有区别,提供的服务也可能有区别。可想而知,每增加一个用户级别,我们要改的代码就漫山遍野。

那应该怎么办呢?我们应该考虑如何把它设计成一个可以扩展的模型。在这个例子里面,既然每次要增加的是用户级别,而且各种服务的差异都体现在用户级别上,我们就需要一个用户级别的模型。在前面的代码里,用户级别只是一个简单的枚举,我们可以给它丰富一下:

interface UserLevel {
  double getRoomPrice(Room room);
}

class GoldUserLevel implements UserLevel {
  public double getRoomPrice(final Room room) {
    return room.getPrice() * 0.8;
  }
}

class SilverUserLevel implements UserLevel {
  public double getRoomPrice(final Room room) {
    return room.getPrice() * 0.9;
  }
}

我们原来的代码就可以变成这样:

class HotelService {
  public double getRoomPrice(final User user, final Room room) {
    return user.getRoomPrice(room);
  }
}

class User {
  private UserLevel level;
  ...
  
  public double getRoomPrice(final Room room) {
    return level.getRoomPrice(room);
  }
}

 

这样一来,再增加白金用户,我们只要写一个新的类就好了:

class PlatinumUserLevel implements UserLevel {
  public double getRoomPrice(final Room room) {
    return room.getPrice() * 0.75;
  }
}

 

之所以我们可以这么做,是因为我们在代码里留好了扩展点:UserLevel。在这里,我们把原来的只支持枚举值的 UserLevel 升级成了一个有行为的 UserLevel。

经过这番改造,HotelService 的 getRoomPrice 这个方法就稳定了下来,我们就不需要根据用户级别不断地调整这个方法了。至此,我们就拥有了一个稳定的构造块,可以在后期的工作中把它当做一个稳定的模块来使用。

当然,在这个例子里,这个方法是比较简单的。而在实际的项目中,业务方法都会比较复杂。



链接:https://juejin.cn/post/6960852120367005726

 

里氏替换原则

链接:https://www.jianshu.com/p/cf9f3c7c0df5

https://juejin.cn/post/6961203475640221704

在学习java类的继承时,我们知道继承有一些优点

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

但又有点也同样存在缺点

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

如何扬长避短呢?方法是引入里氏替换原则

定义

  • 第一种定义,也是最正宗的定义:If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
    如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

  • 第二种定义:Functions that use pointers or references to base classes must be able to useobjects of derived classes without knowing it.
    所有引用基类的地方必须能透明地使用其子类的对象。

第二种定义比较通俗,容易理解:只要有父类出现的地方,都可以用子类来替代,而且不会出现任何错误和异常。但是反过来则不行,有子类出现的地方,不能用其父类替代。

四层含义

里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:

  1. 子类必须实现父类的抽象方法,但不得重写(覆盖)  父类的非抽象(已实现)方法。
  2. 子类中可以增加自己特有的方法。
  3. 当子类重载父类的方法时(方法参数不一致),方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。  如——
    • 若 A类 f(Map() map), AA类  f(HashMap() map)  且在代码里AA替换了A,此时参数调用传入 HashMap类型(具体类型)的参数 mapParam仅执行AA类的方法; (父类不可以被子类替换)
    • 若 A类 f(HashMap() map),AA类  f(Map() map)  且在代码里AA替换了A,此时参数调用传入 HashMap类型(具体类型)的参数 mapParam仅执行A类的方法       (父类可以被子类替换)
  4. 当子类的方法实现  父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。   如——
    •  abstract A类:Map f(); AA类:HashMap f();    HashMap可以替换Map类型
    • abstract A类:HashMap f(), AA类:Map f();  编译报错

依赖倒置原则

https://juejin.cn/post/6961568751720333342

谁依赖谁

依赖倒置原则(Dependency inversion principle,简称 DIP)是这样表述的:

高层模块不应依赖于低层模块,二者应依赖于抽象。

抽象不应依赖于细节,细节应依赖于抽象。

学习这个原则,最重要的是要理解“倒置”,而要理解什么是“倒置”,就要先理解所谓的“正常依赖”是什么样的。

我们很自然地就会写出类似下面的这种代码:

 
class CriticalFeature {   // 高层模块
  private Step1 step1;
  private Step2 step2;
  ...
  
  void run() {
    // 执行第一步
    step1.execute();   // 低层模块
    // 执行第二步
    step2.execute();
    ...
  }
}

 

但是,这种未经审视的结构天然就有一个问题:高层模块会依赖于低层模块。在上面这段代码里,CriticalFeature 类就是高层类,Step1 和 Step2 就是低层模块,而且 Step1 和 Step2 通常都是具体类。虽然这是一种自然而然的写法,但是这种写法确实是有问题的。

在实际的项目中,代码经常会直接耦合在具体的实现上。比如,我们用 Kafka 做消息传递,我们就在代码里直接创建了一个 KafkaProducer 去发送消息。我们就可能会写出这样的代码:

 
class Handler {
  private KafkaProducer producer;
  
  void send() {
    ...
    Message message = ...;
    producer.send(new KafkaRecord<>("topic", message);
    ...
  }
}

也许你会问,我就是用了 Kafka 发消息,创建一个 KafkaProducer,这有什么问题吗?其实,我们需要站在长期的角度去看,什么东西是变的、什么东西是不变的。Kafka 虽然很好,但它并不是系统最核心的部分,我们在未来是可能把它换掉的。

你可能会想,这可是我实现的一个关键组件,我怎么可能会换掉它呢?软件设计需要关注长期、放眼长期,所有那些不在自己掌控之内的东西,都是有可能被替换的。其实,替换一个中间件是经常发生的。所以,依赖于一个可能会变的东西,从设计的角度看,并不是一个好的做法。那我们应该怎么做呢?这就轮到倒置登场了。

所谓倒置,就是把这种习惯性的做法倒过来,让高层模块不再依赖于低层模块。那要是这样的话,我们的功能又该如何完成呢?计算机行业中一句名言告诉了我们答案:

计算机科学中的所有问题都可以通过引入一个间接层得到解决。

是的,引入一个间接层。这个间接层指的就是 DIP 里所说的抽象。也就是说,这段代码里面缺少了一个模型,而这个模型就是这个低层模块在这个过程中所承担的角色。

依赖于抽象

抽象不应依赖于细节,细节应依赖于抽象。

其实,这个可以更简单地理解为一点:依赖于抽象,从这点出发,我们可以推导出一些更具体的指导编码的规则:

  • 任何变量都不应该指向一个具体类;
  • 任何类都不应继承自具体类;
  • 任何方法都不应该改写父类中已经实现的方法。

举个List 声明的例子,其实背后遵循的就是这里的第一条规则:

 
List<String> list = new ArrayList<>();

 


标签:return,room,原则,代码,面向对象,模块,设计,父类
From: https://www.cnblogs.com/wxdlut/p/17657010.html

相关文章

  • 行行AI人才直播第16期:【无界AI首席研究员】刘秋衫《AI创新设计:AIGC赋能设计行业的新思
    在这一轮生成式AI浪潮中,设计行业是受波及最为广泛的一个行业。这是设计师们始料未及的事情,至少在此之前,人们认为以设计、艺术为首的创意产业是最难被AI改变的产业之一。而生成式AI的出现,与其说是一次冲击,不如说是一次机遇,让设计师们重新思考设计、艺术、创意、行业······在......
  • Spring加载机制的设计与实现
    骑士李四记录:1.ApplicationContext是什么ApplicationContext是Spring上下文的核心接口,描述了Spring容器的所有基本功能,是SpringContext(Spring上下文)模块的核心设计。想了解Spring的加载机制,则必须先明白SpringApplicationContext(后简称Spring上下文)到底是什么、是怎么设计的、......
  • 黄冈市扶贫助农商场系统的设计与实现-计算机毕业设计源码+LW文档
    一、设计(论文)选题的依据(选题的目的和意义、该选题在国内外的研究现状及发展趋势,等)(一)选题目的和意义新世纪初期,随着改革开放的脚步越来越近,在党中央的正确领导下,互联网技术发展迅猛,各种网络基础设施的建设也在加速,5G、人工智能等新兴技术的发展,使人们的需求不断增加,推动着技术的进步......
  • 基于ssm的医院预约服务平台-计算机毕业设计源码+LW文档
    一、立题依据(研究的目的与意义及国内外现状):近年来,计算机技术的高速发展带动了许多行业的进步,通过科技手段可以帮助企业改善工作流程,提高工作效率。据统计,大部分企业目前已基本实现信息化管理进程。在管理系统模块中,最重要的莫过于数据库,可以将企业的各项数据通过数据库进行存储,然......
  • 基于STM32设计的智能空调
    一、项目背景随着人们生活水平的不断提高,对居住环境的舒适度要求也越来越高。空调作为一种重要的家电设备,已经成为了现代家庭中必不可少的一部分。本文介绍了一种基于STM32的智能空调设计方案,可以自动地根据环境温度进行温度调节。二、设计思路2.1整体构架智能空调系统由温度检测......
  • Java设计模式
    装饰器模式:装饰器模式是指在不改变现有对象结构的情况下,动态的给改对象增加一些职责(即增加其额外功能)的模式。装饰器模式通常在以下几种情况使用。当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生......
  • 什么是面向对象编程领域的胖接口 - Fat Interface
    在面向对象编程(Object-OrientedProgramming,简称OOP)领域,"胖接口",也称为"FatInterface",是一个被广泛认识并且应当避免的设计反模式。这个术语指的是一个接口(或抽象类)包含了大量的方法,可能超出了单一职责的原则,导致接口变得臃肿、复杂和难以维护。在本文中,我将详细解释什么是胖接......
  • 模拟集成电路设计系列博客——1.2.2 共漏放大器(源极跟随器)
    1.2.2共漏放大器(源极跟随器)另一个电流镜的常见应用时为源极跟随器提供偏置电流,在下图的例子中,\(Q_1\)为源极跟随器,\(Q_2\)为给\(Q_1\)提供偏置电流的有源负载,这个结构一般用于电压缓冲器,因此也被称作源极跟随器。因为输入和输出节点分别在栅极和源极,漏极作为小信号地,这个结构同......
  • 每天一个小知识,今日知识-如何设计一个并发请求控制函数
    假如给你一个数组,里面是请求路径,如何设计一个函数去控制这些请求的并发呢?这里我们用的请求路径为https://jsonplaceholder.typicode.com/todos来模拟constreqArr=[];for(leti=1;i<=10;i++){reqArr.push(`https://jsonplaceholder.typicode.c......
  • 芯片设计中的ECO是什么? ------ 转载
    本文转载自: 芯片设计中的ECO是什么?-腾讯云开发者社区-腾讯云(tencent.com) 如标题所写,我们今天聊一聊IC设计种的ECO。在展开关于ECO的概念之前,我们先大致捋下数字IC设计的流程,有助于我们后面的讨论。数字IC设计流程简述1、确定项目需求根据市场或者芯片功能要求,设计芯片的......