OC面试题 八、设计模式

296 阅读25分钟

面试题:设计模式有哪些?

iOS设计模式中,常见的设计模式可以分为创建型、结构型和行为型三大类,以下是各类中的具体设计模式及其简介:

一、创建型模式(单例,工厂方法模式)

  1. 单例模式(Singleton)

    • 保证一个类只有一个实例,并提供一个访问它的全局访问点。
    • 适用于只需要一个对象保存数据的对象且需要全局使用的数据或方法,如系统的UIApplication、NSNotificationCenter等。
  2. 工厂方法模式(Factory Method)

    • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。
    • 工厂方法把一个类的实例化下放到子类,实现接口的分离和对象的延迟创建。
  3. 抽象工厂模式(Abstract Factory)

    • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
    • 适用于需要创建一系列相关对象且希望这些对象之间保持一定关系的场景。
  4. 建造者模式(Builder)

    • 将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。
    • 适用于需要创建复杂对象且对象的构建过程需要分步骤进行的场景。
  5. 原型模式(Prototype)

    • 用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。
    • 适用于需要创建大量相同或相似对象的场景,可以通过复制原型对象来快速创建新对象。

二、结构型模式(适配器、代理、装饰、桥接、组合模式)

  1. 适配器模式(Adapter)

    • 将一类的接口转换成客户希望的另外一个接口,使原本由于接口不兼容而不能一起工作的类可以一起工作。
    • 适用于需要解决接口不兼容问题的场景,如版本升级后的数据兼容问题。
  2. 代理模式(Proxy)

    • 为其他对象提供一种代理以控制对这个对象的访问。
    • 适用于需要控制对象访问权限或增加额外功能的场景,如数据代理和事件代理。
  3. 装饰模式(Decorator)

    • 动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
    • 适用于需要为对象增加额外功能且不想修改原有类代码的场景。
  4. 外观模式(Facade)

    • 为子系统中的一组接口提供一致的界面,使得子系统更容易使用。
    • 适用于需要将复杂子系统的接口简化成一个高层接口的场景,以便客户端更方便地使用。
  5. 桥接模式(Bridge)

    • 将抽象部分与它的实现部分相分离,使他们可以独立的变化。
    • 适用于抽象部分和实现部分需要独立变化的场景,如不同的操作系统平台上的图形界面实现。
  6. 组合模式(Composite)

    • 将对象组合成树形结构以表示部分整体的关系,使得用户对单个对象和组合对象的使用具有一致性。
    • 适用于需要表示部分与整体关系的场景,如文件系统的目录树结构。

三、行为型模式(观察者、迭代器、命令、责任链模式)

  1. 策略模式(Strategy)

    • 定义一系列的算法,把它们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
    • 适用于需要在不同时间应用不同的业务规则或算法的场景。
  2. 模板方法模式(Template Method)

    • 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
    • 适用于需要在多个子类中共享相同算法结构但具体实现不同的场景。
  3. 观察者模式(Observer)

    • 定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
    • 适用于需要实现对象间一对多通知的场景,如通知机制或KVO机制。
  4. 迭代器模式(Iterator)

    • 提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
    • 适用于需要遍历集合对象并访问其内部元素的场景。
  5. 命令模式(Command)

    • 将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
    • 适用于需要将请求封装为对象并进行参数化、排队或记录的场景。
  6. 状态模式(State)

    • 允许对象在其内部状态改变时改变他的行为,对象看起来似乎改变了他的类。
    • 适用于需要在对象内部状态改变时改变其行为的场景。
  7. 职责链模式(Chain of Responsibility)

    • 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
    • 适用于需要将请求传递给多个对象并依次处理的场景。
  8. 中介者模式(Mediator)

    • 用一个中介对象封装一系列的对象交互。
    • 适用于需要简化多个对象之间复杂交互关系的场景。
  9. 访问者模式(Visitor)

    • 表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
    • 适用于需要在不改变对象结构的情况下为对象增加新操作的场景。
  10. 解释器模式(Interpreter)

    • 给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
    • 适用于需要实现自定义语言或解析特定格式数据的场景。
  11. 备忘录模式(Memento)

    • 在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
    • 适用于需要保存和恢复对象状态的场景。
  12. 享元模式(Flyweight)

    • 使用共享对象,用以尽可能减少内存使用和提高性能。
    • 适用于需要创建大量相似对象的场景,通过共享对象来减少内存占用。

