迪米特法则--设计模式

1、理论原理
迪米特法则(Law of Demeter, LoD),又称最少知识原则(Least Knowledge
Principle, LKP),是一种面向对象编程设计原则。它的核心思想是:一个对象应该
尽量少地了解其他对象,降低对象之间的耦合度,从而提高代码的可维护性和可扩展
性。
关于这个设计原则,我们先来看一下它最原汁原味的英文定义:
Each unit should have only limited knowledge about other units: only units
“closely” related to the current unit. Or: Each unit should only talk to its
friends; Don’t talk to strangers.
我们把它直译成中文,就是下面这个样子:
每个模块(unit)只应该了解那些与它关系密切的模块(units: only units
“closely” related to the current unit)的有限知识(knowledge)。或者说,
每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。
我们之前讲过,大部分设计原则和思想都非常抽象,有各种各样的解读,要想灵活地
应用到实际的开发中,需要有实战经验的积累。迪米特法则也不例外。所以,我结合
我自己的理解和经验,我们可以说的更加直白一点:两个类之间尽量不要直接依赖,
如果必须依赖,最好只依赖必要的接口。

总结来说呢:

我们要去明白,这我们日常的开发的时候,对于两个类之间的相关的依赖会导致我们的代码所编写的关联性是极大的增高的,这样当我们在后期进行维护的时候,就会是使得我们的代码连成一张蜘蛛网,使得我们的代码很难去不影响相关的其他模块的角度去进行相关的修改

如果我们要去进行一个相关的关联的时候,比如说我们实现的相关的类和方法要去对数据库进行相关的调用和进行操作的时候

我们就可去将这个数据库作为一个接口,我们所实现的相关的类和方法都要去调用这个数据库的相关的接口这样的话就会使得我们代码之间的修改变得简单了许多,所在之间产生的耦合度也不会这么大

迪米特法则的主要指导原则如下:
类和类之间尽量不直接依赖。
有依赖关系的类之间,尽量只依赖必要的接口。

从上面的描述中,我们可以看出,迪米特法则包含前后两部分,这两部分讲的是两件
事情,我用两个实战案例分别来解读一下。

2、类之间不直接依赖


"不该有直接依赖关系的类之间,不要有依赖"这个原则强调的是降低类与类之间的耦
合度,避免不必要的依赖。这通常意味着我们应该使用抽象(如接口或抽象类)来解
耦具体实现。让我们通过一个例子来理解这个原则。
假设我们有一个简单的报告生成系统,它需要从不同类型的数据源(如数据库、文
件、API等)获取数据,并输出不同格式的报告(如CSV、JSON、XML等)。以下是
一个不遵循这一原则的实现:

// 具体的数据库类
class Database {
public String fetchData() {
// 从数据库中获取数据
return "data from database";
}
}
// 具体的报告生成类
class ReportGenerator {
private Database database;
public ReportGenerator(Database database) {
this.database = database;
}
public String generateCSVReport() {
String data = database.fetchData();
// 将数据转换为CSV格式
return "CSV report: " + data;
}
}

在上述实现中, ReportGenerator 类直接依赖于具体的 Database 类。这意味着
如果我们想从其他类型的数据源(如文件)获取数据,或者使用不同的数据库实现,
我们需要修改 ReportGenerator 类。这违反了开闭原则(对扩展开放,对修改封
闭),并增加了类与类之间的耦合。
为了遵循 "不该有直接依赖关系的类之间,不要有依赖"原则,我们可以引入抽象来解
耦具体实现。下面是一个修改后的实现:
 

