设计模式-迪米特法则

迪米特法则

迪米特法则 (Law of Demeter, LoD),也被称为“最少知识原则 (Principle of Least Knowledge)”,是面向对象设计中的一个重要原则。

核心思想:一个对象应该对其他对象有尽可能少的了解。

更具体地说,它规定了一个对象 O 中的一个方法 M 应该只调用以下类型对象的方法:

  1. 对象 O 本身 (this/self)。

  2. 作为方法 M 的参数传递进来的对象。

  3. 在方法 M 内部创建的对象 (局部对象)。

  4. 对象 O 的直接成员/组件对象 (实例变量/属性)。

  5. 全局对象 (但应谨慎使用,通常不推荐)。

简单来说,就是“只和你的直接朋友交谈,不要和朋友的朋友交谈”。

为什么叫“迪米特法则”?

这个名字来源于 1987 年在美国东北大学 (Northeastern University) 进行的一个名为 "Demeter Project" 的项目。该项目的目标是开发一种更容易维护和演化的面向对象系统。

目的和好处:

遵循迪米特法则的主要目的是降低类之间的耦合度 (Coupling),从而带来以下好处:

  1. 提高模块的独立性:当一个模块的内部实现发生改变时,由于它只与直接相关的模块交互,因此对其他模块的影响会降到最低。

  2. 增强系统的可维护性:修改一个类时,不需要关心太多其他类的内部细节,减少了连锁反应的风险。

  3. 提高代码的可复用性:低耦合的模块更容易被复用到其他系统中。

  4. 降低复杂性:每个类只需要关注与自己直接相关的交互,使得系统结构更清晰。

  5. 更容易进行单元测试:由于依赖关系简单,更容易对模块进行隔离测试。

违反迪米特法则的常见表现 (坏味道):

当你在代码中看到一长串的点号调用,例如 object.getA().getB().getC().doSomething(),这通常是违反迪米特法则的信号。这被称为“链式调用”或“消息链 (Message Chains)”。

这种写法的问题在于:

  • 当前对象不仅依赖于 object,还间接依赖于 A、B、C 的内部结构。

  • 如果 A、B 或 C 的任何一个类的接口发生改变(比如 getB() 方法没了,或者返回类型变了),当前对象的代码也可能需要修改,即使它本身并不直接关心 B 或 C 的具体实现。

如何遵循迪米特法则?