这些设计模式在iOS开发中都有其独特的应用场景和优势,可以根据具体需求选择合适的设计模式来提高代码的可维护性、可扩展性和可读性。

面试题:MVC和MVVM的区别

iOS中的MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)都是常用的软件架构模式,用于组织应用程序的代码。以下是两者之间的主要区别:

一、架构组成

  1. MVC

    • Model(模型) :负责处理数据逻辑和业务逻辑,是应用程序的数据源。
    • View(视图) :负责展示数据和用户界面,是应用程序的用户界面部分。
    • Controller(控制器) :负责协调Model和View之间的交互,并处理用户输入和应用程序逻辑。
  2. MVVM

    • Model(模型) :与MVC中的模型相同,负责处理数据逻辑和业务逻辑。
    • View(视图) :与MVC中的视图相同,负责展示数据和用户界面。
    • ViewModel(视图模型) :在MVVM中引入的新概念,负责将模型数据适配为视图所需要的数据格式,并处理用户输入和应用程序逻辑。它还可以通过数据绑定机制实现与View的双向绑定,使得数据的变化能够自动反映在视图上。

二、核心思想

  1. MVC

    • 将模型、视图和控制器层进行解耦合编写,降低组件之间的耦合度。
    • 控制器作为模型和视图之间的桥梁,负责处理用户输入、更新模型和视图。
  2. MVVM

    • 在MVC的基础上进一步解耦,将视图层的状态和行为抽象化,通过ViewModel实现视图和模型之间的分离。
    • 关注模型层的变化,利用MVVM框架的机制自动更新视图,实现数据-视图分离。

三、优缺点

  1. MVC

    • 优点

      • 耦合性相对较低,视图层和业务层有一定的分离。
      • 重用性高,多个视图可以共享一个模型。
      • 部署快,开发时间得到缩减。
      • 可维护性高,分离视图层和业务逻辑层使得应用更易于维护和修改。
    • 缺点

      • 没有明确的定义,理解和实现起来可能较为复杂。
      • 不适合小型或中等规模的应用程序,可能增加不必要的复杂性。
      • 视图与控制器间的连接过于紧密,妨碍了他们的独立重用。
      • 视图对模型数据的访问可能不够高效。
  2. MVVM

    • 优点

      • 耦合性更低,实现了视图和业务逻辑的完全分离。
      • 代码更加清晰和容易维护,ViewModel分离出大部分的Controller代码。
      • 方便测试,可以对ViewModel进行单元测试。
      • 开发解耦,允许不同的开发者分别负责逻辑实现和UI实现。
    • 缺点

      • 代码量相对较多,需要对每个Controller实现绑定。
      • 可能增加一些额外的复杂性,如数据绑定机制的实现。

四、应用场景

  1. MVC:适用于规模适中、业务逻辑相对简单的应用程序。它提供了足够的灵活性来组织代码,同时保持了相对简单的架构。
  2. MVVM:更适用于大型、复杂的应用程序,特别是那些需要频繁更新视图和数据的场景。它提供了更高的代码可维护性和可测试性,有助于降低开发成本和提高应用程序的质量。

综上所述,MVC和MVVM在iOS开发中各有优缺点和应用场景。开发者应根据具体项目的需求、规模和复杂度来选择合适的架构模式。

设计模式六大原则

一. 开闭原则

1. 概念:

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应尽量在不修改原(是“原”,指原来的代码)代码的情况下进行扩展。

