当前位置: 首页 > news >正文

编程的法则 迪米特法则(Law of Demeter)也称为“最少知识原则(Principle of Least Knowledge)包括如何实践

编程的法则 迪米特法则(Law of Demeter)也称为“最少知识原则(Principle of Least Knowledge)包括如何实践

flyfish

2017-07-25
2024-07-18

迪米特法则(Law of Demeter)也称为“最少知识原则(Principle of Least Knowledge),是一种软件设计原则,其目的是通过减少模块之间的相互依赖性来提高代码的可维护性和可复用性。
一句话就是尽量减少对象之间的耦合。
每个开发人员都有一个“工具包”——那些倾向于反复使用的解决方案,因为根据个人的经验,它们有效。“往我们的包里装更多的技巧”。往包里装的技巧就是诸如设计模式和编程惯用法。

迪米特法则(Law of Demeter更适合归类为一种“惯用法”而非设计模式。通过了解这个惯用法并理解如何应用它,则代码会更好、更少出错、也更易于维护。减少代码中对象间“耦合”的一种技术。该惯用法也是许多不同设计模式中的组成部分。这种技术概念简单,老外却起了一个难记的名字(老外认为这个名字好记):迪米特法则,叫最少知识原则(Principle of Least Knowledge)更好。

如果你想让你的狗跑的话,你会对狗狗说还是对四条狗腿说?

示例:不遵循迪米特法则

class Leg {
public:void move() {// 移动腿的逻辑}
};class Dog {
public:Leg frontLeft;Leg frontRight;Leg backLeft;Leg backRight;
};// 使用示例
Dog myDog;
myDog.frontLeft.move();
myDog.frontRight.move();
myDog.backLeft.move();
myDog.backRight.move();

在这个示例中,直接操作 Dog 对象的 Leg 属性,这违反了迪米特法则。

示例:遵循迪米特法则

class Leg {
public:void move() {// 移动腿的逻辑}
};class Dog {
public:void run() {frontLeft.move();frontRight.move();backLeft.move();backRight.move();}private:Leg frontLeft;Leg frontRight;Leg backLeft;Leg backRight;
};// 使用示例
Dog myDog;
myDog.run();

通过 Dog 对象的 run() 方法与其交互,而不是直接操作 Leg 属性。这符合迪米特法则。

一个通过接口调用,一个直接深入到其他对象的成员变量

不遵循迪米特法则的代码
#include <iostream>
#include <string>class Address {
public:std::string city;Address(const std::string& city) : city(city) {}
};class Customer {
public:Address address;Customer(const Address& address) : address(address) {}
};class Order {
public:Customer customer;Order(const Customer& customer) : customer(customer) {}
};int main() {Address address("JiNan");Customer customer(address);Order order(customer);// 不遵循迪米特法则的代码std::string city = order.customer.address.city;std::cout << "City: " << city << std::endl;return 0;
}
遵循迪米特法则的代码
#include <iostream>
#include <string>class Address {
public:std::string city;Address(const std::string& city) : city(city) {}
};class Customer {
public:Address address;Customer(const Address& address) : address(address) {}std::string get_city() const {return address.city;}
};class Order {
public:Customer customer;Order(const Customer& customer) : customer(customer) {}std::string get_customer_city() const {return customer.get_city();}
};int main() {Address address("JiNan");Customer customer(address);Order order(customer);// 遵循迪米特法则的代码std::string city = order.get_customer_city();std::cout << "City: " << city << std::endl;return 0;
}

解释

  1. 不遵循迪米特法则的代码 :在这个示例中,直接访问了 order 对象的 customer 成员,然后进一步访问了 customeraddress 成员,再进一步访问了 addresscity 成员。这种直接访问多个层级的成员变量的方法违反了迪米特法则,因为它增加了对象之间的耦合度。

  2. 遵循迪米特法则的代码 :在这个示例中,在 Customer 类中添加了一个 get_city 方法,然后在 Order 类中添加了一个 get_customer_city 方法。这两个方法分别返回了 city 的值。这样做的好处是,在访问 city 时,只调用了 Order 对象的方法,避免了直接访问多个层级的成员变量,从而遵循了迪米特法则,降低了对象之间的耦合度。

看上去使用了迪米特法则, 由于糟糕的设计,会让类接口变得臃肿

如果一个对象有大量的属性,为每个属性都提供 get 方法, set 方法会显得繁琐且冗余

示例说明

假设我们有一个Customer类,它有一个Address对象,Address对象包含StreetCityZipCode等属性。

class Address {
public:std::string getStreet() const { return street; }std::string getCity() const { return city; }std::string getZipCode() const { return zipCode; }void setStreet(const std::string& str) { street = str; }void setCity(const std::string& cty) { city = cty; }void setZipCode(const std::string& zip) { zipCode = zip; }private:std::string street;std::string city;std::string zipCode;
};class Customer {
public:Address getAddress() const { return address; }void setAddress(const Address& addr) { address = addr; }private:Address address;
};
违反迪米特法则的代码

直接访问对象的属性会违反迪米特法则:

Customer customer;
std::string street = customer.getAddress().getStreet(); // 直接访问对象的属性
遵循迪米特法则的代码

为了遵循迪米特法则,我们需要在Customer类中添加包装方法:

class Customer {
public:Address getAddress() const { return address; }void setAddress(const Address& addr) { address = addr; }std::string getStreet() const { return address.getStreet(); }std::string getCity() const { return address.getCity(); }std::string getZipCode() const { return address.getZipCode(); }private:Address address;
};// 使用示例
Customer customer;
std::string street = customer.getStreet(); // 使用包装方法

处理大量属性的方法

如果一个对象有大量的属性,为每个属性都提供 get 方法会显得繁琐且冗余,与简化代码和提高可维护性的初衷相悖。然而,迪米特法则并不是要求机械地为每个属性都提供 get 方法,而是提倡一种设计理念:尽量减少对象之间的耦合,保持模块的独立性和接口的简洁性。
以下是处理大量属性的方法

0. 直接访问对象的属性,没有遵循迪米特法则

#include <iostream>
#include <string>class Address {
public:std::string street;std::string city;std::string state;std::string zipcode;
};class Customer {
public:std::string first_name;std::string last_name;Address address;
};// 使用示例
int main() {Customer customer;customer.first_name = "John";customer.last_name = "Doe";customer.address.street = "123 Main St";customer.address.city = "Anytown";customer.address.state = "CA";customer.address.zipcode = "12345";std::cout << "Customer: " << customer.first_name << " " << customer.last_name << std::endl;std::cout << "Address: " << customer.address.street << ", " << customer.address.city << ", "<< customer.address.state << " " << customer.address.zipcode << std::endl;return 0;
}