// 数据源接口
interface DataSource {
在修改后的实现中,我们引入了 DataSource 接口,并使 ReportGenerator 类
依赖于该接口,而不是具体的实现。这样,我们可以轻松地为报告生成器添加新的数
据源类型,而无需修改现有代码。
通过引入抽象,我们遵循了 "不该有直接依赖关系的类之间,不要有依赖"原则,降低
了类与类之间的耦合,
String fetchData();
}
// 具体的数据库类
class Database implements DataSource {
@Override
public String fetchData() {
// 从数据库中获取数据
return "data from database";
}
}
// 具体的文件类
class FileDataSource implements DataSource {
@Override
public String fetchData() {
// 从文件中获取数据
return "data from file";
}
}
// 报告生成类
class ReportGenerator {
private DataSource dataSource;
public ReportGenerator(DataSource dataSource) {
this.dataSource = dataSource;
}
public String generateCSVReport() {
String data = dataSource.fetchData();
// 将数据转换为CSV格式
return "CSV report: " + data;
}

3、只依赖必要的接口
"有依赖关系的类之间,尽量只依赖必要的接口"这个原则强调的是,当一个类需要依
赖另一个类时,应该尽可能地依赖于最小化的接口。这可以降低类与类之间的耦合,
提高系统的可扩展性和灵活性。
我们还是以上边的用户信息管理案例给大家讲解:

public interface UserService {
boolean register(String cellphone, String password);
boolean login(String cellphone, String password);
UserInfo getUserInfoById(long id);
UserInfo getUserInfoByCellphone(String cellphone);
}
public interface RestrictedUserService {
boolean deleteUserByCellphone(String cellphone);
boolean deleteUserById(long id);
}
public class UserServiceImpl implements UserService, RestrictedUserService {
// ... 省略实现代码...
}
对于绝大部分场景,我们可能只关心和删除无关的方法,如UserController,所以他
只需要依赖他所需要的接口UserService即可:
然而用户管理员需要更多的权限,我们则可以通过组合的形式来实现,让其依赖两个
必要的接口:
public interface UserService {
boolean register(String cellphone, String password);
boolean login(String cellphone, String password);
UserInfo getUserInfoById(long id);
UserInfo getUserInfoByCellphone(String cellphone);
}
public interface RestrictedUserService {
boolean deleteUserByCellphone(String cellphone);
boolean deleteUserById(long id);
}
public class UserServiceImpl implements UserService, RestrictedUserService {
// ... 省略实现代码...
}
public class UserController{
UserService userService;
// ... 省略实现代码...
}
public class UserManagerController{
UserService userService;
RestrictedUserService restrictedUserService;
// ... 省略实现代码...
}
再举一个例子,假如我们要开一个飞行比赛,我们可以写出如下的案例来满足迪米特
法则:
// 飞行行为接口
public interface Flyable {
void fly();
}
// 基类:鸟类
public class Bird {
}
// 子类:能飞的鸟类
public class Sparrow extends Bird implements Flyable {
@Override
public void fly() {
System.out.println("sparrow can fly");
}
}
// 子类:飞机
public class Plane implements Flyable {
@Override
public void fly() {
System.out.println("plane can fly");
}
}
// 子类:企鹅类,不实现Flyable接口
public class Penguin extends Bird {
}
//
public class AirRace {
List<Flyable> list;
public void addFlyable(Flyable flyable){
list.add(flyable);
}
// ...
}
这种实现符合 "有依赖关系的类之间,尽量只依赖必要的接口" 原则,降低了类与类
之间的耦合,提高了系统的可扩展性和灵活性。

这种实现符合 "有依赖关系的类之间,尽量只依赖必要的接口" 原则,降低了类与类
之间的耦合,提高了系统的可扩展性和灵活性。
4、辩证思考与灵活应用
在实际工作中,确实需要在不同的设计原则之间进行权衡。迪米特法则(Law of
Demeter,LoD)是一种有助于降低类之间耦合度的原则,但过度地应用迪米特法则
可能导致代码变得复杂和难以维护。因此,在实际项目中,我们应该根据具体的场景
和需求灵活地应用迪米特法则。以下是一些建议:
1. 避免过度封装:尽管迪米特法则强调类之间的低耦合,但是过度封装可能导致系
统变得难以理解和维护。当一个类需要访问另一个类的属性或方法时,我们应该
权衡封装的成本和收益,而不是盲目地遵循迪米特法则。
2. 拒绝过度解耦:在实际项目中,过度解耦可能导致大量的中间层和传递性调用。
当一个类需要访问另一个类的方法时,如果引入大量的中间层会导致系统变得复
杂和低效,那么我们应该考虑放宽迪米特法则的约束。
3. 与其他设计原则和模式相结合:在实际项目中,我们应该灵活地将迪米特法则与
其他设计原则(如单一职责原则、开闭原则等)和设计模式(如外观模式、代理
模式等)相结合。这样可以使我们在降低耦合度的同时,保持代码的可读性、可
维护性和可扩展性。
4. 考虑实际需求和场景:在应用迪米特法则时,我们应该关注实际的需求和场景。
如果一个项目的需求和场景较为简单,那么过度地应用迪米特法则可能导致不必
要的开发成本。相反,如果一个项目的需求和场景较为复杂,那么遵循迪米特法
则可能有助于提高系统的稳定性和可维护性。
总之,在实际项目中,我们应该根据需求和场景灵活地应用迪米特法则,既要降低类
之间的耦合度,又要保持代码的可读性、可维护性和可扩展性。这样,我们才能够更
好地满足项目的需求,提高软件的质量和效率。
小问题:在今天的讲解中,我们提到了“高内聚、松耦合”“单一职责原则”“接口隔离原
则”“基于接口而非实现编程”“迪米特法则”,你能总结一下它们之间的区别和联系吗?
设计原则是你在绝大部分场景中应该这样做,不是必须这么做。(怎么样写出优秀的
代码?)

1、职责要单一,类和接口的粒度要适中,类要有内聚性 (单一职责,接口隔离)
高内聚
2、面向抽象编程,要依赖抽象,而不依赖具体 要解耦合、(开闭原则,依赖倒
置,迪米特法则) 低耦合 可扩展,可维护
3、类和类之间的关联要紧凑,只依赖必要的接口(迪米特法则) 低耦合
4、组合优于继承,继承后最好不要重写(最好保证继承的作用是代码复用),如果
重写不要采用一些手段禁用方法(里式替换原则)
5、不要写重复的代码,代码实现(在保证功能和性能的前提下)最好简单一点,多
用大家常用的库,最好不用大家不熟悉的语法,最好不要自己造轮子,写代码禁止花
里胡哨,编写好的注释和文档,使用好的命名规范。(kiss,dry) 可读性 安全性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值