2.模拟场景:

在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

3.Solution:

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

4.注意事项:

  • 通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法

  • 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类

  • 抽象层尽量保持稳定,一旦确定即不允许修改 5.开闭原则的优点:

  • 可复用性

  • 可维护性 6.开闭原则图解: image.png 二. 里氏代换原则

1.概述: 派生类(子类)对象能够替换其基类(父类)对象被调用

2.概念:

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3.子类为什么可以替换父类的位置?:

满足继承的时候,父类肯定存在非私有成员,子类肯定是得到了父类的这些非私有成员(假设,父类的的成员全部是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就可以在子类对象中调用这些非私有成员。所以,子类对象可以替换父类对象的位置。

4.里氏代换原则优点:

需求变化时,只须继承,而别的东西不会改变。由于里氏代换原则才使得开放封闭成为可能。这样使得子类在父类无需修改的话就可以扩展。

5.里氏代换原则Demo:

代码正文:

//------------------------------------------------------------------------------
// <copyright file="Program.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------

namespace TestApp
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Transportation transportation = new Transportation();
            transportation.Say();
            Transportation sedan = new Sedan();
            sedan.Say();
            Console.ReadKey();
        }
    }

    class Transportation
    {
        public Transportation()
        {
            Console.WriteLine("Transportation?");
        }

        public virtual void Say()
        {
            Console.WriteLine("121");
        }
    }

    class Sedan:Transportation
    {
        public Sedan()
        {
            Console.WriteLine("Transportation:Sedan");
        }

        public override void Say()
        {
            Console.WriteLine("Sedan");
        }
    }

    class Bicycles : Transportation
    {
        public Bicycles()
        {
            Console.WriteLine("Transportation:Bicycles");
        }

        public override void Say()
        {
            Console.WriteLine("Bicycles");
        }
    }
}

6.里氏代换原则图解:

image.png 三. 依赖倒转原则

1.概念:

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

2.依赖倒转原则用处:

有些时候为了代码复用,一般会把常用的代码写成函数或类库。这样开发新项目时,直接用就行了。比如做项目时大多要访问数据库,所以我们就把访问数据库的代码写成了函数。每次做项目去调用这些函数。那么我们的问题来了。我们要做新项目时,发现业务逻辑的高层模块都是一样的,但客户却希望使用不同的数据库或存储住处方式,这时就出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起,没办法复用这些高层模块。所以不管是高层模块和低层模块都应该依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个更改都不用担心了。

3.注意事项:

  • 高层模块不应该依赖低层模块。两个都应该依赖抽象
  • 抽象不应该依赖细节。细节应该依赖抽象

4.模拟场景:

场景:

假设现在需要一个Monitor工具,去运行一些已有的APP,自动化来完成我们的工作。Monitor工具需要启动这些已有的APP,并且写下Log。

代码实现1:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;

    public class AppOne
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne=1,
            AppTwo=2
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            return number == AppNumber.AppOne ? appOne.Start() : appTwo.Start();
        }

        public bool ExportAppLog()
        {
            return number == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog();
        }
    }
}

代码解析1:

在代码实现1中我们已经轻松实现了Monitor去运行已有APP并且写下LOG的需求。并且代码已经上线了.

春...夏...秋...冬...

春...夏...秋...冬...

春...夏...秋...冬...

就这样,三年过去了。

一天客户找上门了,公司业务扩展了,现在需要新加3个APP用Monitor自动化。这样我们就必须得改Monitor。

代码实现2:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne = 1,
            AppTwo = 2,
            AppThree = 3,
            AppFour = 4,
            AppFive = 5
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppThree appThree = new AppThree();
        private AppFour appFour = new AppFour();
        private AppFive appFive = new AppFive();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.Start();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.Start();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.Start();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.Start();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.Start();
            }

            return result;
        }

        public bool ExportAppLog()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.ExportLog();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.ExportLog();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.ExportLog();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.ExportLog();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.ExportLog();
            }

            return result;
        }
    }
}