简单的场景可以用,如果遇到以下场景就不适合了
代码直接依赖于 Customer 和 Address 类的内部结构。如果 Address 类的内部结构发生变化(例如,属性名称或类型改变),所有直接访问这些属性的代码都需要修改。
当代码中大量地方直接访问对象的属性时,维护变得困难。如果需要添加验证逻辑例如权限检查、数据验证或日志或修改属性的访问方式,需要在每个访问点进行修改。

1. 提供有意义的接口 以下是开始遵循迪米特法则

#include <iostream>
#include <string>class Customer {std::string first_name;std::string last_name;public:Customer(const std::string& first_name, const std::string& last_name): first_name(first_name), last_name(last_name) {}std::string get_full_name() const {return first_name + " " + last_name;}
};// 使用示例
int main() {Customer customer("Fly", "Fish");std::cout << "Full Name: " << customer.get_full_name() << std::endl;return 0;
}

2. 封装相关属性

#include <iostream>
#include <string>class Address {std::string street;std::string city;std::string zipcode;public:Address(const std::string& street, const std::string& city, const std::string& zipcode): street(street), city(city), zipcode(zipcode) {}std::string get_address() const {return street + ", " + city + ", " + zipcode;}
};class Customer {std::string name;Address address;public:Customer(const std::string& name, const Address& address): name(name), address(address) {}Address get_address() const {return address;}
};// 使用示例
int main() {Address address("123 Main St", "JiNan", "12345");Customer customer("Fly Fish", address);std::cout << "Address: " << customer.get_address().get_address() << std::endl;return 0;
}

3. 使用数据传输对象(DTO)

#include <iostream>
#include <string>class CustomerDTO {
public:std::string name;std::string address;std::string phone;std::string email;CustomerDTO(const std::string& name, const std::string& address, const std::string& phone, const std::string& email): name(name), address(address), phone(phone), email(email) {}
};class Customer {std::string name;std::string address;std::string phone;std::string email;public:Customer(const std::string& name, const std::string& address, const std::string& phone, const std::string& email): name(name), address(address), phone(phone), email(email) {}CustomerDTO to_dto() const {return CustomerDTO(name, address, phone, email);}
};// 使用示例
int main() {Customer customer("Fly Fish", "123 Main St, JiNan", "555-1234", "Fly.Fish@example.com");CustomerDTO dto = customer.to_dto();std::cout << "DTO Name: " << dto.name << std::endl;std::cout << "DTO Address: " << dto.address << std::endl;std::cout << "DTO Phone: " << dto.phone << std::endl;std::cout << "DTO Email: " << dto.email << std::endl;return 0;
}

4. 批量访问方法

