美文网首页
六大设计原则之迪米特法则(Law of Demeter)

六大设计原则之迪米特法则(Law of Demeter)

作者: 程序猿TODO | 来源:发表于2020-11-26 10:08 被阅读0次

    定义

    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 类图里面,没有了ClientGasEngine的依赖,耦合性降低。

    如何实践

    今后在做对象与对象之间交互的设计时,应该极力避免引出中间对象的情况(需要导入其他对象的类):需要什么对象直接返回即可,降低类之间的耦合度。

    相关文章

      网友评论

          本文标题:六大设计原则之迪米特法则(Law of Demeter)

          本文链接:https://www.haomeiwen.com/subject/oxumiktx.html