代码解析2:

这样会给系统添加新的相互依赖。并且随着时间和需求的推移,会有更多的APP需要用Monitor来监测,这个Monitor工具也会被越来越对的if...else撑爆炸,而且代码随着APP越多,越难维护。最终会导致Monitor走向灭亡(下线)。

介于这种情况,可以用Monitor这个模块来生成其它的程序,使得系统能够用在需要的APP上。OOD给我们提供了一种机制来实现这种“依赖倒置”。

代码实现3:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;

    public interface IApp
    {
        bool Start();
        bool ExportLog();
    }

    public class AppOne : IApp
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo : IApp
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        private IApp iapp;
        public Monitor(IApp iapp)
        {
            this.iapp = iapp;
        }

        public bool StartApp()
        {
            return iapp.Start();
        }

        public bool ExportAppLog()
        {
            return iapp.ExportLog();
        }
    }
}

代码解析3:

现在Monitor依赖于IApp这个接口,而与具体实现的APP类没有关系,所以无论再怎么添加APP都不会影响到Monitor本身,只需要去添加一个实现IApp接口的APP类就可以了。

四. 接口隔离原则 1.概念:

客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上

2.含义:

接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则。

3.模拟场景:

一个OA系统,外部只负责提交和撤回工作流,内部负责审核和驳回工作流。

4.代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    public interface IReview
    {
        void ReviewWorkFlow();

        void RejectWorkFlow();
    }

    public class Review : IReview
    {
        public void ReviewWorkFlow()
        {
            Console.WriteLine("开始审核工作流");
        }

        public void RejectWorkFlow()
        {
            Console.WriteLine("已经驳回工作流");
        }
    }

    public interface ISubmit
    {
        void SubmitWorkFlow();

        void CancelWorkFlow();
    }

    public class Submit : ISubmit
    {
        public void SubmitWorkFlow()
        {
            Console.WriteLine("开始提交工作流");
        }

        public void CancelWorkFlow()
        {
            Console.WriteLine("已经撤销工作流");
        }
    }
}

5.代码解析:

其实接口隔离原则很好理解,在上面的例子里可以看出来,如果把OA的外部和内部都定义一个接口的话,那这个接口会很大,而且实现接口的类也会变得臃肿。

image.png

五. 合成/聚合复用原则 1.概念:

合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)经常又叫做合成复用原则。合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。

2.合成/聚合解析:

  • 聚合概念:

    聚合用来表示“拥有”关系或者整体与部分的关系。代表部分的对象有可能会被多个代表整体的对象所共享,而且不一定会随着某个代表整体的对象被销毁或破坏而被销毁或破坏,部分的生命周期可以超越整体。例如,Iphone5和IOS,当Iphone5删除后,IOS还能存在,IOS可以被Iphone6引用。

    聚合关系UML类图:

image.png 代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    class IOS
    { 
    }

    class Iphone5
    {
        private IOS ios;
        public Iphone5(IOS ios)
        {
            this.ios = ios;
        }
    }
}

合成概念:

合成用来表示一种强得多的“拥有”关系。在一个合成关系里,部分和整体的生命周期是一样的。一个合成的新对象完全拥有对其组成部分的支配权,包括它们的创建和湮灭等。使用程序语言的术语来说,合成而成的新对象对组成部分的内存分配、内存释放有绝对的责任。一个合成关系中的成分对象是不能与另一个合成关系共享的。一个成分对象在同一个时间内只能属于一个合成关系。如果一个合成关系湮灭了,那么所有的成分对象要么自己湮灭所有的成分对象(这种情况较为普遍)要么就得将这一责任交给别人(较为罕见)。例如:水和鱼的关系,当水没了,鱼也不可能独立存在。 合成关系UML类图:

image.png 代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;

    class Fish
    {
        public Fish CreateFish()
        {
            Console.WriteLine("一条小鱼儿");
            return new Fish();
        }
    }

    class Water
    {
        private Fish fish;
        public Water()
        {
            fish = new Fish();
        }

        public void CreateWater()
        {
            // 当创建了一个水的地方,那这个地方也得放点鱼进去
            fish.CreateFish();
        }
    }
}

3.模拟场景:

比如说我们先摇到号(这个比较困难)了,需要为自己买一辆车,如果4S店里的车默认的配置都是一样的。那么我们只要买车就会有这些配置,这时使用了继承关系:

image.png 不可能所有汽车的配置都是一样的,所以就有SUV和小轿车两种(只列举两种比较热门的车型),并且使用机动车对它们进行聚合使用。这时采用了合成/聚合的原则:

image.png 六. 迪米特法则 1.概念:

一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

2.模拟场景:

场景:公司财务总监发出指令,让财务部门的人去统计公司已发公司的人数。

一个常态的编程:(肯定是不符LoD的反例) UML类图:

image.png 代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// 财务总监
    /// </summary>
    public class CFO
    {
        /// <summary>
        /// 财务总监发出指令,让财务部门统计已发工资人数
        /// </summary>
        public void Directive(Finance finance)
        {
            List<Employee> employeeList = new List<Employee>();
            // 初始化已发工资人数
            for (int i = 0; i < 500; i++)
            {
                employeeList.Add(new Employee());
            }

            // 转告财务部门开始统计已结算公司的员工
            finance.SettlementSalary(employeeList);
        }
    }

    /// <summary>
    /// 财务部
    /// </summary>
    public class Finance
    {
        /// <summary>
        /// 统计已结算公司的员工
        /// </summary>
        public void SettlementSalary(List<Employee> employeeList) 
        {
            Console.WriteLine(string.Format("已结算工资人数:{0}", employeeList.Count));
        }
    }

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee
    {

    }

    /// <summary>
    /// 主程序
    /// </summary>
    public class Runner
    {
        public static void main(String[] args)
        {
            CFO cfo = new CFO();
            // 财务总监发出指令
            cfo.Directive(new Finance());
        }
    }
}

根据模拟的场景:财务总监让财务部门总结已发工资的人数。 财务总监和员工是陌生关系(即总监不需要对员工执行任何操作)。根据上述UML图和代码解决办法显然可以看出,上述做法违背了LoD法则。 依据LoD法则解耦:(符合LoD的例子)

UML类图:

image.png 代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     作      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// 财务总监
    /// </summary>
    public class CFO
    {
        /// <summary>
        /// 财务总监发出指令,让财务部门统计已发工资人数
        /// </summary>
        public void Directive(Finance finance)
        {
            // 通知财务部门开始统计已结算公司的员工
            finance.SettlementSalary();
        }
    }

    /// <summary>
    /// 财务部
    /// </summary>
    public class Finance
    {
        private List<Employee> employeeList;  

        //传递公司已工资的人
        public Finance(List<Employee> _employeeList)
        {
            this.employeeList = _employeeList;  
    }  

        /// <summary>
        /// 统计已结算公司的员工
        /// </summary>
        public void SettlementSalary() 
        {
            Console.WriteLine(string.Format("已结算工资人数:{0}", employeeList.Count));
        }
    }

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee
    {

    }

    /// <summary>
    /// 主程序
    /// </summary>
    public class Runner
    {
        public static void main(String[] args)
        {
            List<Employee> employeeList = new List<Employee>();

            // 初始化已发工资人数
            for (int i = 0; i < 500; i++)
            {
                employeeList.Add(new Employee());
            }

            CFO cfo = new CFO();

            // 财务总监发出指令
            cfo.Directive(new Finance(employeeList));
        }
    }
}

根据LoD原则我们需要让财务总监和员工之间没有之间的联系。这样才是遵守了迪米特法则。