#include <iostream>
#include <string>
#include <tuple>class Customer {std::string name;std::string address;std::string phone;std::string email;public:Customer(const std::string& name, const std::string& address, const std::string& phone, const std::string& email): name(name), address(address), phone(phone), email(email) {}std::tuple<std::string, std::string, std::string> get_contact_info() const {return std::make_tuple(address, phone, email);}
};// 使用示例
int main() {Customer customer("Fly Fish", "123 Main St, JiNan", "555-1234", "Fly.Fish@example.com");auto [address, phone, email] = customer.get_contact_info();std::cout << "Address: " << address << std::endl;std::cout << "Phone: " << phone << std::endl;std::cout << "Email: " << email << std::endl;return 0;
}

除了上面的做法还可以用设计模式、领域驱动设计、组合而非继承以及单一职责原则等等

迪米特法则(Law of Demeter)规定方法只能与以下对象通信:

该方法所属类的对象
作为方法参数传递的对象
对象的属性所引用的对象
方法创建的局部对象
全局变量引用的对象

#include <iostream>
#include <string>class Address {
private:std::string street;std::string city;std::string state;std::string zipcode;public:Address(const std::string& street, const std::string& city, const std::string& state, const std::string& zipcode): street(street), city(city), state(state), zipcode(zipcode) {}std::string get_full_address() const {return street + ", " + city + ", " + state + " " + zipcode;}// 自身对象的方法std::string get_city() const {return city;}
};class Customer {
private:std::string first_name;std::string last_name;Address address;public:Customer(const std::string& first_name, const std::string& last_name, const Address& address): first_name(first_name), last_name(last_name), address(address) {}// 自身对象的方法std::string get_full_name() const {return first_name + " " + last_name;}// 作为方法参数传递的对象的方法void print_address(const Address& addr) const {std::cout << addr.get_full_address() << std::endl;}// 对象的属性所引用的对象的方法std::string get_customer_city() const {return address.get_city();}// 方法创建的局部对象的方法void print_greeting() const {std::string greeting = "Hello, " + first_name + "!";std::cout << greeting << std::endl;}
};// 全局变量引用的对象
Address global_address("456 Another St", "Othertown", "TX", "67890");int main() {Address address("123 Main St", "Anytown", "CA", "12345");Customer customer("John", "Doe", address);// 该方法所属类的对象的方法std::cout << "Customer: " << customer.get_full_name() << std::endl;// 作为方法参数传递的对象的方法customer.print_address(address);// 对象的属性所引用的对象的方法std::cout << "Customer's City: " << customer.get_customer_city() << std::endl;// 方法创建的局部对象的方法customer.print_greeting();// 全局变量引用的对象std::cout << "Global Address: " << global_address.get_full_address() << std::endl;return 0;
}

Law of Demeter: Principle of Least Knowledge

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 微服务之间Feign调用
  • mac M1 创建Mysql8.0容器
  • C# Blazor Server与JS互操作无法获取大数据量返回值
  • 2024-07-13 Unity AI状态机2 —— 项目介绍
  • Dify中的高质量索引模式实现过程
  • 华为USG6000V防火墙NAT智能选举
  • Python和C++行人轨迹预推算和空间机器人多传感融合双图算法模型
  • 字节码编程之bytebuddy结合javaagent支持多种监控方式
  • 【Spring全家桶系列之核心篇 | Spring Cloud】 - 第七章 掌握Gateway核心技术,实现高效路由与转发
  • 灵雀云AML:赋能金融AI,构建数智时代核心竞争力
  • Android SurfaceView 组件介绍,挖洞原理详解
  • Apache httpd-vhosts.conf 配置详解(附Demo)
  • 【学习笔记】无人机(UAV)在3GPP系统中的增强支持(十一)-无人机服务可用性用例需求
  • 不常用的第三方服务集成
  • [米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-22 TPG图像测试数据发生器设计
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 《Java编程思想》读书笔记-对象导论
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • ➹使用webpack配置多页面应用(MPA)
  • Docker容器管理
  • dva中组件的懒加载
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • JavaWeb(学习笔记二)
  • Making An Indicator With Pure CSS
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Rancher-k8s加速安装文档
  • ReactNative开发常用的三方模块
  • 不上全站https的网站你们就等着被恶心死吧
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 动态规划入门(以爬楼梯为例)
  • 多线程事务回滚
  • 复杂数据处理
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 目录与文件属性:编写ls
  • 配置 PM2 实现代码自动发布
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • kubernetes资源对象--ingress
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • ## 1.3.Git命令
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #APPINVENTOR学习记录
  • (2)STL算法之元素计数
  • (PySpark)RDD实验实战——取最大数出现的次数
  • (ZT)出版业改革:该死的死,该生的生
  • (定时器/计数器)中断系统(详解与使用)
  • (利用IDEA+Maven)定制属于自己的jar包
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (四)模仿学习-完成后台管理页面查询
  • (一)appium-desktop定位元素原理
  • (已解决)Bootstrap精美弹出框模态框modal,实现js向modal传递数据
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)