定义
You only ask for objects which you directly need.
即:一个对象应该对尽可能少的对象有接触,也就是只接触那些真正需要接触的对象。
定义解读
- 迪米特法则也叫做最少知道原则(Least Know Principle), 一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流)。
优点
实践迪米特法则可以良好地降低类与类之间的耦合,减少类与类之间的关联程度,让类与类之间的协作更加直接。
代码讲解
下面通过一个简单的关于汽车的例子来讲解一下迪米特法则。
需求点
设计一个汽车类,包含汽车的品牌名称,引擎等成员变量。提供一个方法返回引擎的品牌名称。
不好的设计
Car类:
//================== Car.h ==================
@class GasEngine;
@interface Car : NSObject
//构造方法
- (instancetype)initWithEngine:(GasEngine *)engine;
//返回私有成员变量:引擎的实例
- (GasEngine *)usingEngine;
@end
//================== Car.m ==================
#import "Car.h"
#import "GasEngine.h"
@implementation Car
{
GasEngine *_engine;
}
- (instancetype)initWithEngine:(GasEngine *)engine{
self = [super init];
if (self) {
_engine = engine;
}
return self;
}
- (GasEngine *)usingEngine{
return _engine;
}
@end
从上面可以看出,Car的构造方法需要传入一个引擎的实例对象。而且因为引擎的实例对象被赋到了Car对象的私有成员变量里面。所以Car类给外部提供了一个返回引擎对象的方法:usingEngine
。
而这个引擎类GasEngine
有一个品牌名称的成员变量brandName
:
//================== GasEngine.h ==================
@interface GasEngine : NSObject
@property (nonatomic, copy) NSString *brandName;
@end
这样一来,客户端就可以拿到引擎的品牌名称了:
//================== Client.m ==================
#import "GasEngine.h"
#import "Car.h"
- (NSString *)findCarEngineBrandName:(Car *)car{
GasEngine *engine = [car usingEngine];
NSString *engineBrandName = engine.brandName;//获取到了引擎的品牌名称
return engineBrandName;
}
上面的设计完成了需求,但是却违反了迪米特法则。原因是在客户端的findCarEngineBrandName:
中引入了和入参(Car)和返回值(NSString)无关的GasEngine
对象。增加了客户端与 GasEngine
的耦合。而这个耦合显然是不必要更是可以避免的。
接下来我们看一下如何设计可以避免这种耦合:
较好的设计
同样是Car这个类,我们去掉原有的返回引擎对象的方法,而是增加一个直接返回引擎品牌名称的方法:
//================== Car.h ==================
@class GasEngine;
@interface Car : NSObject
//构造方法
- (instancetype)initWithEngine:(GasEngine *)engine;
//直接返回引擎品牌名称
- (NSString *)usingEngineBrandName;
@end
//================== Car.m ==================
#import "Car.h"
#import "GasEngine.h"
@implementation Car
{
GasEngine *_engine;
}
- (instancetype)initWithEngine:(GasEngine *)engine{
self = [super init];
if (self) {
_engine = engine;
}
return self;
}
- (NSString *)usingEngineBrandName{
return _engine.brand;
}
@end
因为直接usingEngineBrandName
直接返回了引擎的品牌名称,所以在客户端里面就可以直接拿到这个值,而不需要间接地通过原来的GasEngine
实例来获取。
我们看一下客户端操作的变化:
//================== Client.m ==================
#import "Car.h"
- (NSString *)findCarEngineBrandName:(Car *)car{
NSString *engineBrandName = [car usingEngineBrandName]; //直接获取到了引擎的品牌名称
return engineBrandName;
}
与之前的设计不同,在客户端里面,没有引入GasEngine
类,而是直接通过Car
实例获取到了需要的数据。
这样设计的好处是,如果这辆车的引擎换成了电动引擎(原来的GasEngine
类换成了ElectricEngine
类),客户端代码可以不做任何修改!因为它没有引入任何引擎类,而是直接获取了引擎的品牌名称。
所以在这种情况下我们只需要修改Car类的usingEngineBrandName
方法实现,将新引擎的品牌名称返回即可。
下面来看一下这两个设计的UML 类图,可以更形象地看出两种设计上的区别:
UML 类图对比
未实践迪米特法则:
实践了迪米特法则:
很明显,在实践了迪米特法则的 UML 类图里面,没有了
Client
对GasEngine
的依赖,耦合性降低。
如何实践
今后在做对象与对象之间交互的设计时,应该极力避免引出中间对象的情况(需要导入其他对象的类):需要什么对象直接返回即可,降低类之间的耦合度。