当发现违反迪米特法则的情况时,可以考虑以下重构方法:

  1. 封装和委托 (Encapsulation and Delegation):

    • 如果对象 O 需要调用 object.getA().getB().doSomething(),可以考虑在 object 类中添加一个新方法,比如 doSomethingRelatedToObjectB(),这个新方法内部去处理与 A 和 B 的交互,然后 O 只需要调用 object.doSomethingRelatedToObjectB() 即可。

    • 这样,object 封装了与 A 和 B 的交互细节,O 只需要与它的直接朋友 object 对话。

    例子:

    • 不好的设计 (违反 LoD):

      class Wallet {private Money money;public Wallet(Money money) { this.money = money; }public Money getMoney() { return money; }
      }
      ​
      class Money {private int amount;public Money(int amount) { this.amount = amount; }public int getAmount() { return amount; }public void setAmount(int amount) { this.amount = amount; }
      }
      ​
      class Person {private Wallet wallet;public Person(Wallet wallet) { this.wallet = wallet; }
      ​public void pay(int amountToPay) {// 违反了 LoD:Person 通过 Wallet 拿到了 Money,然后操作 Moneyint currentAmount = wallet.getMoney().getAmount();if (currentAmount >= amountToPay) {wallet.getMoney().setAmount(currentAmount - amountToPay);System.out.println("支付成功: " + amountToPay);} else {System.out.println("余额不足");}}
      }
    • 好的设计 (遵循 LoD):

      class Wallet { // Wallet 作为 Money 的直接朋友private Money money;public Wallet(Money money) { this.money = money; }
      ​// Wallet 负责处理支付逻辑,而不是暴露 Money 对象public boolean spend(int amountToSpend) {return money.deduct(amountToSpend);}
      ​public int getCurrentBalance() {return money.getAmount();}
      }
      ​
      class Money {private int amount;public Money(int amount) { this.amount = amount; }public int getAmount() { return amount; }
      ​public boolean deduct(int amountToDeduct) { // Money 自己负责扣款if (this.amount >= amountToDeduct) {this.amount -= amountToDeduct;return true;}return false;}
      }
      ​
      class Person { // Person 的直接朋友是 Walletprivate Wallet wallet;public Person(Wallet wallet) { this.wallet = wallet; }
      ​public void pay(int amountToPay) {// Person 只和它的直接朋友 Wallet 交互if (wallet.spend(amountToPay)) {System.out.println("支付成功: " + amountToPay);} else {System.out.println("余额不足,当前余额: " + wallet.getCurrentBalance());}}
      }

      在这个改进后的例子中,Person 不再需要知道 Wallet 内部是如何管理 Money 的,也不需要直接操作 Money 对象。Person 只与 Wallet 交互,告诉它要支付多少钱。Wallet 负责具体的支付逻辑,它与它的直接朋友 Money 交互。

  2. 移动方法 (Move Method):

    • 如果一个方法过多地使用了另一个类的数据和方法,可以考虑将这个方法移动到那个类中。

需要注意的平衡:

  • 过度应用可能导致问题:如果为了严格遵守迪米特法则而创建大量仅仅是进行简单委托的“包装方法 (Wrapper Methods)”,可能会导致类的接口膨胀,反而增加系统的复杂性。

  • Getter 方法本身不一定违反 LoD:object.getData() 本身不违反迪米特法则,因为 Data 对象是 object 的一个组件(或者是通过参数传入的,或者是局部创建的)。问题在于你如何使用这个返回的 Data 对象。如果你只是读取 Data 的状态(比如 data.getValue()),通常是可以接受的。但如果你接着调用 data.getAnotherObject().doSomething(),那就可能违反了。

  • 数据结构 (Data Structures) vs. 对象 (Objects):对于纯粹的数据结构(例如,DTO - Data Transfer Object),其目的就是暴露数据,这时获取其内部数据是正常的。迪米特法则更多地应用于行为丰富的对象。

总结:

迪米特法则是指导我们设计低耦合、高内聚系统的一个重要原则。它的核心思想是限制对象之间的知识,让每个对象只与它的直接朋友交互。通过封装和委托,我们可以有效地应用迪米特法则,从而创建出更易于维护、扩展和理解的软件系统。但同时也要注意避免过度设计,找到一个合适的平衡点。

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

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

相关文章

结构性-代理模式

动态代理主要是为了处理重复创建模板代码的场景。 使用示例 public interface MyInterface {String doSomething(); }public class MyInterfaceImpl implements MyInterface{Overridepublic String doSomething() {return "接口方法dosomething";} }public class M…

Unity大型项目资源框架

🎯 Unity大型项目资源管理:低端机检测后自动切换资源框架(大厂风格) 🧩 框架目标 ✅ 启动时检测机型性能,判定设备等级 ✅ 同一资源有高配/中配/低配不同压缩格式 ✅ 根据设备等级,加载对应资源包(AB) ✅ 支持动态切换(可用来切换特效/贴图分辨率/模型LOD) ✅ 保证…

MATLAB仿真:偏振光在光纤通信中的应用研究_可复现,有问题请联系博主

MATLAB仿真:偏振光在光纤通信中的应用研究 1. 研究概述 本文通过MATLAB仿真研究偏振光在光纤通信中的关键技术,包括偏振态生成、传输特性和检测方法,重点分析偏振模色散(PMD)的影响机制,并设计偏振控制优化方案。 %% 主程序框架 clc; clear; close all; addpath(Polar…

CTA-861-G-2017中文pdf版

CTA-861-G标准(2016年11月发布)规范未压缩高速数字接口的DTV配置,涵盖视频格式、色彩编码、辅助信息传输等,适用于DVI、HDMI等接口,还涉及EDID数据结构及HDR元数据等内容。

C++核心编程_继承方式

继承的语法&#xff1a;class 子类 : 继承方式 父类 继承降属性权限&#xff0c;不可升属性权限 继承方式一共有三种&#xff1a; 公共继承 保护继承 私有继承 #include <iostream> #include <string> using namespace std;class Base1 { public:int m_A; p…

Dockerfile常用指令介绍

Dockerfile常用指令介绍 Dockerfile是一个文本文件&#xff0c;用于定义Docker镜像的构建过程。下面介绍一些最常用的Dockerfile指令及其用法&#xff1a; 基础指令 FROM - 指定基础镜像 FROM python:3.9-slim这是Dockerfile的第一个指令&#xff0c;用于指定构建镜像的基础镜…

Spring中@Primary注解的作用与使用

在 Spring 框架中&#xff0c;Primary 注解用于解决依赖注入时的歧义性&#xff08;Ambiguity&#xff09;问题。当 Spring 容器中存在多个相同类型的 Bean 时&#xff0c;通过 Primary 标记其中一个 Bean 作为默认的首选注入对象。 核心作用&#xff1a; 解决多个同类型 Bean …

本地优先的状态管理与工具选型策略

本地优先&#xff1a;合理把控状态共享边界 在 React 应用开发过程中&#xff0c;开发者容易陷入一个认知误区——过度追求状态的全局化。许多新手开发者在项目初期就急于引入 Redux、Zustand 或 Jotai 等状态管理工具&#xff0c;将一些本应属于组件内部的琐碎状态&#xff0…

OpenCV CUDA模块图像处理-----对图像执行 均值漂移过程(Mean Shift Procedure)函数meanShiftProc()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 执行一个均值漂移过程&#xff08;mean-shift procedure&#xff09;&#xff0c;并将处理后的点的信息&#xff08;它们的颜色和位置&#xff0…

硬件I2C和软件I2C的区别

硬件I2C和软件I2C的区别 一、硬件I2C 1、硬件IC的局限性及学习意义 尽管硬件IC外设在STM32等微控制器中提供了标准化的通信支持&#xff0c;但在实际应用中&#xff0c;其稳定性可能存在问题。例如&#xff0c;某些情况下外设会因事件检测异常而进入死锁状态&#xff0c;仅能…

推荐12个wordpress企业网站模板

WordPress企业网站模板是一种专为企业网站设计的WordPress主题&#xff0c;旨在帮助企业创建专业、美观且易于管理的网站。这些模板通常具备响应式设计、SEO优化、多语言支持等功能&#xff0c;能够满足不同行业和企业的需求。 WordPress企业网站模板的适用场景 企业官网&…

68道Hbase高频题整理(附答案背诵版)

简述什么是Hbase数据库&#xff1f; Hbase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;它利用HBase技术在HDFS上提供了类似于Bigtable的能力。换句话说&#xff0c;Hbase是Apache Hadoop生态系统中的一部分&#xff0c;可以为大数据应用提供快速的随机…

PyTorch——卷积操作(2)

二维矩阵 [[ ]] 这里面conv2d(N,C,H,W)里面的四个是 N就是batch size也就是输入图片的数量&#xff0c;C就是通道数这只是一个二维张量所以通道为1&#xff0c;H就是高&#xff0c;W就是宽&#xff0c;所以是1 1 5 5 卷积核 reshape 第一个参数是batch size样本数量 第二个参数…

Linux之MySQL安装篇

1.确保Yum环境是否能正常使用 使用yum环境进行软件的安装 yum -y install mysql-server mysql2.确保软件包已正常完成安装 3.设置防火墙和selinux配置 ## 关闭防火墙 systemctl stop firewalld## 修该selinux配置 vim /etc/selinux/config 将seliuxenforcing修改为sel…

Devops系列---python基础篇二

1、列表 1.1 概念 格式&#xff1a; 名称 [ “元素1”,“元素2”,…] #定义一个列表 computer ["主机","键盘","显示器","鼠标"]类型方法用途查index(“元素”)查看元素索引位置count(“元素”)统计元素出现的次数reverse()倒序排…

LeetCode - 234. 回文链表

目录 题目 快慢双指针步骤 读者可能的错误写法 正确的写法 题目 234. 回文链表 - 力扣&#xff08;LeetCode&#xff09; 快慢双指针步骤 找到链表的中点&#xff08;find_mid函数&#xff09;&#xff1a; 使用快慢指针&#xff0c;慢指针每次走一步&#xff0c;快指针…

UniApp 全生命周期钩子详解

&#x1f449; 整理不易&#xff0c;如果本文对你有帮助&#xff0c;欢迎点个【赞 &#x1f44d;】【收藏 ⭐】【关注 &#x1f9e1;】 后续我们还将继续分享实用的 UniApp 教程&#xff0c;比如&#xff1a; 文件上传全局请求封装状态管理动态路由等… &#x1f4ee; 有任何…

探索NautilusTrader:下一代开源算法交易平台的革命性突破

在金融科技的浪潮中,量化交易领域正经历一场由开源技术驱动的变革。NautilusTrader(https://github.com/nautechsystems/nautilus_trader)作为一款高性能、生产级的算法交易平台,正以其创新的设计理念和强大的技术架构重塑开发者的策略研发流程。 一、核心定位:打破回测与…

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…